Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

 Conflicts:
	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
	yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
This commit is contained in:
YunaiV 2023-09-11 16:19:55 +08:00
commit 8b50cd9661
153 changed files with 4837 additions and 647 deletions

View File

@ -16,11 +16,11 @@
<module>yudao-module-member</module>
<module>yudao-module-system</module>
<module>yudao-module-infra</module>
<!-- <module>yudao-module-pay</module>-->
<!-- <module>yudao-module-bpm</module>-->
<!-- <module>yudao-module-bpm</module>-->
<!-- <module>yudao-module-report</module>-->
<!-- <module>yudao-module-mp</module>-->
<!-- <module>yudao-module-mall</module>-->
<module>yudao-module-pay</module>
<module>yudao-module-mall</module>
<!-- 示例项目 -->
<module>yudao-example</module>
</modules>

221
sql/mysql/brokerage.sql Normal file
View File

@ -0,0 +1,221 @@
-- 增加配置表
create table trade_config
(
id bigint auto_increment comment '自增主键' primary key,
brokerage_enabled bit default 1 not null comment '是否启用分佣',
brokerage_enabled_condition tinyint default 0 not null comment '分佣模式1-人人分销 2-指定分销',
brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模式: 1-没有推广人2-新用户, 3-扫码覆盖',
brokerage_post_urls varchar(2000) default '' null comment '分销海报图地址数组',
brokerage_first_percent int default 0 not null comment '一级返佣比例',
brokerage_second_percent int default 0 not null comment '二级返佣比例',
brokerage_withdraw_min_price int default 0 not null comment '用户提现最低金额',
brokerage_bank_names varchar(200) default '' not null comment '提现银行字典类型=brokerage_bank_name',
brokerage_frozen_days int default 7 not null comment '佣金冻结时间()',
brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment '提现方式1-钱包2-银行卡3-微信4-支付宝',
creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
deleted bit default b'0' not null comment '是否删除',
tenant_id bigint default 0 not null comment '租户编号'
) comment '交易中心配置';
-- 增加分销用户扩展表
create table trade_brokerage_user
(
id bigint auto_increment comment '用户编号' primary key,
bind_user_id bigint null comment '推广员编号',
bind_user_time datetime null comment '推广员绑定时间',
brokerage_enabled bit default 1 not null comment '是否成为推广员',
brokerage_time datetime null comment '成为分销员时间',
price int default 0 not null comment '可用佣金',
frozen_price int default 0 not null comment '冻结佣金',
creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
deleted bit default b'0' not null comment '是否删除',
tenant_id bigint default 0 not null comment '租户编号'
) comment '分销用户';
create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编号';
create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是否成为推广员';
create table trade_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 trade_brokerage_record (user_id) comment '用户编号';
create index idx_biz on trade_brokerage_record (biz_type, biz_id) comment '业务';
create index idx_status on trade_brokerage_record (status) comment '状态';
create table trade_brokerage_withdraw
(
id int auto_increment comment '编号'
primary key,
user_id bigint not null comment '用户编号',
price int default 0 not null comment '提现金额',
fee_price int default 0 not null comment '提现手续费',
total_price int default 0 not null comment '当前总佣金',
type tinyint default 0 not null comment '提现类型1-钱包2-银行卡3-微信4-支付宝',
name varchar(64) null comment '真实姓名',
account_no varchar(64) null comment '账号',
bank_name varchar(100) null comment '银行名称',
bank_address varchar(200) null comment '开户地址',
account_qr_code_url varchar(512) null comment '收款码',
status tinyint(2) default 0 not null comment '状态0-审核中10-审核通过 20-审核不通过预留11 - 提现成功21-提现失败',
audit_reason varchar(128) null comment '审核驳回原因',
audit_time datetime null comment '审核时间',
remark varchar(500) null comment '备注',
creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
deleted bit default b'0' not null comment '是否删除',
tenant_id bigint default 0 not null comment '租户编号'
)
comment '佣金提现';
create index idx_user_id on trade_brokerage_withdraw (user_id) comment '用户编号';
create index idx_audit_status on trade_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', '人人分销', 1, 1, '所有用户都可以分销'),
('brokerage_enabled_condition', '指定分销', 2, 2, '仅可后台手动设置推广员');
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', '没有推广人', 1, 1, '只要用户没有推广人随时都可以绑定推广关系'),
('brokerage_bind_mode', '新用户', 2, 2, '仅新用户注册时才能绑定推广关系'),
('brokerage_bind_mode', '扫码覆盖', 3, 3, '如果用户已经有推广人推广人会被变更');
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', '订单返佣', 1, 1),
('brokerage_record_biz_type', '申请提现', 2, 2);
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);
-- 交易中心配置菜单 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('交易中心配置', '', 2, 0, 2072, 'config', 'ep:setting', 'trade/config/index', 0, 'TradeConfig');
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('交易中心配置查询', 'trade:config:query', 3, 1, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('交易中心配置保存', 'trade:config:save', 3, 2, @parentId, '', '', '', 0);
-- 增加菜单分销
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('分销', '', 1, 5, 2072, 'brokerage', 'fa-solid:project-diagram', '', 0, '');
-- 按钮父菜单ID
SELECT @brokerageMenuId := LAST_INSERT_ID();
-- 增加菜单分销员
-- 菜单 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('分销用户', '', 2, 0, @brokerageMenuId, 'brokerage-user', 'fa-solid:user-tie', 'trade/brokerage/user/index', 0,
'TradeBrokerageUser');
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户查询', 'trade:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户修改推广员', 'trade:brokerage-user:update-brokerage-user', 3, 5, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('分销用户清除推广员', 'trade:brokerage-user:clear-brokerage-user', 3, 6, @parentId, '', '', '', 0);
-- 增加菜单佣金记录
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('佣金记录', '', 2, 1, @brokerageMenuId, 'brokerage-record', 'fa:money', 'trade/brokerage/record/index', 0,
'TradeBrokerageRecord');
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('佣金记录查询', 'trade:brokerage-record:query', 3, 1, @parentId, '', '', '', 0);
-- 增加菜单佣金提现
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('佣金提现', '', 2, 2, @brokerageMenuId, 'brokerage-withdraw', 'fa:credit-card',
'trade/brokerage/withdraw/index', 0, 'TradeBrokerageWithdraw');
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);

View File

@ -1,5 +1,5 @@
-- ----------------------------
-- 支付-钱包表
-- 会员钱包表
-- ----------------------------
DROP TABLE IF EXISTS `pay_wallet`;
CREATE TABLE `pay_wallet`
@ -8,8 +8,8 @@ CREATE TABLE `pay_wallet`
`user_id` bigint NOT NULL COMMENT '用户编号',
`user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型',
`balance` int NOT NULL DEFAULT 0 COMMENT '余额单位分',
`total_expense` bigint NOT NULL DEFAULT 0 COMMENT '累计支出单位分',
`total_recharge` bigint NOT NULL DEFAULT 0 COMMENT '累计充值单位分',
`total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出单位分',
`total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值单位分',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -17,28 +17,27 @@ CREATE TABLE `pay_wallet`
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='支付钱包表';
) ENGINE=InnoDB COMMENT='会员钱包表';
-- ----------------------------
-- 支付- 钱包余额明细
-- 会员钱包流水
-- ----------------------------
DROP TABLE IF EXISTS `pay_wallet_transaction`;
CREATE TABLE `pay_wallet_transaction`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`wallet_id` bigint NOT NULL COMMENT '会员钱包 id',
`biz_type` tinyint NOT NULL COMMENT '关联类型',
`biz_id` bigint NOT NULL COMMENT '关联业务编号',
`no` varchar(64) NOT NULL COMMENT '流水号',
`description` varchar(255) COMMENT '操作说明',
`amount` int NOT NULL COMMENT '交易金额, 单位分',
`balance` int NOT NULL COMMENT '余额, 单位分',
`transaction_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '交易时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`wallet_id` bigint NOT NULL COMMENT '会员钱包 id',
`biz_type` tinyint NOT NULL COMMENT '关联类型',
`biz_id` varchar(64) NOT NULL COMMENT '关联业务编号',
`no` varchar(64) NOT NULL COMMENT '流水号',
`title` varchar(128) NOT NULL COMMENT '流水标题',
`price` int NOT NULL COMMENT '交易金额, 单位分',
`balance` int NOT NULL COMMENT '余额, 单位分',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='支付钱包余额明细';
) ENGINE=InnoDB COMMENT='会员钱包流水';

View File

@ -27,7 +27,7 @@
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version>
<dynamic-datasource.version>3.6.1</dynamic-datasource.version>
<mybatis-plus-join-boot-starter.version>1.4.5</mybatis-plus-join-boot-starter.version>
<mybatis-plus-join.version>1.4.6</mybatis-plus-join.version>
<redisson.version>3.18.0</redisson.version>
<dm8.jdbc.version>8.1.2.141</dm8.jdbc.version>
<!-- 服务保障相关 -->
@ -220,7 +220,7 @@
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
<version>${mybatis-plus-join-boot-starter.version}</version>
<version>${mybatis-plus-join.version}</version>
</dependency>
<dependency>

View File

@ -41,7 +41,7 @@ public class CommonResult<T> implements Serializable {
* 因为 A 方法返回的 CommonResult 对象不满足调用其的 B 方法的返回所以需要进行转换
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {

View File

@ -1,5 +1,9 @@
package cn.iocoder.yudao.framework.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
@ -7,6 +11,9 @@ import java.io.Serializable;
*
* 类名加了 ing 的原因是避免和 ES SortField 重名
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SortingField implements Serializable {
/**
@ -27,30 +34,4 @@ public class SortingField implements Serializable {
*/
private String order;
// 空构造方法解决反序列化
public SortingField() {
}
public SortingField(String field, String order) {
this.field = field;
this.order = order;
}
public String getField() {
return field;
}
public SortingField setField(String field) {
this.field = field;
return this;
}
public String getOrder() {
return order;
}
public SortingField setOrder(String order) {
this.order = order;
return this;
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.pay.util;
package cn.iocoder.yudao.framework.common.util.number;
import cn.hutool.core.util.NumberUtil;
@ -23,6 +23,17 @@ public class MoneyUtils {
return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();
}
/**
* 计算百分比金额向下传入
*
* @param price 金额
* @param rate 百分比例如说 56.77% 则传入 56.77
* @return 百分比金额
*/
public static Integer calculateRatePriceFloor(Integer price, Double rate) {
return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
}
/**
* 计算百分比金额
*

View File

@ -1,63 +0,0 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import java.util.Map;
// TODO @jason其它模块主要是无法 pay client 初始化存在问题所以我感觉是不是可以搞个 PayClientInitializer 接口这样PayClientFactory get 这个支付模式对应的 PayClientInitializer通过它来创建具体注入的地方可以在 PayChannel init 方法那
/**
* 代理支付 Client 的抽象类
*
* 用于支付 Client 由其它模块实现例如钱包支付
*
* @author jason
*/
public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
private final DelegatePayClient<Config> delegate;
public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
super(channelId, channelCode, config);
delegate = this;
}
@Override
protected void doInit() {
delegate.doInit();
}
@Override
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
return delegate.doUnifiedOrder(reqDTO);
}
@Override
protected PayOrderRespDTO doGetOrder(String outTradeNo) {
return delegate.doGetOrder(outTradeNo);
}
@Override
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
return delegate.doUnifiedRefund(reqDTO);
}
@Override
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
return delegate.doGetRefund(outTradeNo, outRefundNo);
}
@Override
protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body) {
return delegate.doParseRefundNotify(params, body);
}
@Override
protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body) {
return delegate.doParseOrderNotify(params, body);
}
}

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import java.time.LocalDateTime;
@ -17,11 +18,11 @@ import java.util.Map;
*
* @author jason
*/
public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
public MockPayClient(Long channelId, MockPayClientConfig config) {
public MockPayClient(Long channelId, NonePayClientConfig config) {
super(channelId, PayChannelEnum.MOCK.getCode(), config);
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.Validator;
/**
* 模拟支付的 PayClientConfig 实现类
*
* @author jason
*/
@Data
public class MockPayClientConfig implements PayClientConfig {
/**
* 配置名称
*
* 如果不加任何属性JsonUtils.parseObject2 解析会报错所以暂时加个名称
*/
private String name;
@Override
public void validate(Validator validator) {
// 模拟支付配置无需校验
}
}

View File

@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -30,7 +29,7 @@ public enum PayChannelEnum {
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
MOCK("mock", "模拟支付", MockPayClientConfig.class),
MOCK("mock", "模拟支付", NonePayClientConfig.class),
WALLET("wallet", "钱包支付", NonePayClientConfig.class);

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest;
import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMpRequest;
import com.xingyuv.jushauth.cache.AuthStateCache;
import com.xingyuv.jushauth.config.AuthConfig;
import com.xingyuv.jushauth.config.AuthSource;
@ -13,6 +14,8 @@ import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
import java.lang.reflect.Method;
import static com.xingyuv.jushauth.config.AuthDefaultSource.WECHAT_MP;
/**
* 第三方授权拓展 request 工厂类
* 为使得拓展配置 {@link AuthConfig} 和默认配置齐平所以自定义本工厂类
@ -55,6 +58,12 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory {
}
protected AuthRequest getExtendRequest(String source) {
// TODO 芋艿临时兼容 justauth 迁移的类型不对问题
if (WECHAT_MP.name().equalsIgnoreCase(source)) {
AuthConfig config = properties.getType().get(WECHAT_MP.name());
return new AuthWeChatMpRequest(config, authStateCache);
}
AuthExtendSource authExtendSource;
try {
authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase());

View File

@ -0,0 +1,178 @@
package cn.iocoder.yudao.framework.social.core.request;
import com.alibaba.fastjson.JSONObject;
import com.xingyuv.jushauth.cache.AuthStateCache;
import com.xingyuv.jushauth.config.AuthConfig;
import com.xingyuv.jushauth.config.AuthDefaultSource;
import com.xingyuv.jushauth.enums.AuthResponseStatus;
import com.xingyuv.jushauth.enums.AuthUserGender;
import com.xingyuv.jushauth.enums.scope.AuthWechatMpScope;
import com.xingyuv.jushauth.exception.AuthException;
import com.xingyuv.jushauth.model.AuthCallback;
import com.xingyuv.jushauth.model.AuthResponse;
import com.xingyuv.jushauth.model.AuthToken;
import com.xingyuv.jushauth.model.AuthUser;
import com.xingyuv.jushauth.request.AuthDefaultRequest;
import com.xingyuv.jushauth.utils.AuthScopeUtils;
import com.xingyuv.jushauth.utils.GlobalAuthUtils;
import com.xingyuv.jushauth.utils.HttpUtils;
import com.xingyuv.jushauth.utils.UrlBuilder;
/**
* 微信公众平台登录
*
* @author yangkai.shen (https://xkcoding.com)
* @since 1.1.0
*/
public class AuthWeChatMpRequest extends AuthDefaultRequest {
public AuthWeChatMpRequest(AuthConfig config) {
super(config, AuthDefaultSource.WECHAT_MP);
}
public AuthWeChatMpRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.WECHAT_MP, authStateCache);
}
/**
* 微信的特殊性此时返回的信息同时包含 openid access_token
*
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String openId = authToken.getOpenId();
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
if (object.containsKey("unionid")) {
authToken.setUnionId(object.getString("unionid"));
}
return AuthUser.builder()
.rawUserInfo(object)
.username(object.getString("nickname"))
.nickname(object.getString("nickname"))
.avatar(object.getString("headimgurl"))
.location(location)
.uuid(openId)
.gender(AuthUserGender.getWechatRealGender(object.getString("sex")))
.token(authToken)
.source(source.toString())
.build();
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("errcode")) {
throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
}
}
/**
* 获取token适用于获取access_token和刷新token
*
* @param accessTokenUrl 实际请求token的地址
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("openid"))
.scope(accessTokenObject.getString("scope"))
.build();
}
/**
* 返回带{@code state}参数的授权url授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("appid", config.getClientId())
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
.queryParam("response_type", "code")
.queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWechatMpScope.values())))
.queryParam("state", getRealState(state).concat("#wechat_redirect"))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("appid", config.getClientId())
.queryParam("secret", config.getClientSecret())
.queryParam("code", code)
.queryParam("grant_type", "authorization_code")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("openid", authToken.getOpenId())
.queryParam("lang", "zh_CN")
.build();
}
/**
* 返回获取userInfo的url
*
* @param refreshToken getAccessToken方法返回的refreshToken
* @return 返回获取userInfo的url
*/
@Override
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
.queryParam("appid", config.getClientId())
.queryParam("grant_type", "refresh_token")
.queryParam("refresh_token", refreshToken)
.build();
}
}

View File

@ -18,6 +18,9 @@ import java.util.List;
/**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
*
* 1. {@link BaseMapper} MyBatis Plus 的基础接口提供基础的 CRUD 能力
* 2. {@link MPJBaseMapper} MyBatis Plus Join 的基础接口提供连表 Join 能力
*/
public interface BaseMapperX<T> extends MPJBaseMapper<T> {

View File

@ -60,4 +60,14 @@ public class ProductSkuRespDTO {
*/
private Double volume;
// TODO @puhui 2 字段需要改下firstBrokerageRecordsecondBrokerageRecord和分佣保持一致
/**
* 一级分销的佣金单位
*/
private Integer subCommissionFirstPrice;
/**
* 二级分销的佣金单位
*/
private Integer subCommissionSecondPrice;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.promotion.api.bargain;
/**
* 砍价活动 Api 接口
*
* @author HUIHUI
*/
public interface BargainActivityApi {
/**
* 更新砍价活动库存
*
* @param activityId 砍价活动编号
* @param count 购买数量
*/
void updateBargainActivityStock(Long activityId, Integer count);
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.bargain.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
// TODO @芋艿这块要在看看
@ -40,17 +39,7 @@ public class BargainRecordCreateReqDTO {
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
// TODO @puhui999spuNamepicUrl 之类字段不用传递
/**
* 商品名字
*/
@NotEmpty(message = "商品名字不能为空")
private String spuName;
/**
* 商品图片
*/
@NotEmpty(message = "商品图片不能为空")
private String picUrl;
/**
* 砍价商品单价
*/
@ -61,17 +50,7 @@ public class BargainRecordCreateReqDTO {
*/
@NotNull(message = "商品原价不能为空")
private Integer price;
// TODO @puhui999nicknameavatar 不用传递去查询
/**
* 用户昵称
*/
@NotEmpty(message = "用户昵称不能为空")
private String nickname;
/**
* 用户头像
*/
@NotEmpty(message = "用户头像不能为空")
private String avatar;
/**
* 开团状态进行中 砍价成功 砍价失败
*/

View File

@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
// TODO @芋艿后面也再撸撸这几个接口
@ -51,13 +51,29 @@ public interface CombinationRecordApi {
*/
void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
// TODO @puhui999是不是搞成具体的方法拼团成功拼团失败这种方法
/**
* 更新拼团状态为成功
*
* @param userId 用户编号
* @param orderId 订单编号
*/
void updateRecordStatusToSuccess(Long userId, Long orderId);
/**
* 更新开团记录状态
* 更新拼团状态为失败
*
* @param reqDTO 请求 DTO
* @param userId 用户编号
* @param orderId 订单编号
*/
void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO);
void updateRecordStatusToFailed(Long userId, Long orderId);
/**
* 更新拼团状态为 进行中
*
* @param userId 用户编号
* @param orderId 订单编号
* @param startTime 开始时间
*/
void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime);
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 拼团记录的更新状态 Request DTO
*
* @author HUIHUI
*/
@Data
public class CombinationRecordUpdateStatusReqDTO {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
/**
* 开团状态正在开团 拼团成功 拼团失败
*/
@NotNull(message = "开团状态不能为空")
private Integer status;
/**
* 团开始时间
*/
private LocalDateTime startTime;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.promotion.api.seckill;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
/**
* 秒杀活动 API 接口
*
* @author HUIHUI
*/
public interface SeckillActivityApi {
/**
* 更新秒杀库存
*
* @param updateStockReqDTO 请求
*/
void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.promotion.api.seckill.dto;
import lombok.Data;
import java.util.List;
/**
* 更新秒杀库存 request DTO
*
* @author HUIHUI
*/
@Data
public class SeckillActivityUpdateStockReqDTO {
// TODO @puhui999参数校验
// TODO @puhui999秒杀的话一次只能购买一种商品哈不能多个哈
/**
* 活动编号
*/
private Long activityId;
/**
* 总购买数量
*/
private Integer count;
/**
* 活动商品
*/
private List<Item> items;
@Data
public static class Item {
/**
* SPU 编号
*/
private Long spuId;
/**
* SKU 编号
*/
private Long skuId;
/**
* 购买数量
*/
private Integer count;
}
}

View File

@ -55,6 +55,7 @@ public interface ErrorCodeConstants {
ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改");
ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "更新秒杀活动库存失败,原因秒杀库存不足");
// ========== 秒杀时段 1-013-009-000 ==========
ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.promotion.api.bargain;
import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BARGAIN_ACTIVITY_NOT_EXISTS;
/**
* 砍价活动 Api 接口实现类
*
* @author HUIHUI
*/
@Service
public class BargainActivityApiImpl implements BargainActivityApi {
@Resource
private BargainActivityService bargainActivityService;
@Override
public void updateBargainActivityStock(Long activityId, Integer count) {
// TODO @puhui999可以整个实现到 bargainActivityService
// 查询砍价活动
BargainActivityDO activity = bargainActivityService.getBargainActivity(activityId);
if (activity == null) {
throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
}
// 更新砍价库存
// TODO @puhui999考虑下并发更新问题
BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
reqVO.setId(activityId);
reqVO.setStock(activity.getStock() - count);
bargainActivityService.updateBargainActivity(reqVO);
}
}

View File

@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -43,12 +43,19 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
}
@Override
public void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO) {
if (null == reqDTO.getStartTime()) {
recordService.updateCombinationRecordStatusByUserIdAndOrderId(reqDTO);
} else {
recordService.updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(reqDTO);
}
public void updateRecordStatusToSuccess(Long userId, Long orderId) {
recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.SUCCESS.getStatus(), userId, orderId);
}
@Override
public void updateRecordStatusToFailed(Long userId, Long orderId) {
recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId);
}
@Override
public void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime) {
recordService.updateRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordStatusEnum.IN_PROGRESS.getStatus(),
userId, orderId, startTime);
}
}

View File

@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.promotion.api.seckill;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_UPDATE_STOCK_FAIL;
/**
* 秒杀活动接口 Api 接口实现类
*
* @author HUIHUI
*/
@Service
public class SeckillActivityApiImpl implements SeckillActivityApi {
@Resource
private SeckillActivityService activityService;
// TODO @puhui建议这块弄到 activityService 实现哈
// TODO @puhui这个方法要考虑事务性
@Override
public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
// TODO @puhui999长方法最好有 1.1 1.2 2.1 这种步骤哈
SeckillActivityDO seckillActivity = activityService.getSeckillActivity(updateStockReqDTO.getActivityId());
if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
// 获取活动商品
// TODO @puhui999在一个方法里dos dolist 最好保持一致要么用 s要么用 list
List<SeckillProductDO> productDOs = activityService.getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
// TODO @puhui999这个是不是搞成 CollectionUtils.convertMultiMap()
List<SeckillActivityUpdateStockReqDTO.Item> items = updateStockReqDTO.getItems();
Map<Long, List<Long>> map = new HashMap<>();
items.forEach(item -> {
if (map.containsKey(item.getSpuId())) {
List<Long> skuIds = map.get(item.getSpuId());
skuIds.add(item.getSkuId());
map.put(item.getSpuId(), skuIds);
} else {
List<Long> list = new ArrayList<>();
list.add(item.getSkuId());
map.put(item.getSpuId(), list);
}
});
// 过滤出购买的商品
// TODO @puhui999productDOList 可以简化成 productList一般来说do 之类不用带着哈在变量里
List<SeckillProductDO> productDOList = CollectionUtils.filterList(productDOs, item -> map.get(item.getSpuId()).contains(item.getSkuId()));
Map<Long, SeckillActivityUpdateStockReqDTO.Item> productDOMap = CollectionUtils.convertMap(items, SeckillActivityUpdateStockReqDTO.Item::getSkuId, p -> p);
// 检查活动商品库存是否充足
// TODO @puhui999避免 b 这种无业务含义的变量
boolean b = CollectionUtils.anyMatch(productDOList, item -> {
SeckillActivityUpdateStockReqDTO.Item item1 = productDOMap.get(item.getSkuId());
return (item.getStock() < item1.getCount()) || (item.getStock() - item1.getCount()) < 0;
});
if (b) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
// TODO @puhui999类似 doList应该和下面的 update 逻辑粘的更紧密一点so 在空行的时候应该挪到 74 之后里去甚至更合理应该是 79 之后说白了逻辑要分块每个模块涉及的代码要紧密在一起
List<SeckillProductDO> doList = CollectionUtils.convertList(productDOList, item -> {
item.setStock(item.getStock() - productDOMap.get(item.getSkuId()).getCount());
return item;
});
// 更新活动库存
// TODO @puhui999考虑下并发更新
seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
activityService.updateSeckillActivity(seckillActivity);
// 更新活动商品库存
activityService.updateSeckillActivityProductList(doList);
}
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.controller.app.combination;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO;
@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.Max;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -52,14 +52,13 @@ public class AppCombinationRecordController {
@RequestParam(value = "activityId", required = false) Long activityId,
@RequestParam("status") Integer status,
@RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) {
ZoneId zoneId = ZoneId.systemDefault();
List<AppCombinationRecordRespVO> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
AppCombinationRecordRespVO record = new AppCombinationRecordRespVO();
record.setId((long) i);
record.setNickname("用户" + i);
record.setAvatar("头像" + i);
record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId));
record.setExpireTime(LocalDateTime.now());
record.setUserSize(10);
record.setUserCount(i);
record.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
@ -74,14 +73,13 @@ public class AppCombinationRecordController {
@Operation(summary = "获得拼团记录明细")
@Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) {
ZoneId zoneId = ZoneId.systemDefault();
AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO();
// 团长
AppCombinationRecordRespVO headRecord = new AppCombinationRecordRespVO();
headRecord.setId(1L);
headRecord.setNickname("用户" + 1);
headRecord.setAvatar("头像" + 1);
headRecord.setExpireTime((LocalDateTime.ofInstant(DateUtils.addTime(Duration.ofDays(1)).toInstant(), zoneId)));
headRecord.setExpireTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
headRecord.setUserSize(10);
headRecord.setUserCount(3);
headRecord.setStatus(1);
@ -96,7 +94,7 @@ public class AppCombinationRecordController {
record.setId((long) i);
record.setNickname("用户" + i);
record.setAvatar("头像" + i);
record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId));
record.setExpireTime(LocalDateTime.now());
record.setUserSize(10);
record.setUserCount(i);
record.setStatus(1);

View File

@ -9,6 +9,7 @@ import lombok.*;
import java.time.LocalDateTime;
// TODO 芋艿把字段的顺序 do 顺序对齐下
/**
* 拼团记录 DO
*
@ -27,34 +28,28 @@ import java.time.LocalDateTime;
@AllArgsConstructor
public class CombinationRecordDO extends BaseDO {
/**
* 编号主键自增
*/
@TableId
private Long id;
/**
* 拼团活动编号
*
* 关联 {@link CombinationActivityDO#getId()}
*/
private Long activityId;
/**
* 拼团商品单价
*
* 冗余 {@link CombinationProductDO#getCombinationPrice()}
*/
private Integer combinationPrice;
/**
* SPU 编号
*/
private Long spuId;
/**
* SKU 编号
*/
private Long skuId;
/**
* 用户编号
*/
private Long userId;
/**
* 订单编号
*/
private Long orderId;
/**
* 团长编号
*
* 关联 {@link CombinationRecordDO#getId()}
*/
private Long headId;
/**
* 商品名字
*/
@ -64,9 +59,14 @@ public class CombinationRecordDO extends BaseDO {
*/
private String picUrl;
/**
* 拼团商品单价
* SKU 编号
*/
private Integer combinationPrice;
private Long skuId;
/**
* 用户编号
*/
private Long userId;
/**
* 用户昵称
*/
@ -75,6 +75,13 @@ public class CombinationRecordDO extends BaseDO {
* 用户头像
*/
private String avatar;
/**
* 团长编号
*
* 关联 {@link CombinationRecordDO#getId()}
*/
private Long headId;
/**
* 开团状态
*
@ -82,23 +89,9 @@ public class CombinationRecordDO extends BaseDO {
*/
private Integer status;
/**
* 是否虚拟成团
* 订单编号
*/
private Boolean virtualGroup;
/**
* 过期时间单位小时
*
* 关联 {@link CombinationActivityDO#getLimitDuration()}
*/
private Integer expireTime;
/**
* 开始时间 (订单付款后开始的时间)
*/
private LocalDateTime startTime;
/**
* 结束时间成团时间/失败时间
*/
private LocalDateTime endTime;
private Long orderId;
/**
* 开团需要人数
*
@ -109,5 +102,24 @@ public class CombinationRecordDO extends BaseDO {
* 已加入拼团人数
*/
private Integer userCount;
/**
* 是否虚拟成团
*/
private Boolean virtualGroup;
/**
* 过期时间
*
* 基于 {@link CombinationRecordDO#getStartTime()} + {@link CombinationActivityDO#getLimitDuration()} 计算
*/
private LocalDateTime expireTime;
/**
* 开始时间 (订单付款后开始的时间)
*/
private LocalDateTime startTime;
/**
* 结束时间成团时间/失败时间
*/
private LocalDateTime endTime;
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -16,9 +16,11 @@ public interface CombinationRecordService {
/**
* 更新拼团状态
*
* @param reqDTO 请求 DTO
* @param status 状态
* @param userId 用户编号
* @param orderId 订单编号
*/
void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO);
void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId);
/**
* 创建拼团记录
@ -30,9 +32,12 @@ public interface CombinationRecordService {
/**
* 更新拼团状态和开始时间
*
* @param reqDTO 请求 DTO
* @param status 状态
* @param userId 用户编号
* @param orderId 订单编号
* @param startTime 开始时间
*/
void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO);
void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime);
/**
* 获得拼团状态

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.service.combination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@ -21,6 +20,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
// TODO 芋艿等拼团记录做完完整 review
/**
* 拼团记录 Service 实现类
*
@ -38,27 +38,27 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) {
public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
// 校验拼团是否存在
CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId());
CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
// 更新状态
recordDO.setStatus(reqDTO.getStatus());
recordDO.setStatus(status);
recordMapper.updateById(recordDO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) {
CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId());
public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime) {
CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
// 更新状态
recordDO.setStatus(reqDTO.getStatus());
recordDO.setStatus(status);
// 更新开始时间
recordDO.setStartTime(reqDTO.getStartTime());
recordDO.setStartTime(startTime);
recordMapper.updateById(recordDO);
// 更新拼团参入人数
List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), reqDTO.getStatus());
List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
if (CollUtil.isNotEmpty(recordDOs)) {
recordDOs.forEach(item -> {
item.setUserCount(recordDOs.size());
@ -115,8 +115,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
// 2. 创建拼团记录
CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO);
record.setVirtualGroup(false);
// TODO @puhui999过期时间应该是 Date
record.setExpireTime(activity.getLimitDuration());
record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
record.setUserSize(activity.getUserSize());
recordMapper.insert(record);
}

View File

@ -33,6 +33,20 @@ public interface SeckillActivityService {
*/
void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
/**
* 更新秒杀活动
*
* @param activityDO 秒杀活动
*/
void updateSeckillActivity(SeckillActivityDO activityDO);
/**
* 更新秒杀活动商品
*
* @param productList 活动商品列表
*/
void updateSeckillActivityProductList(List<SeckillProductDO> productList);
/**
* 关闭秒杀活动
*

View File

@ -79,8 +79,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
* 1. 校验秒杀时段是否存在
* 2. 秒杀商品是否参加其它活动
*
* @param configIds 秒杀时段数组
* @param spuId 商品 SPU 编号
* @param configIds 秒杀时段数组
* @param spuId 商品 SPU 编号
* @param activityId 秒杀活动编号
*/
private void validateProductConflict(List<Long> configIds, Long spuId, Long activityId) {
@ -92,15 +92,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
if (activityId != null) { // 排除自己
activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
}
// TODO @puhui999一个 spu参与两个活动应该没关系关键是活动时间不充能重叠
// 2.2 过滤出所有 spuId 有交集的活动判断是否存在重叠
List<SeckillActivityDO> activityDOs1 = filterList(activityList, s -> ObjectUtil.equal(s.getSpuId(), spuId));
if (isNotEmpty(activityDOs1)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
// 2.3 过滤出所有 configIds 有交集的活动判断是否存在重叠
List<SeckillActivityDO> activityDOs2 = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
if (isNotEmpty(activityDOs2)) {
// 2.2 过滤出所有 configIds 有交集的活动判断是否存在重叠
List<SeckillActivityDO> conflictActivityList = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
if (isNotEmpty(conflictActivityList)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
}
@ -108,7 +102,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
/**
* 校验秒杀商品是否都存在
*
* @param spuId 商品 SPU 编号
* @param spuId 商品 SPU 编号
* @param products 秒杀商品
*/
private void validateProductExists(Long spuId, List<SeckillProductBaseVO> products) {
@ -150,11 +144,21 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
updateSeckillProduct(updateObj, updateReqVO.getProducts());
}
@Override
public void updateSeckillActivity(SeckillActivityDO activityDO) {
seckillActivityMapper.updateById(activityDO);
}
@Override
public void updateSeckillActivityProductList(List<SeckillProductDO> productList) {
seckillProductMapper.updateBatch(productList);
}
/**
* 更新秒杀商品
*
* @param activity 秒杀活动
* @param products 该活动的最新商品配置
* @param products 该活动的最新商品配置
*/
private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductBaseVO> products) {
// 第一步对比新老数据获得添加修改删除的列表

View File

@ -1,18 +1,9 @@
package cn.iocoder.yudao.module.promotion.util;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
/**
* 活动工具类
@ -31,21 +22,4 @@ public class PromotionUtils {
return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
}
/**
* 校验商品 sku 是否都存在
*
* @param skus 数据库中的商品 skus
* @param products 需要校验的商品
* @param func 获取需要校验的商品的 skuId
*/
public static <T> void validateProductSkuAllExists(List<ProductSkuRespDTO> skus, List<T> products, Function<T, Long> func) {
// 校验 sku 个数是否一致
Set<Long> skuIdsSet = CollectionUtils.convertSet(products, func);
Set<Long> skuIdsSet1 = CollectionUtils.convertSet(skus, ProductSkuRespDTO::getId);
// 校验 skuId 是否存在
if (anyMatch(skuIdsSet, s -> !skuIdsSet1.contains(s))) {
throw exception(SKU_NOT_EXISTS);
}
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.trade.api.brokerage;
import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
/**
* 分销 API 接口
*
* @author owen
*/
public interface BrokerageApi {
/**
* 获得分销用户
*
* @param userId 用户编号
* @return 分销用户信息
*/
BrokerageUserDTO getBrokerageUser(Long userId);
/**
* 绑定推广员
*
* @param userId 用户编号
* @param bindUserId 推广员编号
* @param isNewUser 是否为新用户
* @return 是否绑定
*/
boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser);
}

View File

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

View File

@ -11,7 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
*/
public interface ErrorCodeConstants {
// ========== Order 模块 1-011-000-000 ==========
// ========== Order 模块 1-011-000-000 ==========
ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1_011_000_001, "商品 SKU 不存在");
ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1_011_000_002, "商品 SPU 不可售卖");
ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_011_000_004, "商品 SKU 库存不足");
@ -35,7 +35,7 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递");
ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态");
// ========== After Sale 模块 1-011-000-100 ==========
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误");
ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后");
@ -50,7 +50,7 @@ public interface ErrorCodeConstants {
ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
// ========== Cart 模块 1-011-002-000 ==========
// ========== Cart 模块 1-011-002-000 ==========
ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
// ========== Price 相关 1-011-003-000 ============
@ -58,7 +58,7 @@ public interface ErrorCodeConstants {
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1_011_003_001, "计算快递运费异常,收件人地址编号为空");
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
// ========== 物流 Express 模块 1-011-004-000 ==========
// ========== 物流 Express 模块 1-011-004-000 ==========
ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司");
ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商比如【快递100】");
@ -67,11 +67,21 @@ public interface ErrorCodeConstants {
ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常");
ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}");
// ========== 物流 Template 模块 1-011-005-000 ==========
// ========== 物流 Template 模块 1-011-005-000 ==========
ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名");
ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在");
// ========== 物流 PICK_UP 模块 1-011-006-000 ==========
ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
// ========== 分销用户 模块 1011007000 ==========
ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");
ErrorCode BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH = new ErrorCode(1011007001, "用户冻结佣金({})数量不足");
ErrorCode BROKERAGE_BIND_SELF = new ErrorCode(1011007002, "不能绑定自己");
ErrorCode BROKERAGE_BIND_USER_NOT_ENABLED = new ErrorCode(1011007003, "绑定用户没有推广资格");
ErrorCode BROKERAGE_BIND_CONDITION_ADMIN = new ErrorCode(1011007004, "仅可在后台绑定推广员");
ErrorCode BROKERAGE_BIND_MODE_REGISTER = new ErrorCode(1011007005, "只有在注册时可以绑定");
ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1011007006, "已绑定了推广人");
ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1011007007, "下级不能绑定自己的上级");
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 分销关系绑定模式枚举
*
* @author owen
*/
@AllArgsConstructor
@Getter
public enum BrokerageBindModeEnum implements IntArrayValuable {
/**
* 只要用户没有推广人随时都可以绑定分销关系
*/
ANYTIME(1, "没有推广人"),
/**
* 仅新用户注册时才能绑定推广关系
*/
REGISTER(2, "新用户"),
/**
* 每次扫码都覆盖
*/
OVERRIDE(3, "扫码覆盖"),
;
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;
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 分佣模式枚举
*
* @author owen
*/
@AllArgsConstructor
@Getter
public enum BrokerageEnabledConditionEnum implements IntArrayValuable {
/**
* 所有用户都可以分销
*/
ALL(1, "人人分销"),
/**
* 仅可后台手动设置推广员
*/
ADMIN(2, "指定分销"),
;
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;
}
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 佣金记录业务类型枚举
*
* @author owen
*/
@AllArgsConstructor
@Getter
public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", 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;
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 佣金记录状态枚举
*
* @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;
}
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 佣金提现状态枚举
*
* @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;
}
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 佣金提现类型枚举
*
* @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;
}
}

View File

@ -54,6 +54,10 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
@ -82,6 +86,11 @@
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

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

View File

@ -76,6 +76,11 @@ public class TradeAfterSaleController {
public CommonResult<TradeAfterSaleDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
// 查询订单
TradeAfterSaleDO afterSale = afterSaleService.getAfterSale(id);
// TODO @puhui999这里建议改成如果为 null直接返回 success null主要查询操作尽量不要有非空的提示哈交给前端处理
// if (afterSale == null) {
// return success(null, AFTER_SALE_NOT_FOUND.getMsg());
// }
// 查询订单
TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId());
// 查询订单项
@ -92,7 +97,11 @@ public class TradeAfterSaleController {
TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
respVO.setId((long) i);
respVO.setUserId((long) i);
respVO.setUserType(1);
respVO.setUserType(i % 2 == 0 ? 2 : 1);
// 模拟系统操作
if (i == 2) {
respVO.setUserType(3);
}
respVO.setAfterSaleId(id);
respVO.setOrderId((long) i);
respVO.setOrderItemId((long) i);

View File

@ -35,7 +35,7 @@ public class TradeAfterSaleDetailRespVO extends TradeAfterSaleBaseVO {
/**
* 售后日志
*/
private List<TradeAfterSaleLogRespVO> afterSaleLog;
private List<TradeAfterSaleLogRespVO> logs;
@Schema(description = "管理后台 - 交易订单的详情的订单项目")
@Data

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.trade.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.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
import cn.iocoder.yudao.module.trade.convert.brokerage.record.BrokerageRecordConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
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("/trade/brokerage-record")
@Validated
public class BrokerageRecordController {
@Resource
private BrokerageRecordService brokerageRecordService;
@GetMapping("/get")
@Operation(summary = "获得佣金记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
BrokerageRecordDO brokerageRecord = brokerageRecordService.getBrokerageRecord(id);
return success(BrokerageRecordConvert.INSTANCE.convert(brokerageRecord));
}
@GetMapping("/page")
@Operation(summary = "获得佣金记录分页")
@PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
public CommonResult<PageResult<BrokerageRecordRespVO>> getBrokerageRecordPage(@Valid BrokerageRecordPageReqVO pageVO) {
PageResult<BrokerageRecordDO> pageResult = brokerageRecordService.getBrokerageRecordPage(pageVO);
return success(BrokerageRecordConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.trade.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.NotEmpty;
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 BrokerageRecordBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23353")
@NotEmpty(message = "业务编号不能为空")
private String bizId;
@Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "业务类型不能为空")
private Integer bizType;
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(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;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.trade.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 BrokerageRecordPageReqVO 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;
}

View File

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

View File

@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.*;
import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - 分销用户")
@RestController
@RequestMapping("/trade/brokerage-user")
@Validated
public class BrokerageUserController {
@Resource
private BrokerageUserService brokerageUserService;
@Resource
private BrokerageRecordService brokerageRecordService;
@Resource
private MemberUserApi memberUserApi;
@PutMapping("/update-brokerage-user")
@Operation(summary = "修改推广员")
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-user')")
public CommonResult<Boolean> updateBrokerageUser(@Valid @RequestBody BrokerageUserUpdateBrokerageUserReqVO updateReqVO) {
brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), updateReqVO.getBindUserId());
return success(true);
}
@PutMapping("/clear-brokerage-user")
@Operation(summary = "清除推广员")
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:clear-brokerage-user')")
public CommonResult<Boolean> clearBrokerageUser(@Valid @RequestBody BrokerageUserClearBrokerageUserReqVO updateReqVO) {
brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), null);
return success(true);
}
@PutMapping("/update-brokerage-enable")
@Operation(summary = "修改推广资格")
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-enable')")
public CommonResult<Boolean> updateBrokerageEnabled(@Valid @RequestBody BrokerageUserUpdateBrokerageEnabledReqVO updateReqVO) {
brokerageUserService.updateBrokerageUserEnabled(updateReqVO.getId(), updateReqVO.getEnabled());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得分销用户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
public CommonResult<BrokerageUserRespVO> getBrokerageUser(@RequestParam("id") Long id) {
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id);
return success(BrokerageUserConvert.INSTANCE.convert(brokerageUser));
}
@GetMapping("/page")
@Operation(summary = "获得分销用户分页")
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
public CommonResult<PageResult<BrokerageUserRespVO>> getBrokerageUserPage(@Valid BrokerageUserPageReqVO pageVO) {
// 分页查询
PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
// 涉及到的用户
Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
// 查询用户信息
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
// 合计分佣订单
Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
userId -> userId,
userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
// 合计推广用户数量
Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
userId -> userId,
userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId));
// todo 合计提现
return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap, userOrderSummaryMap));
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.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 BrokerageUserBaseVO {
@Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
@NotNull(message = "推广员编号不能为空")
private Long bindUserId;
@Schema(description = "推广员绑定时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime bindUserTime;
@Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "推广资格不能为空")
private Boolean brokerageEnabled;
@Schema(description = "成为分销员时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime brokerageTime;
@Schema(description = "可用佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "11089")
@NotNull(message = "可用佣金不能为空")
private Integer price;
@Schema(description = "冻结佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "30916")
@NotNull(message = "冻结佣金不能为空")
private Integer frozenPrice;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 清除推广员 Request VO")
@Data
@ToString(callSuper = true)
public class BrokerageUserClearBrokerageUserReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
@NotNull(message = "用户编号不能为空")
private Long id;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.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 BrokerageUserPageReqVO extends PageParam {
@Schema(description = "推广员编号", example = "4587")
private Long bindUserId;
@Schema(description = "推广资格")
private Boolean brokerageEnabled;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.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 BrokerageUserRespVO extends BrokerageUserBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
// ========== 用户信息 ==========
@Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
private String avatar;
@Schema(description = "用户昵称", example = "李四")
private String nickname;
// ========== 推广信息 ==========
@Schema(description = "推广用户数量(一级)", example = "20019")
private Integer brokerageUserCount;
@Schema(description = "推广订单数量", example = "20019")
private Integer brokerageOrderCount;
@Schema(description = "推广订单金额", example = "20019")
private Integer brokerageOrderPrice;
// ========== 提现信息 ==========
@Schema(description = "已提现金额", example = "20019")
private Integer withdrawPrice;
@Schema(description = "已提现次数", example = "20019")
private Integer withdrawCount;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
@Data
@ToString(callSuper = true)
public class BrokerageUserUpdateBrokerageEnabledReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
@NotNull(message = "用户编号不能为空")
private Long id;
@Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "推广资格不能为空")
private Boolean enabled;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
@Data
@ToString(callSuper = true)
public class BrokerageUserUpdateBrokerageUserReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
@NotNull(message = "用户编号不能为空")
private Long id;
@Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
@NotNull(message = "推广员编号不能为空")
private Long bindUserId;
}

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.trade.controller.admin.config;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 交易中心配置")
@RestController
@RequestMapping("/trade/config")
@Validated
public class TradeConfigController {
@Resource
private TradeConfigService tradeConfigService;
@PutMapping("/save")
@Operation(summary = "更新交易中心配置")
@PreAuthorize("@ss.hasPermission('trade:config:save')")
public CommonResult<Boolean> updateConfig(@Valid @RequestBody TradeConfigSaveReqVO updateReqVO) {
tradeConfigService.saveTradeConfig(updateReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得交易中心配置")
@PreAuthorize("@ss.hasPermission('trade:config:query')")
public CommonResult<TradeConfigRespVO> getConfig() {
TradeConfigDO config = tradeConfigService.getTradeConfig();
return success(TradeConfigConvert.INSTANCE.convert(config));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import java.util.List;
/**
* 交易中心配置 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class TradeConfigBaseVO {
// ========== 分销相关 ==========
@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;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 交易中心配置 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TradeConfigRespVO extends TradeConfigBaseVO {
@Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 交易中心配置更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TradeConfigSaveReqVO extends TradeConfigBaseVO {
}

View File

@ -67,6 +67,11 @@ public class TradeOrderController {
public CommonResult<TradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
// 查询订单
TradeOrderDO order = tradeOrderQueryService.getOrder(id);
// TODO @puhui999这里建议改成如果为 null直接返回 success null主要查询操作尽量不要有非空的提示哈交给前端处理
// if (order == null) {
// return success(null, ORDER_NOT_FOUND.getMsg());
// }
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
// orderLog

View File

@ -26,24 +26,23 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
private MemberUserRespVO user;
/**
* TODO 订单操作日志, 先模拟一波返回 logs简洁然后复数哈
* TODO 订单操作日志, 先模拟一波
*/
private List<OrderLog> orderLog;
private List<OrderLog> logs;
// TODO @puhui999swagger 注解
@Schema(description = "管理后台 - 交易订单的操作日志")
@Data
public static class OrderLog {
/**
* 内容
*/
@Schema(description = "操作详情", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单发货")
private String content;
/**
* 创建时间
*/
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-01 10:50:20")
private LocalDateTime createTime;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer userType;
}
@Schema(description = "管理后台 - 交易订单的详情的订单项目")

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.trade.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.trade.controller.app.brokerage.vo.record.AppBrokerageProductPriceRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
import cn.iocoder.yudao.module.trade.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.RequestParam;
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("/trade/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));
}
@GetMapping("/get-product-brokerage-price")
@Operation(summary = "获得商品的分销金额")
public CommonResult<AppBrokerageProductPriceRespVO> getProductBrokeragePrice(@RequestParam("spuId") Long spuId) {
AppBrokerageProductPriceRespVO respVO = new AppBrokerageProductPriceRespVO();
respVO.setEnabled(true); // TODO @疯狂需要开启分销 + 人允许分销
respVO.setBrokerageMinPrice(1);
respVO.setBrokerageMaxPrice(2);
return success(respVO);
}
}

View File

@ -0,0 +1,134 @@
package cn.iocoder.yudao.module.trade.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.trade.controller.app.brokerage.vo.user.*;
import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Arrays.asList;
@Tag(name = "用户 APP - 分销用户")
@RestController
@RequestMapping("/trade/brokerage-user")
@Validated
@Slf4j
public class AppBrokerageUserController {
@Resource
private BrokerageUserService brokerageUserService;
// TODO 芋艿临时 mock =>
@GetMapping("/get")
@Operation(summary = "获得个人分销信息")
@PreAuthenticated
public CommonResult<AppBrokerageUserRespVO> getBrokerageUser() {
AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO()
.setBrokerageEnabled(true)
.setPrice(2000)
.setFrozenPrice(3000);
return success(respVO);
}
@PutMapping("/bind")
@Operation(summary = "绑定推广员")
@PreAuthenticated
public CommonResult<Boolean> bindBrokerageUser(@Valid @RequestBody AppBrokerageUserBindReqVO reqVO) {
return success(brokerageUserService.bindBrokerageUser(getLoginUserId(), reqVO.getBindUserId(), false));
}
// TODO 芋艿临时 mock =>
@GetMapping("/get-summary")
@Operation(summary = "获得个人分销统计")
@PreAuthenticated
public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
.setYesterdayPrice(1)
.setBrokeragePrice(2)
.setFrozenPrice(3)
.setWithdrawPrice(4)
.setFirstBrokerageUserCount(166)
.setSecondBrokerageUserCount(233);
return success(respVO);
}
// TODO 芋艿临时 mock =>
@GetMapping("/rank-page-by-user-count")
@Operation(summary = "获得分销用户排行分页(基于用户量)")
@PreAuthenticated
public CommonResult<PageResult<AppBrokerageUserRankByUserCountRespVO>> getBrokerageUserRankPageByUserCount(AppBrokerageUserRankPageReqVO pageReqVO) {
AppBrokerageUserRankByUserCountRespVO vo1 = new AppBrokerageUserRankByUserCountRespVO()
.setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokerageUserCount(10);
AppBrokerageUserRankByUserCountRespVO vo2 = new AppBrokerageUserRankByUserCountRespVO()
.setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokerageUserCount(6);
AppBrokerageUserRankByUserCountRespVO vo3 = new AppBrokerageUserRankByUserCountRespVO()
.setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokerageUserCount(4);
AppBrokerageUserRankByUserCountRespVO vo4 = new AppBrokerageUserRankByUserCountRespVO()
.setId(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));
}
// TODO 芋艿临时 mock =>
@GetMapping("/rank-page-by-price")
@Operation(summary = "获得分销用户排行分页(基于佣金)")
@PreAuthenticated
public CommonResult<PageResult<AppBrokerageUserRankByPriceRespVO>> getBrokerageUserChildSummaryPageByPrice(AppBrokerageUserRankPageReqVO pageReqVO) {
AppBrokerageUserRankByPriceRespVO vo1 = new AppBrokerageUserRankByPriceRespVO()
.setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(10);
AppBrokerageUserRankByPriceRespVO vo2 = new AppBrokerageUserRankByPriceRespVO()
.setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(6);
AppBrokerageUserRankByPriceRespVO vo3 = new AppBrokerageUserRankByPriceRespVO()
.setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(4);
AppBrokerageUserRankByPriceRespVO vo4 = new AppBrokerageUserRankByPriceRespVO()
.setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(4);
return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
}
// TODO 芋艿临时 mock =>
@GetMapping("/child-summary-page")
@Operation(summary = "获得下级分销统计分页")
@PreAuthenticated
public CommonResult<PageResult<AppBrokerageUserChildSummaryRespVO>> getBrokerageUserChildSummaryPage(
AppBrokerageUserChildSummaryPageReqVO pageReqVO) {
AppBrokerageUserChildSummaryRespVO vo1 = new AppBrokerageUserChildSummaryRespVO()
.setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(10).setBrokeragePrice(20).setBrokerageOrderCount(30)
.setBrokerageTime(LocalDateTime.now());
AppBrokerageUserChildSummaryRespVO vo2 = new AppBrokerageUserChildSummaryRespVO()
.setId(1L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
.setBrokeragePrice(20).setBrokeragePrice(30).setBrokerageOrderCount(40)
.setBrokerageTime(LocalDateTime.now());
return success(new PageResult<>(asList(vo1, vo2), 10L));
}
// TODO 芋艿临时 mock =>
@GetMapping("/get-rank-by-price")
@Operation(summary = "获得分销用户排行(基于佣金)")
@Parameter(name = "times", description = "时间段", required = true)
public CommonResult<Integer> bindBrokerageUser(
@RequestParam("times") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] times) {
return success(1);
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.trade.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.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
import cn.iocoder.yudao.module.trade.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.*;
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("/trade/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));
}
// TODO 芋艿临时 mock =>
@PostMapping("/create")
@Operation(summary = "创建分销提现")
@PreAuthenticated
public CommonResult<Long> createBrokerageWithdraw(@RequestBody @Valid AppBrokerageWithdrawCreateReqVO createReqVO) {
return success(1L);
}
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 商品的分销金额 Response VO")
@Data
public class AppBrokerageProductPriceRespVO {
@Schema(description = "是否开启", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Boolean enabled;
@Schema(description = "分销最小金额,单位:分", example = "100")
private Integer brokerageMinPrice;
@Schema(description = "分销最大金额,单位:分", example = "100")
private Integer brokerageMaxPrice;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "应用 App - 分销记录分页 Request VO")
@Data
public class AppBrokerageRecordPageReqVO extends PageParam {
@Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = BrokerageRecordBizTypeEnum.class, message = "业务类型必须是 {value}")
private Integer bizType;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = BrokerageRecordStatusEnum.class, message = "状态必须是 {value}")
private Integer status;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.trade.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;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.trade.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 javax.validation.constraints.NotNull;
@Schema(description = "应用 App - 绑定推广员 Request VO")
@Data
public class AppBrokerageUserBindReqVO extends PageParam {
@Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "推广员编号不能为空")
private Long bindUserId;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 下级分销统计分页 Request VO")
@Data
public class AppBrokerageUserChildSummaryPageReqVO extends PageParam {
public static final String SORT_FIELD_USER_COUNT = "userCount";
public static final String SORT_FIELD_ORDER_COUNT = "orderCount";
public static final String SORT_FIELD_PRICE = "price";
@Schema(description = "用户昵称", example = "") // 模糊匹配
private String nickname;
@Schema(description = "排序字段", example = "userCount")
private SortingField sortingField;
@Schema(description = "下级的级别", example = "1") // 1 - 直接下级2 - 间接下级
private Integer level;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - 下级分销统计 Response VO")
@Data
public class AppBrokerageUserChildSummaryRespVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
private String nickname;
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
private String avatar;
@Schema(description = "佣金金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer brokeragePrice;
@Schema(description = "分销订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer brokerageOrderCount;
@Schema(description = "分销用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
private Integer brokerageUserCount;
@Schema(description = "成为分销员时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime brokerageTime;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 个人分销统计 Response VO")
@Data
public class AppBrokerageUserMySummaryRespVO {
@Schema(description = "昨天的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer yesterdayPrice;
@Schema(description = "提现的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer withdrawPrice;
@Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
private Integer brokeragePrice;
@Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
private Integer frozenPrice;
@Schema(description = "分销用户数量(一级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer firstBrokerageUserCount;
@Schema(description = "分销用户数量(二级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer secondBrokerageUserCount;
}

View File

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

View File

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

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.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;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 分销用户信息 Response VO")
@Data
public class AppBrokerageUserRespVO {
@Schema(description = "是否有分销资格", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean brokerageEnabled;
@Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
private Integer price;
@Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
private Integer frozenPrice;
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.Validator;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
@Schema(description = "用户 App - 分销提现创建 Request VO")
@Data
public class AppBrokerageWithdrawCreateReqVO {
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
private Integer type;
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
@Min(value = 1, message = "提现金额不能小于 1")
private Integer price;
// ========== 银行卡微信支付宝 提现相关字段 ==========
@Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789")
@NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class})
private String accountNo;
// ========== 微信支付宝 提现相关字段 ==========
@Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png")
@URL(message = "收款码的图片,必须是一个 URL")
private String accountQrCodeUrl;
// ========== 银行卡 提现相关字段 ==========
@Schema(description = "持卡人姓名", example = "张三")
@NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class})
private String name;
@Schema(description = "提现银行", example = "1")
@NotBlank(message = "提现银行不能为空", groups = {Bank.class})
private Integer bankName;
@Schema(description = "开户地址", example = "海淀支行")
private String bankAddress;
public interface Wallet {
}
public interface Bank {
}
public interface Wechat {
}
public interface Alipay {
}
public void validate(Validator validator) {
if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(type)) {
ValidationUtils.validate(validator, this, Wallet.class);
} else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) {
ValidationUtils.validate(validator, this, Bank.class);
} else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) {
ValidationUtils.validate(validator, this, Wechat.class);
} else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) {
ValidationUtils.validate(validator, this, Alipay.class);
}
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.trade.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;
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.trade.controller.app.config;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.trade.controller.app.config.vo.AppTradeConfigRespVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static java.util.Arrays.asList;
@Tag(name = "用户 App - 交易配置")
@RestController
@RequestMapping("/trade/config")
@RequiredArgsConstructor
@Validated
@Slf4j
public class AppTradeConfigController {
@GetMapping("/get")
public CommonResult<AppTradeConfigRespVO> getTradeConfig() {
AppTradeConfigRespVO respVO = new AppTradeConfigRespVO();
respVO.setBrokeragePosterUrls(asList(
"https://api.java.crmeb.net/crmebimage/product/2020/08/03/755bf516b1ca4b6db3bfeaa4dd5901cdh71kob20re.jpg",
"https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/406d729b84ed4ec9a2171bfcf6fd0634ughzbz9kfi.jpg",
"https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/efb1e4e7fe604fe1988b4213ce08cb11tdsyijtd2r.jpg"
));
respVO.setBrokerageFrozenDays(10);
respVO.setBrokerageWithdrawMinPrice(100);
return success(respVO);
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.trade.controller.app.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "用户 App - 交易配置 Response VO")
@Data
public class AppTradeConfigRespVO {
@Schema(description = "分销海报地址数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> brokeragePosterUrls;
@Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer brokerageFrozenDays;
@Schema(description = "佣金提现最小金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer brokerageWithdrawMinPrice;
}

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
// TODO 芋艿后续要实现下配送配置
// TODO 芋艿后续要实现下配送配置后续融合到 AppTradeConfigRespVO
@Schema(description = "用户 App - 配送配置 Response VO")
@Data
public class AppDeliveryConfigRespVO {

View File

@ -4,7 +4,6 @@ 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.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
@ -82,9 +81,10 @@ public class AppTradeOrderController {
public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
// 查询订单
TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
if (order == null) {
return success(null);
}
// TODO @puhui999这里建议改成如果为 null直接返回 success null主要查询操作尽量不要有非空的提示哈交给前端处理
// if (order == null) {
// return success(null, ORDER_NOT_FOUND.getMsg());
// }
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());

View File

@ -50,12 +50,18 @@ public class AppTradeOrderSettlementReqVO {
private Long seckillActivityId;
// ========== 拼团活动相关字段 ==========
// TODO @puhui999是不是拼团记录的编号哈
@Schema(description = "拼团活动编号", example = "1024")
private Long combinationActivityId;
@Schema(description = "拼团团长编号", example = "2048")
private Long combinationHeadId;
// ========== 砍价活动相关字段 ==========
// TODO @puhui999是不是砍价记录的编号哈
@Schema(description = "砍价活动编号", example = "123")
private Long bargainActivityId;
@Data
@Schema(description = "用户 App - 商品项")
@Valid

View File

@ -78,7 +78,7 @@ public interface TradeAfterSaleConvert {
// 处理订单信息
respVO.setOrder(convert(order));
// 处理售后日志
respVO.setAfterSaleLog(convertList1(logs));
respVO.setLogs(convertList1(logs));
return respVO;
}
List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.trade.convert.brokerage.record;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.util.List;
/**
* 佣金记录 Convert
*
* @author owen
*/
@Mapper
public interface BrokerageRecordConvert {
BrokerageRecordConvert INSTANCE = Mappers.getMapper(BrokerageRecordConvert.class);
BrokerageRecordRespVO convert(BrokerageRecordDO bean);
List<BrokerageRecordRespVO> convertList(List<BrokerageRecordDO> list);
PageResult<BrokerageRecordRespVO> convertPage(PageResult<BrokerageRecordDO> page);
// TODO @疯狂可能 title 不是很固化会存在类似沐晴成功购买XXX JVM 实战
default BrokerageRecordDO convert(BrokerageUserDO user, BrokerageRecordBizTypeEnum bizType, String bizId,
Integer brokerageFrozenDays, int brokeragePrice, LocalDateTime unfreezeTime,
String title) {
brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
// 不冻结时佣金直接就是结算状态
Integer status = brokerageFrozenDays > 0
? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
: BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
return new BrokerageRecordDO().setUserId(user.getId())
.setBizType(bizType.getType()).setBizId(bizId)
.setPrice(brokeragePrice).setTotalPrice(user.getBrokeragePrice())
.setTitle(title)
.setDescription(StrUtil.format(bizType.getDescription(), String.valueOf(brokeragePrice / 100.0)))
.setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime);
}
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.trade.convert.brokerage.user;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 分销用户 Convert
*
* @author owen
*/
@Mapper
public interface BrokerageUserConvert {
BrokerageUserConvert INSTANCE = Mappers.getMapper(BrokerageUserConvert.class);
BrokerageUserRespVO convert(BrokerageUserDO bean);
List<BrokerageUserRespVO> convertList(List<BrokerageUserDO> list);
PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> page);
default PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> pageResult,
Map<Long, MemberUserRespDTO> userMap,
Map<Long, Long> brokerageUserCountMap,
Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap) {
PageResult<BrokerageUserRespVO> result = convertPage(pageResult);
for (BrokerageUserRespVO vo : result.getList()) {
// 用户信息
Optional.ofNullable(userMap.get(vo.getId())).ifPresent(
user -> vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
// 推广用户数量一级
vo.setBrokerageUserCount(MapUtil.getInt(brokerageUserCountMap, vo.getId(), 0));
// 推广订单数量推广订单金额
Optional<UserBrokerageSummaryBO> orderSummaryOptional = Optional.ofNullable(userOrderSummaryMap.get(vo.getId()));
vo.setBrokerageOrderCount(orderSummaryOptional.map(UserBrokerageSummaryBO::getCount).orElse(0))
.setBrokerageOrderPrice(orderSummaryOptional.map(UserBrokerageSummaryBO::getPrice).orElse(0));
// todo 已提现次数已提现金额
vo.setWithdrawCount(0);
vo.setWithdrawPrice(0);
}
return result;
}
BrokerageUserDTO convertDTO(BrokerageUserDO brokerageUser);
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.convert.config;
import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 交易中心配置 Convert
*
* @author owen
*/
@Mapper
public interface TradeConfigConvert {
TradeConfigConvert INSTANCE = Mappers.getMapper(TradeConfigConvert.class);
TradeConfigDO convert(TradeConfigSaveReqVO bean);
TradeConfigRespVO convert(TradeConfigDO bean);
}

View File

@ -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.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
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.enums.DictTypeConstants;
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.sku.dto.ProductSkuRespDTO;
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.trade.api.order.dto.TradeOrderRespDTO;
@ -93,6 +95,7 @@ public interface TradeOrderConvert {
items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
return new ProductSkuUpdateStockReqDTO(items);
}
List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
@Mappings({
@ -151,9 +154,10 @@ public interface TradeOrderConvert {
TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
orderLog.setContent("订单操作" + i);
orderLog.setCreateTime(LocalDateTime.now());
orderLog.setUserType(i % 2 == 0 ? 2 : 1);
orderLogs.add(orderLog);
}
orderVO.setOrderLog(orderLogs);
orderVO.setLogs(orderLogs);
return orderVO;
}
@ -273,4 +277,10 @@ public interface TradeOrderConvert {
TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId()))
.setBasePrice(item.getPayPrice() * item.getCount())
.setFirstFixedPrice(sku.getSubCommissionFirstPrice())
.setSecondFixedPrice(sku.getSubCommissionSecondPrice());
}
}

View File

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

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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("trade_brokerage_user")
@KeySequence("trade_brokerage_user_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BrokerageUserDO extends BaseDO {
/**
* 用户编号
* <p>
* 对应 MemberUserDO id 字段
*/
@TableId
private Long id;
/**
* 推广员编号
* <p>
* 关联 MemberUserDO id 字段
*/
private Long bindUserId;
/**
* 推广员绑定时间
*/
private LocalDateTime bindUserTime;
/**
* 是否有分销资格
*/
private Boolean brokerageEnabled;
/**
* 成为分销员时间
*/
private LocalDateTime brokerageTime;
/**
* 可用佣金
*/
private Integer brokeragePrice;
/**
* 冻结佣金
*/
private Integer frozenPrice;
}

View File

@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.config;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import java.util.List;
/**
* 交易中心配置 DO
*
* @author owen
*/
@TableName(value = "trade_config", autoResultMap = true)
@KeySequence("trade_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeConfigDO extends BaseDO {
/**
* 自增主键
*/
@TableId
private Long id;
// ========== 分销相关 ==========
/**
* 是否启用分佣
*/
private Boolean brokerageEnabled;
/**
* 分佣模式
* <p>
* 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
*/
private Integer brokerageEnabledCondition;
/**
* 分销关系绑定模式
* <p>
* 枚举 {@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;
/**
* 提现方式
* <p>
* 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
*/
@TableField(typeHandler = IntegerListTypeHandler.class)
private List<Integer> brokerageWithdrawType;
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.trade.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.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
/**
* 佣金记录 Mapper
*
* @author owen
*/
@Mapper
public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
default PageResult<BrokerageRecordDO> selectPage(BrokerageRecordPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageRecordDO>()
.eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
.eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
.eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
.betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(BrokerageRecordDO::getId));
}
default List<BrokerageRecordDO> selectListByStatusAndUnfreezeTimeLt(Integer status, LocalDateTime unfreezeTime) {
return selectList(new LambdaQueryWrapper<BrokerageRecordDO>()
.eq(BrokerageRecordDO::getStatus, status)
.lt(BrokerageRecordDO::getUnfreezeTime, unfreezeTime));
}
default int updateByIdAndStatus(Integer id, Integer status, BrokerageRecordDO updateObj) {
return update(updateObj, new LambdaQueryWrapper<BrokerageRecordDO>()
.eq(BrokerageRecordDO::getId, id)
.eq(BrokerageRecordDO::getStatus, status));
}
default BrokerageRecordDO selectByBizTypeAndBizId(Integer bizType, String bizId) {
return selectOne(BrokerageRecordDO::getBizType, bizType,
BrokerageRecordDO::getBizId, bizId);
}
// TODO @疯狂mysql 关键字大写哈这样看起来清晰点例如说 SELECT COUNT(1)
@Select("select count(1), sum(price) from trade_brokerage_record where user_id = #{userId} and biz_type = #{bizType} and status = #{status}")
UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
@Param("bizType") Integer bizType,
@Param("status") Integer status);
}

View File

@ -0,0 +1,115 @@
package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 分销用户 Mapper
*
* @author owen
*/
@Mapper
public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageUserDO>()
.eqIfPresent(BrokerageUserDO::getBindUserId, reqVO.getBindUserId())
.eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
.betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(BrokerageUserDO::getId));
}
/**
* 更新用户可用佣金增加
*
* @param id 用户编号
* @param incrCount 增加佣金正数
*/
default void updatePriceIncr(Long id, Integer incrCount) {
Assert.isTrue(incrCount > 0);
LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
.setSql(" price = price + " + incrCount)
.eq(BrokerageUserDO::getId, id);
update(null, lambdaUpdateWrapper);
}
/**
* 更新用户可用佣金减少
* 注意理论上佣金可能已经提现这时会扣出负数确保平台不会造成损失
*
* @param id 用户编号
* @param incrCount 增加佣金负数
*/
default void updatePriceDecr(Long id, Integer incrCount) {
Assert.isTrue(incrCount < 0);
LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
.setSql(" price = price + " + incrCount) // 负数所以使用 +
.eq(BrokerageUserDO::getId, id);
update(null, lambdaUpdateWrapper);
}
/**
* 更新用户冻结佣金增加
*
* @param id 用户编号
* @param incrCount 增加冻结佣金正数
*/
default void updateFrozenPriceIncr(Long id, Integer incrCount) {
Assert.isTrue(incrCount > 0);
LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
.setSql(" frozen_price = frozen_price + " + incrCount)
.eq(BrokerageUserDO::getId, id);
update(null, lambdaUpdateWrapper);
}
/**
* 更新用户冻结佣金减少
* 注意理论上冻结佣金可能已经解冻这时会扣出负数确保平台不会造成损失
*
* @param id 用户编号
* @param incrCount 减少冻结佣金负数
*/
default void updateFrozenPriceDecr(Long id, Integer incrCount) {
Assert.isTrue(incrCount < 0);
LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
.setSql(" frozen_price = frozen_price + " + incrCount) // 负数所以使用 +
.eq(BrokerageUserDO::getId, id);
update(null, lambdaUpdateWrapper);
}
/**
* 更新用户冻结佣金减少, 更新用户佣金增加
*
* @param id 用户编号
* @param incrCount 减少冻结佣金负数
* @return 更新条数
*/
default int updateFrozenPriceDecrAndPriceIncr(Long id, Integer incrCount) {
Assert.isTrue(incrCount < 0);
LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
.setSql(" frozen_price = frozen_price + " + incrCount + // 负数所以使用 +
", price = price + " + -incrCount) // 负数所以使用 -
.eq(BrokerageUserDO::getId, id)
.ge(BrokerageUserDO::getFrozenPrice, -incrCount); // cas 逻辑
return update(null, lambdaUpdateWrapper);
}
default void updateBindUserIdAndBindUserTimeToNull(Long id) {
update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
.eq(BrokerageUserDO::getId, id)
.set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
}
default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
.eq(BrokerageUserDO::getId, id)
.set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
}
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.trade.dal.mysql.config;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 交易中心配置 Mapper
*
* @author owen
*/
@Mapper
public interface TradeConfigMapper extends BaseMapperX<TradeConfigDO> {
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.trade.dal.redis.no;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 订单序号的 Redis DAO
*
* @author HUIHUI
*/
@Repository
public class TradeOrderNoRedisDAO {
public static final String TRADE_ORDER_NO_PREFIX = "O";
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 生成序号
*
* @param prefix 前缀
* @return 序号
*/
public String generate(String prefix) {
String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
return noPrefix + no;
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.trade.job.brokerage;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 佣金解冻 Job
*
* @author owen
*/
@Component
@TenantJob
public class BrokerageRecordUnfreezeJob implements JobHandler {
@Resource
private BrokerageRecordService brokerageRecordService;
@Override
public String execute(String param) {
int count = brokerageRecordService.unfreezeRecord();
return StrUtil.format("解冻佣金 {} 个", count);
}
}

View File

@ -0,0 +1,4 @@
/**
* 占位文件无特殊用途
*/
package cn.iocoder.yudao.module.trade.job;

View File

@ -90,12 +90,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
public TradeAfterSaleDO getAfterSale(Long id) {
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
// TODO @puhui999读不到不要这里报错哈交给前端报错一般是读取信息不到message 提示然后 close tab
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
return afterSale;
return tradeAfterSaleMapper.selectById(id);
}
// TODO 芋艿拼团失败要不要发起售后的方式退款还是走取消逻辑

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.trade.service.brokerage.bo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 佣金 增加 Request BO
*
* @author owen
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BrokerageAddReqBO {
/**
* 业务编号
*/
@NotBlank(message = "业务编号不能为空")
private String bizId;
/**
* 佣金基数
*/
@NotNull(message = "佣金基数不能为空")
private Integer basePrice;
/**
* 一级佣金固定
*/
private Integer firstFixedPrice;
/**
* 二级佣金固定
*/
private Integer secondFixedPrice;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.trade.service.brokerage.bo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户佣金合计 BO
*
* @author owen
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserBrokerageSummaryBO {
/**
* 佣金数量
*/
private Integer count;
/**
* 佣金总额
*/
private Integer price;
}

Some files were not shown because too many files have changed in this diff Show More