f
This commit is contained in:
@@ -20,6 +20,10 @@ service main {
|
||||
@handler AdminGetAgentList
|
||||
get /list (AdminGetAgentListReq) returns (AdminGetAgentListResp)
|
||||
|
||||
// 代理团队树(同钻石团队 + 邀请关系)
|
||||
@handler AdminGetAgentTeamTree
|
||||
get /team/tree (AdminGetAgentTeamTreeReq) returns (AdminGetAgentTeamTreeResp)
|
||||
|
||||
// 代理审核
|
||||
@handler AdminAuditAgent
|
||||
post /audit (AdminAuditAgentReq) returns (AdminAuditAgentResp)
|
||||
@@ -32,6 +36,10 @@ service main {
|
||||
@handler AdminGetAgentOrderList
|
||||
get /order/list (AdminGetAgentOrderListReq) returns (AdminGetAgentOrderListResp)
|
||||
|
||||
// 单笔订单分账视图:同一响应返回佣金 + 返佣(避免前端双请求重复走鉴权)
|
||||
@handler AdminGetAgentOrderSettlement
|
||||
get /order/settlement (AdminGetAgentOrderSettlementReq) returns (AdminGetAgentOrderSettlementResp)
|
||||
|
||||
// 代理佣金分页查询
|
||||
@handler AdminGetAgentCommissionList
|
||||
get /commission/list (AdminGetAgentCommissionListReq) returns (AdminGetAgentCommissionListResp)
|
||||
@@ -94,9 +102,12 @@ type (
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
Mobile *string `form:"mobile,optional"` // 手机号(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编码(可选)
|
||||
Region *string `form:"region,optional"` // 区域(可选)
|
||||
Level *int64 `form:"level,optional"` // 等级(可选)
|
||||
TeamLeaderId *string `form:"team_leader_id,optional"` // 团队首领ID(可选)
|
||||
AgentId *string `form:"agent_id,optional"` // 代理主键UUID,精确查询;传入时不再按 team_leader 与其它条件组合筛选
|
||||
RealName *string `form:"real_name,optional"` // 实名姓名(可选,模糊匹配已三要素核验记录)
|
||||
}
|
||||
AgentListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
@@ -108,7 +119,8 @@ type (
|
||||
RealName string `json:"real_name"` // 姓名(来自实名表)
|
||||
IdCard string `json:"id_card"` // 身份证(解密后明文返回)
|
||||
WechatId string `json:"wechat_id"` // 微信号
|
||||
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
||||
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
||||
SubordinateCount int64 `json:"subordinate_count"` // 直属下级代理数(team_leader_id 为本代理 id)
|
||||
AgentCode int64 `json:"agent_code"`
|
||||
Balance float64 `json:"balance"` // 钱包余额
|
||||
TotalEarnings float64 `json:"total_earnings"` // 累计收益
|
||||
@@ -121,6 +133,21 @@ type (
|
||||
Total int64 `json:"total"` // 总数
|
||||
Items []AgentListItem `json:"items"` // 列表数据
|
||||
}
|
||||
AdminGetAgentTeamTreeReq {
|
||||
AgentId string `form:"agent_id"` // 锚点代理ID(UUID)
|
||||
}
|
||||
AgentTeamTreeNode {
|
||||
Id string `json:"id"`
|
||||
AgentCode int64 `json:"agent_code"`
|
||||
Level int64 `json:"level"`
|
||||
LevelName string `json:"level_name"`
|
||||
Mobile string `json:"mobile"`
|
||||
IsAnchor bool `json:"is_anchor"`
|
||||
Children []AgentTeamTreeNode `json:"children"`
|
||||
}
|
||||
AdminGetAgentTeamTreeResp {
|
||||
Root AgentTeamTreeNode `json:"root"`
|
||||
}
|
||||
// 代理审核
|
||||
AdminAuditAgentReq {
|
||||
AuditId int64 `json:"audit_id"` // 审核记录ID
|
||||
@@ -135,12 +162,14 @@ type (
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
ProductId *string `form:"product_id,optional"` // 产品ID(可选)
|
||||
LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选)
|
||||
}
|
||||
AgentLinkListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
SetPrice float64 `json:"set_price"` // 设定价格
|
||||
@@ -157,14 +186,18 @@ type (
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选)
|
||||
OrderStatus *string `form:"order_status,optional"` // 订单状态(可选):pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||
}
|
||||
AgentOrderListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
OrderAmount float64 `json:"order_amount"` // 订单金额
|
||||
@@ -180,18 +213,28 @@ type (
|
||||
Total int64 `json:"total"` // 总数
|
||||
Items []AgentOrderListItem `json:"items"` // 列表数据
|
||||
}
|
||||
AdminGetAgentOrderSettlementReq {
|
||||
OrderNo string `form:"order_no"` // 商户订单号
|
||||
}
|
||||
AdminGetAgentOrderSettlementResp {
|
||||
Commissions []AgentCommissionListItem `json:"commissions"` // 本单销售佣金
|
||||
Rebates []AgentRebateListItem `json:"rebates"` // 本单返佣
|
||||
}
|
||||
// 代理佣金分页查询
|
||||
AdminGetAgentCommissionListReq {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
}
|
||||
AgentCommissionListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
Status int64 `json:"status"` // 状态
|
||||
@@ -203,18 +246,23 @@ type (
|
||||
}
|
||||
// 代理返佣分页查询
|
||||
AdminGetAgentRebateListReq {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
SourceAgentId *string `form:"source_agent_id,optional"` // 来源代理ID(可选)
|
||||
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
SourceAgentCode *string `form:"source_agent_code,optional"` // 来源代理编号(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
||||
}
|
||||
AgentRebateListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
||||
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 获得返佣代理编号
|
||||
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
||||
SourceAgentCode int64 `json:"source_agent_code"` // 来源代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
RebateType int64 `json:"rebate_type"` // 返佣类型
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||
@@ -229,12 +277,14 @@ type (
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
UpgradeType *int64 `form:"upgrade_type,optional"` // 升级类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
}
|
||||
AgentUpgradeListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
FromLevel int64 `json:"from_level"` // 原等级
|
||||
ToLevel int64 `json:"to_level"` // 目标等级
|
||||
UpgradeType int64 `json:"upgrade_type"` // 升级类型
|
||||
@@ -249,15 +299,20 @@ type (
|
||||
}
|
||||
// 代理提现分页查询
|
||||
AdminGetAgentWithdrawalListReq {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理主键 UUID(弹窗内按代理筛选时传入)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选,模糊匹配)
|
||||
Status string `form:"status,optional"` // 状态(可选):1=待审核,2=已通过,3=已拒绝,5=已打款
|
||||
WithdrawalType string `form:"withdrawal_type,optional"` // 提现方式(可选):1=支付宝,2=银行卡
|
||||
PayeeAccount *string `form:"payee_account,optional"` // 收款账户(可选,模糊)
|
||||
PayeeName *string `form:"payee_name,optional"` // 收款人(可选,模糊)
|
||||
}
|
||||
AgentWithdrawalListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
||||
@@ -294,6 +349,7 @@ type (
|
||||
AgentRealNameListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
Name string `json:"name"` // 姓名
|
||||
IdCard string `json:"id_card"` // 身份证号
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
@@ -412,20 +468,21 @@ type (
|
||||
AdminGetInviteCodeListReq {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
Code *string `form:"code,optional"` // 邀请码(可选)
|
||||
AgentId *string `form:"agent_id,optional"` // 发放代理ID(可选,NULL表示平台发放)
|
||||
Code *string `form:"code,optional"` // 邀请码(可选,模糊匹配)
|
||||
TargetLevel *int64 `form:"target_level,optional"` // 目标等级(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
Status string `form:"status,optional"` // 状态(可选):0=未使用,1=已使用,2=已失效
|
||||
}
|
||||
InviteCodeListItem {
|
||||
Id string `json:"id"` // 主键
|
||||
Code string `json:"code"` // 邀请码
|
||||
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
||||
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
||||
TargetLevel int64 `json:"target_level"` // 目标等级
|
||||
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
||||
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
||||
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
||||
Id string `json:"id"` // 主键
|
||||
Code string `json:"code"` // 邀请码
|
||||
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
||||
AgentCode int64 `json:"agent_code"` // 发放代理编号(平台为0)
|
||||
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
||||
TargetLevel int64 `json:"target_level"` // 目标等级
|
||||
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
||||
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
||||
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
||||
UsedAgentCode int64 `json:"used_agent_code"` // 使用代理编号
|
||||
UsedTime string `json:"used_time"` // 使用时间
|
||||
ExpireTime string `json:"expire_time"` // 过期时间
|
||||
Remark string `json:"remark"` // 备注
|
||||
|
||||
@@ -49,7 +49,6 @@ type (
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes,optional"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
}
|
||||
|
||||
@@ -65,7 +64,6 @@ type (
|
||||
ProductEn *string `json:"product_en,optional"` // 英文名
|
||||
Description *string `json:"description,optional"` // 描述
|
||||
Notes *string `json:"notes,optional"` // 备注
|
||||
CostPrice *float64 `json:"cost_price,optional"` // 成本
|
||||
SellPrice *float64 `json:"sell_price,optional"` // 售价
|
||||
}
|
||||
|
||||
@@ -99,7 +97,7 @@ type (
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
@@ -123,7 +121,7 @@ type (
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
@@ -141,6 +139,7 @@ type (
|
||||
FeatureId string `json:"feature_id"` // 功能ID
|
||||
ApiId string `json:"api_id"` // API标识
|
||||
Name string `json:"name"` // 功能描述
|
||||
CostPrice float64 `json:"cost_price"` // 模块成本价(元,来自 feature)
|
||||
Sort int64 `json:"sort"` // 排序
|
||||
Enable int64 `json:"enable"` // 是否启用
|
||||
IsImportant int64 `json:"is_important"` // 是否重要
|
||||
|
||||
@@ -53,6 +53,8 @@ type (
|
||||
PaymentScene string `form:"payment_scene,optional"` // 支付平台
|
||||
Amount float64 `form:"amount,optional"` // 金额
|
||||
Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||
IsAgentOrder *bool `form:"is_agent_order,optional"` // 是否代理订单
|
||||
AgentCode string `form:"agent_code,optional"` // 代理编号
|
||||
CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始
|
||||
CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束
|
||||
PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始
|
||||
@@ -81,6 +83,7 @@ type (
|
||||
RefundTime string `json:"refund_time"` // 退款时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
|
||||
AgentCode string `json:"agent_code"` // 代理编号
|
||||
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理
|
||||
}
|
||||
// 详情请求
|
||||
|
||||
@@ -39,6 +39,7 @@ type (
|
||||
AdminGetPlatformUserListReq {
|
||||
Page int64 `form:"page,default=1"` // 页码
|
||||
PageSize int64 `form:"pageSize,default=20"` // 每页数量
|
||||
UserId string `form:"user_id,optional"` // 平台用户主键 ID,精确筛选
|
||||
Mobile string `form:"mobile,optional"` // 手机号
|
||||
Nickname string `form:"nickname,optional"` // 昵称
|
||||
Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否
|
||||
|
||||
@@ -312,9 +312,9 @@ type (
|
||||
}
|
||||
// 生成推广链接
|
||||
AgentGeneratingLinkReq {
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
SetPrice float64 `json:"set_price"` // 设定价格
|
||||
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
SetPrice string `json:"set_price"` // 设定价格
|
||||
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
||||
}
|
||||
AgentGeneratingLinkResp {
|
||||
LinkIdentifier string `json:"link_identifier"` // 推广链接标识
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package admin_agent
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"qnc-server/app/main/api/internal/logic/admin_agent"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/result"
|
||||
"qnc-server/pkg/lzkit/validator"
|
||||
)
|
||||
|
||||
func AdminGetAgentOrderSettlementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.AdminGetAgentOrderSettlementReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := admin_agent.NewAdminGetAgentOrderSettlementLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AdminGetAgentOrderSettlement(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package admin_agent
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"qnc-server/app/main/api/internal/logic/admin_agent"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/result"
|
||||
"qnc-server/pkg/lzkit/validator"
|
||||
)
|
||||
|
||||
func AdminGetAgentTeamTreeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.AdminGetAgentTeamTreeReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := admin_agent.NewAdminGetAgentTeamTreeLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AdminGetAgentTeamTree(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/order/list",
|
||||
Handler: admin_agent.AdminGetAgentOrderListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/order/settlement",
|
||||
Handler: admin_agent.AdminGetAgentOrderSettlementHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/product_config/list",
|
||||
@@ -103,6 +108,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/rebate/list",
|
||||
Handler: admin_agent.AdminGetAgentRebateListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/team/tree",
|
||||
Handler: admin_agent.AdminGetAgentTeamTreeHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/upgrade/agent",
|
||||
|
||||
@@ -2,6 +2,7 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
@@ -27,20 +28,46 @@ func NewAdminGetAgentCommissionListLogic(ctx context.Context, svcCtx *svc.Servic
|
||||
|
||||
func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *types.AdminGetAgentCommissionListReq) (resp *types.AdminGetAgentCommissionListResp, err error) {
|
||||
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
|
||||
|
||||
// 按 agent_code 筛选:先查出 agent_id
|
||||
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||
if parseErr == nil {
|
||||
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||
if findErr == nil && agent != nil {
|
||||
builder = builder.Where(squirrel.Eq{"agent_id": agent.Id})
|
||||
} else {
|
||||
// 找不到对应代理,返回空列表
|
||||
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||
}
|
||||
} else {
|
||||
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 按 order_no 筛选:先查出 order_id
|
||||
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||
if findErr == nil && order != nil {
|
||||
builder = builder.Where(squirrel.Eq{"order_id": order.Id})
|
||||
} else {
|
||||
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.AgentId != nil {
|
||||
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
|
||||
}
|
||||
if req.Status != nil {
|
||||
builder = builder.Where(squirrel.Eq{"status": *req.Status})
|
||||
}
|
||||
// 产品名称筛选功能已移除,如需可按product_id筛选
|
||||
|
||||
list, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 批量查product_name
|
||||
// 批量查 product_name
|
||||
productIds := make(map[string]struct{})
|
||||
for _, v := range list {
|
||||
productIds[v.ProductId] = struct{}{}
|
||||
@@ -58,11 +85,38 @@ func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *type
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查 agent_code
|
||||
agentIds := make([]string, 0, len(list))
|
||||
for _, v := range list {
|
||||
if v.AgentId != "" {
|
||||
agentIds = append(agentIds, v.AgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
// 批量查 order_no
|
||||
orderIds := make([]string, 0, len(list))
|
||||
for _, v := range list {
|
||||
if v.OrderId != "" {
|
||||
orderIds = append(orderIds, v.OrderId)
|
||||
}
|
||||
}
|
||||
orderNoMap := make(map[string]string)
|
||||
if len(orderIds) > 0 {
|
||||
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": orderIds})
|
||||
orders, _ := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
|
||||
for _, o := range orders {
|
||||
orderNoMap[o.Id] = o.OrderNo
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]types.AgentCommissionListItem, 0, len(list))
|
||||
for _, v := range list {
|
||||
item := types.AgentCommissionListItem{}
|
||||
_ = copier.Copy(&item, v)
|
||||
item.AgentCode = agentCodeMap[v.AgentId]
|
||||
item.ProductName = productNameMap[v.ProductId]
|
||||
item.OrderNo = orderNoMap[v.OrderId]
|
||||
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/globalkey"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -29,6 +32,16 @@ func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAg
|
||||
if req.AgentId != nil {
|
||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||
}
|
||||
if req.AgentCode != nil && strings.TrimSpace(*req.AgentCode) != "" {
|
||||
var agentCode int64
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(*req.AgentCode), "%d", &agentCode); err == nil {
|
||||
builder = builder.Where(
|
||||
"agent_id IN (SELECT id FROM agent WHERE agent_code = ? AND del_state = ?)",
|
||||
agentCode,
|
||||
globalkey.DelStateNo,
|
||||
)
|
||||
}
|
||||
}
|
||||
if req.LinkIdentifier != nil && *req.LinkIdentifier != "" {
|
||||
builder = builder.Where("link_identifier = ?", *req.LinkIdentifier)
|
||||
}
|
||||
@@ -60,11 +73,20 @@ func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAg
|
||||
}
|
||||
}
|
||||
|
||||
agentIds := make([]string, 0, len(links))
|
||||
for _, link := range links {
|
||||
if link.AgentId != "" {
|
||||
agentIds = append(agentIds, link.AgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
items := make([]types.AgentLinkListItem, 0, len(links))
|
||||
for _, link := range links {
|
||||
items = append(items, types.AgentLinkListItem{
|
||||
Id: link.Id,
|
||||
AgentId: link.AgentId,
|
||||
AgentCode: agentCodeMap[link.AgentId],
|
||||
ProductId: link.ProductId,
|
||||
ProductName: productNameMap[link.ProductId],
|
||||
SetPrice: link.SetPrice,
|
||||
|
||||
@@ -3,9 +3,12 @@ package admin_agent
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
"qnc-server/pkg/lzkit/crypto"
|
||||
|
||||
@@ -31,26 +34,52 @@ func NewAdminGetAgentListLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
||||
func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListReq) (resp *types.AdminGetAgentListResp, err error) {
|
||||
builder := l.svcCtx.AgentModel.SelectBuilder()
|
||||
|
||||
// 如果传入TeamLeaderId,则查找该团队首领下的所有代理
|
||||
if req.TeamLeaderId != nil {
|
||||
builder = builder.Where(squirrel.Eq{"team_leader_id": *req.TeamLeaderId})
|
||||
}
|
||||
if req.Level != nil {
|
||||
builder = builder.Where(squirrel.Eq{"level": *req.Level})
|
||||
}
|
||||
if req.Mobile != nil && *req.Mobile != "" {
|
||||
// 加密手机号进行查询
|
||||
encryptedMobile, err := crypto.EncryptMobile(*req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||
exactByAgentId := req.AgentId != nil && *req.AgentId != ""
|
||||
if exactByAgentId {
|
||||
builder = builder.Where(squirrel.Eq{"id": *req.AgentId})
|
||||
} else {
|
||||
// 如果传入TeamLeaderId,则查找该团队首领下的直属代理(不含首领本人:钻石等业务会把 team_leader_id 写成自己)
|
||||
if req.TeamLeaderId != nil && *req.TeamLeaderId != "" {
|
||||
tl := *req.TeamLeaderId
|
||||
builder = builder.Where(squirrel.Eq{"team_leader_id": tl})
|
||||
builder = builder.Where(squirrel.NotEq{"id": tl})
|
||||
}
|
||||
if req.Level != nil {
|
||||
builder = builder.Where(squirrel.Eq{"level": *req.Level})
|
||||
}
|
||||
if req.Mobile != nil && *req.Mobile != "" {
|
||||
// 加密手机号进行查询
|
||||
encryptedMobile, err := crypto.EncryptMobile(*req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||
}
|
||||
builder = builder.Where(squirrel.Eq{"mobile": encryptedMobile})
|
||||
}
|
||||
if req.Region != nil && *req.Region != "" {
|
||||
// 注意:region字段现在是可选的,查询时需要处理NULL值
|
||||
// 如果region字段是NULL,使用IS NULL查询;否则使用等值查询
|
||||
// 这里简化处理,直接使用等值查询(如果数据库中有NULL值,需要特殊处理)
|
||||
builder = builder.Where(squirrel.Eq{"region": *req.Region})
|
||||
}
|
||||
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||
// 将字符串转换为int64
|
||||
var agentCode int64
|
||||
if _, err := fmt.Sscanf(*req.AgentCode, "%d", &agentCode); err == nil {
|
||||
builder = builder.Where(squirrel.Eq{"agent_code": agentCode})
|
||||
}
|
||||
}
|
||||
builder = builder.Where(squirrel.Eq{"mobile": encryptedMobile})
|
||||
}
|
||||
if req.Region != nil && *req.Region != "" {
|
||||
// 注意:region字段现在是可选的,查询时需要处理NULL值
|
||||
// 如果region字段是NULL,使用IS NULL查询;否则使用等值查询
|
||||
// 这里简化处理,直接使用等值查询(如果数据库中有NULL值,需要特殊处理)
|
||||
builder = builder.Where(squirrel.Eq{"region": *req.Region})
|
||||
|
||||
if req.RealName != nil && strings.TrimSpace(*req.RealName) != "" {
|
||||
name := strings.TrimSpace(*req.RealName)
|
||||
likePattern := "%" + name + "%"
|
||||
builder = builder.Where(`EXISTS (
|
||||
SELECT 1 FROM agent_real_name arn
|
||||
WHERE arn.agent_id = agent.id
|
||||
AND arn.del_state = ?
|
||||
AND arn.verify_time IS NOT NULL
|
||||
AND arn.name LIKE ?
|
||||
)`, globalkey.DelStateNo, likePattern)
|
||||
}
|
||||
|
||||
agents, total, err := l.svcCtx.AgentModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
|
||||
@@ -58,6 +87,17 @@ func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
leaderIds := make([]string, 0, len(agents))
|
||||
for _, a := range agents {
|
||||
if a.Id != "" {
|
||||
leaderIds = append(leaderIds, a.Id)
|
||||
}
|
||||
}
|
||||
subCountByLeader, err := l.svcCtx.AgentModel.CountSubordinatesByTeamLeaderIds(l.ctx, leaderIds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "统计直属下级数量失败: %v", err)
|
||||
}
|
||||
|
||||
items := make([]types.AgentListItem, 0, len(agents))
|
||||
|
||||
for _, agent := range agents {
|
||||
@@ -117,17 +157,18 @@ func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListR
|
||||
}
|
||||
|
||||
item := types.AgentListItem{
|
||||
Id: agent.Id,
|
||||
UserId: agent.UserId,
|
||||
Level: agent.Level,
|
||||
LevelName: levelName,
|
||||
Region: region,
|
||||
Mobile: agent.Mobile,
|
||||
RealName: realName,
|
||||
IdCard: idCardPlain,
|
||||
WechatId: wechatId,
|
||||
TeamLeaderId: teamLeaderId,
|
||||
AgentCode: agent.AgentCode,
|
||||
Id: agent.Id,
|
||||
UserId: agent.UserId,
|
||||
Level: agent.Level,
|
||||
LevelName: levelName,
|
||||
Region: region,
|
||||
Mobile: agent.Mobile,
|
||||
RealName: realName,
|
||||
IdCard: idCardPlain,
|
||||
WechatId: wechatId,
|
||||
TeamLeaderId: teamLeaderId,
|
||||
SubordinateCount: subCountByLeader[agent.Id],
|
||||
AgentCode: agent.AgentCode,
|
||||
Balance: 0,
|
||||
TotalEarnings: 0,
|
||||
FrozenBalance: 0,
|
||||
|
||||
@@ -2,6 +2,8 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
@@ -28,21 +30,57 @@ func NewAdminGetAgentOrderListLogic(ctx context.Context, svcCtx *svc.ServiceCont
|
||||
}
|
||||
}
|
||||
|
||||
func emptyAgentOrderListResp() *types.AdminGetAgentOrderListResp {
|
||||
return &types.AdminGetAgentOrderListResp{
|
||||
Total: 0,
|
||||
Items: []types.AgentOrderListItem{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) {
|
||||
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
|
||||
Where("del_state = ?", globalkey.DelStateNo)
|
||||
|
||||
if req.AgentId != nil {
|
||||
if req.AgentId != nil && *req.AgentId != "" {
|
||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||
}
|
||||
if req.OrderId != nil {
|
||||
|
||||
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||
code, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||
if parseErr != nil {
|
||||
return emptyAgentOrderListResp(), nil
|
||||
}
|
||||
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, code)
|
||||
if findErr != nil || agent == nil {
|
||||
return emptyAgentOrderListResp(), nil
|
||||
}
|
||||
builder = builder.Where("agent_id = ?", agent.Id)
|
||||
}
|
||||
|
||||
if req.OrderId != nil && *req.OrderId != "" {
|
||||
builder = builder.Where("order_id = ?", *req.OrderId)
|
||||
}
|
||||
|
||||
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||
o, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||
if findErr != nil || o == nil {
|
||||
return emptyAgentOrderListResp(), nil
|
||||
}
|
||||
builder = builder.Where("order_id = ?", o.Id)
|
||||
}
|
||||
|
||||
if req.ProcessStatus != nil {
|
||||
builder = builder.Where("process_status = ?", *req.ProcessStatus)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
if req.OrderStatus != nil && *req.OrderStatus != "" {
|
||||
builder = builder.Where(squirrel.Expr(
|
||||
"order_id IN (SELECT id FROM `order` WHERE status = ? AND del_state = ?)",
|
||||
*req.OrderStatus,
|
||||
globalkey.DelStateNo,
|
||||
))
|
||||
}
|
||||
|
||||
page := req.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
@@ -57,7 +95,6 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理订单列表失败, %v", err)
|
||||
}
|
||||
|
||||
// 批量查询产品名称
|
||||
productIdSet := make(map[string]struct{})
|
||||
for _, order := range orders {
|
||||
productIdSet[order.ProductId] = struct{}{}
|
||||
@@ -74,13 +111,50 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
||||
}
|
||||
}
|
||||
|
||||
// 组装响应
|
||||
agentIds := make([]string, 0, len(orders))
|
||||
orderIds := make([]string, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
if order.AgentId != "" {
|
||||
agentIds = append(agentIds, order.AgentId)
|
||||
}
|
||||
if order.OrderId != "" {
|
||||
orderIds = append(orderIds, order.OrderId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
orderNoMap := make(map[string]string)
|
||||
orderStatusMap := make(map[string]string)
|
||||
if len(orderIds) > 0 {
|
||||
uniq := make([]string, 0, len(orderIds))
|
||||
seen := make(map[string]struct{}, len(orderIds))
|
||||
for _, id := range orderIds {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
uniq = append(uniq, id)
|
||||
}
|
||||
if len(uniq) > 0 {
|
||||
os, _ := l.svcCtx.OrderModel.FindAll(l.ctx, l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": uniq}), "")
|
||||
for _, o := range os {
|
||||
orderNoMap[o.Id] = o.OrderNo
|
||||
orderStatusMap[o.Id] = o.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]types.AgentOrderListItem, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
items = append(items, types.AgentOrderListItem{
|
||||
Id: order.Id,
|
||||
AgentId: order.AgentId,
|
||||
AgentCode: agentCodeMap[order.AgentId],
|
||||
OrderId: order.OrderId,
|
||||
OrderNo: orderNoMap[order.OrderId],
|
||||
ProductId: order.ProductId,
|
||||
ProductName: productNameMap[order.ProductId],
|
||||
OrderAmount: order.OrderAmount,
|
||||
@@ -89,6 +163,7 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
||||
PriceCost: order.PriceCost,
|
||||
AgentProfit: order.AgentProfit,
|
||||
ProcessStatus: order.ProcessStatus,
|
||||
OrderStatus: orderStatusMap[order.OrderId],
|
||||
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AdminGetAgentOrderSettlementLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAdminGetAgentOrderSettlementLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentOrderSettlementLogic {
|
||||
return &AdminGetAgentOrderSettlementLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AdminGetAgentOrderSettlementLogic) AdminGetAgentOrderSettlement(req *types.AdminGetAgentOrderSettlementReq) (resp *types.AdminGetAgentOrderSettlementResp, err error) {
|
||||
empty := &types.AdminGetAgentOrderSettlementResp{
|
||||
Commissions: []types.AgentCommissionListItem{},
|
||||
Rebates: []types.AgentRebateListItem{},
|
||||
}
|
||||
if req.OrderNo == "" {
|
||||
return empty, nil
|
||||
}
|
||||
on := req.OrderNo
|
||||
page := int64(1)
|
||||
pageSize := int64(200)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var cResp *types.AdminGetAgentCommissionListResp
|
||||
var cErr error
|
||||
var rResp *types.AdminGetAgentRebateListResp
|
||||
var rErr error
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
resp, e := NewAdminGetAgentCommissionListLogic(l.ctx, l.svcCtx).AdminGetAgentCommissionList(
|
||||
&types.AdminGetAgentCommissionListReq{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
OrderNo: &on,
|
||||
},
|
||||
)
|
||||
mu.Lock()
|
||||
cResp, cErr = resp, e
|
||||
mu.Unlock()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
resp, e := NewAdminGetAgentRebateListLogic(l.ctx, l.svcCtx).AdminGetAgentRebateList(
|
||||
&types.AdminGetAgentRebateListReq{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
OrderNo: &on,
|
||||
},
|
||||
)
|
||||
mu.Lock()
|
||||
rResp, rErr = resp, e
|
||||
mu.Unlock()
|
||||
}()
|
||||
wg.Wait()
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
if rErr != nil {
|
||||
return nil, rErr
|
||||
}
|
||||
if cResp == nil {
|
||||
cResp = &types.AdminGetAgentCommissionListResp{Items: []types.AgentCommissionListItem{}}
|
||||
}
|
||||
if rResp == nil {
|
||||
rResp = &types.AdminGetAgentRebateListResp{Items: []types.AgentRebateListItem{}}
|
||||
}
|
||||
commissions := cResp.Items
|
||||
if commissions == nil {
|
||||
commissions = []types.AgentCommissionListItem{}
|
||||
}
|
||||
rebates := rResp.Items
|
||||
if rebates == nil {
|
||||
rebates = []types.AgentRebateListItem{}
|
||||
}
|
||||
return &types.AdminGetAgentOrderSettlementResp{
|
||||
Commissions: commissions,
|
||||
Rebates: rebates,
|
||||
}, nil
|
||||
}
|
||||
@@ -60,6 +60,14 @@ func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.Ad
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询实名认证列表失败, %v", err)
|
||||
}
|
||||
|
||||
agentIds := make([]string, 0, len(realNames))
|
||||
for _, realName := range realNames {
|
||||
if realName.AgentId != "" {
|
||||
agentIds = append(agentIds, realName.AgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
// 组装响应
|
||||
items := make([]types.AgentRealNameListItem, 0, len(realNames))
|
||||
for _, realName := range realNames {
|
||||
@@ -100,6 +108,7 @@ func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.Ad
|
||||
item := types.AgentRealNameListItem{
|
||||
Id: realName.Id,
|
||||
AgentId: realName.AgentId,
|
||||
AgentCode: agentCodeMap[realName.AgentId],
|
||||
Name: realName.Name,
|
||||
IdCard: idCard,
|
||||
Mobile: mobile,
|
||||
|
||||
@@ -2,6 +2,8 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
@@ -35,9 +37,47 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG
|
||||
if req.AgentId != nil {
|
||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||
}
|
||||
if req.SourceAgentId != nil {
|
||||
builder = builder.Where("source_agent_id = ?", *req.SourceAgentId)
|
||||
|
||||
// 按 agent_code 筛选:先查出 agent_id
|
||||
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||
if parseErr == nil {
|
||||
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||
if findErr == nil && agent != nil {
|
||||
builder = builder.Where("agent_id = ?", agent.Id)
|
||||
} else {
|
||||
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||
}
|
||||
} else {
|
||||
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 按 source_agent_code 筛选:先查出 source_agent_id
|
||||
if req.SourceAgentCode != nil && *req.SourceAgentCode != "" {
|
||||
sourceAgentCodeInt, parseErr := strconv.ParseInt(*req.SourceAgentCode, 10, 64)
|
||||
if parseErr == nil {
|
||||
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, sourceAgentCodeInt)
|
||||
if findErr == nil && agent != nil {
|
||||
builder = builder.Where("source_agent_id = ?", agent.Id)
|
||||
} else {
|
||||
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||
}
|
||||
} else {
|
||||
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 按 order_no 筛选:先查出 order_id
|
||||
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||
if findErr == nil && order != nil {
|
||||
builder = builder.Where("order_id = ?", order.Id)
|
||||
} else {
|
||||
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.RebateType != nil {
|
||||
builder = builder.Where("rebate_type = ?", *req.RebateType)
|
||||
}
|
||||
@@ -74,17 +114,48 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG
|
||||
}
|
||||
}
|
||||
|
||||
agentIds := make([]string, 0, len(rebates)*2)
|
||||
for _, rebate := range rebates {
|
||||
if rebate.AgentId != "" {
|
||||
agentIds = append(agentIds, rebate.AgentId)
|
||||
}
|
||||
if rebate.SourceAgentId != "" {
|
||||
agentIds = append(agentIds, rebate.SourceAgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
// 批量查 order_no
|
||||
orderIds := make([]string, 0, len(rebates))
|
||||
for _, rebate := range rebates {
|
||||
if rebate.OrderId != "" {
|
||||
orderIds = append(orderIds, rebate.OrderId)
|
||||
}
|
||||
}
|
||||
orderNoMap := make(map[string]string)
|
||||
if len(orderIds) > 0 {
|
||||
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": orderIds})
|
||||
orders, _ := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
|
||||
for _, o := range orders {
|
||||
orderNoMap[o.Id] = o.OrderNo
|
||||
}
|
||||
}
|
||||
|
||||
// 组装响应
|
||||
items := make([]types.AgentRebateListItem, 0, len(rebates))
|
||||
for _, rebate := range rebates {
|
||||
items = append(items, types.AgentRebateListItem{
|
||||
Id: rebate.Id,
|
||||
AgentId: rebate.AgentId,
|
||||
SourceAgentId: rebate.SourceAgentId,
|
||||
OrderId: rebate.OrderId,
|
||||
RebateType: rebate.RebateType,
|
||||
Amount: rebate.RebateAmount,
|
||||
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
Id: rebate.Id,
|
||||
AgentId: rebate.AgentId,
|
||||
AgentCode: agentCodeMap[rebate.AgentId],
|
||||
SourceAgentId: rebate.SourceAgentId,
|
||||
SourceAgentCode: agentCodeMap[rebate.SourceAgentId],
|
||||
OrderId: rebate.OrderId,
|
||||
OrderNo: orderNoMap[rebate.OrderId],
|
||||
RebateType: rebate.RebateType,
|
||||
Amount: rebate.RebateAmount,
|
||||
Status: rebate.Status,
|
||||
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
"qnc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AdminGetAgentTeamTreeLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAdminGetAgentTeamTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentTeamTreeLogic {
|
||||
return &AdminGetAgentTeamTreeLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func resolveDiamondTeamRootId(agent *model.Agent) string {
|
||||
if agent.TeamLeaderId.Valid && agent.TeamLeaderId.String != "" && agent.TeamLeaderId.String != agent.Id {
|
||||
return agent.TeamLeaderId.String
|
||||
}
|
||||
return agent.Id
|
||||
}
|
||||
|
||||
func levelName(lv int64) string {
|
||||
switch lv {
|
||||
case 1:
|
||||
return "普通"
|
||||
case 2:
|
||||
return "黄金"
|
||||
case 3:
|
||||
return "钻石"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AdminGetAgentTeamTreeLogic) AdminGetAgentTeamTree(req *types.AdminGetAgentTeamTreeReq) (resp *types.AdminGetAgentTeamTreeResp, err error) {
|
||||
if req.AgentId == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("agent_id 不能为空"), "")
|
||||
}
|
||||
|
||||
anchor, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.AgentId)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("代理不存在"), "")
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理失败: %v", err)
|
||||
}
|
||||
|
||||
teamRootId := resolveDiamondTeamRootId(anchor)
|
||||
|
||||
builder := l.svcCtx.AgentModel.SelectBuilder().
|
||||
Where("del_state = ?", globalkey.DelStateNo).
|
||||
Where(squirrel.Or{
|
||||
squirrel.Eq{"id": teamRootId},
|
||||
squirrel.And{
|
||||
squirrel.Eq{"team_leader_id": teamRootId},
|
||||
squirrel.NotEq{"id": teamRootId},
|
||||
},
|
||||
})
|
||||
|
||||
members, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询团队成员失败: %v", err)
|
||||
}
|
||||
|
||||
agentById := make(map[string]*model.Agent, len(members)+1)
|
||||
memberIDs := make([]string, 0, len(members)+1)
|
||||
anchorInTeam := false
|
||||
for _, m := range members {
|
||||
agentById[m.Id] = m
|
||||
memberIDs = append(memberIDs, m.Id)
|
||||
if m.Id == anchor.Id {
|
||||
anchorInTeam = true
|
||||
}
|
||||
}
|
||||
if !anchorInTeam {
|
||||
agentById[anchor.Id] = anchor
|
||||
memberIDs = append(memberIDs, anchor.Id)
|
||||
members = append(members, anchor)
|
||||
}
|
||||
|
||||
relBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
|
||||
Where("relation_type = ? AND del_state = ?", 1, globalkey.DelStateNo).
|
||||
Where(squirrel.Eq{"parent_id": memberIDs}).
|
||||
Where(squirrel.Eq{"child_id": memberIDs})
|
||||
|
||||
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, relBuilder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请关系失败: %v", err)
|
||||
}
|
||||
|
||||
childSet := make(map[string]map[string]struct{})
|
||||
addEdge := func(parentId, childId string) {
|
||||
if parentId == "" || childId == "" || parentId == childId {
|
||||
return
|
||||
}
|
||||
if _, ok := agentById[parentId]; !ok {
|
||||
return
|
||||
}
|
||||
if _, ok := agentById[childId]; !ok {
|
||||
return
|
||||
}
|
||||
if childSet[parentId] == nil {
|
||||
childSet[parentId] = make(map[string]struct{})
|
||||
}
|
||||
childSet[parentId][childId] = struct{}{}
|
||||
}
|
||||
|
||||
for _, r := range relations {
|
||||
addEdge(r.ParentId, r.ChildId)
|
||||
}
|
||||
|
||||
reachable := map[string]bool{teamRootId: true}
|
||||
var walkReach func(string)
|
||||
walkReach = func(id string) {
|
||||
for cid := range childSet[id] {
|
||||
if reachable[cid] {
|
||||
continue
|
||||
}
|
||||
reachable[cid] = true
|
||||
walkReach(cid)
|
||||
}
|
||||
}
|
||||
walkReach(teamRootId)
|
||||
|
||||
for _, m := range members {
|
||||
if m.Id == teamRootId {
|
||||
continue
|
||||
}
|
||||
if !reachable[m.Id] {
|
||||
addEdge(teamRootId, m.Id)
|
||||
if !reachable[m.Id] {
|
||||
reachable[m.Id] = true
|
||||
walkReach(m.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
childrenMap := make(map[string][]string)
|
||||
for pid, set := range childSet {
|
||||
slice := make([]string, 0, len(set))
|
||||
for cid := range set {
|
||||
slice = append(slice, cid)
|
||||
}
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return agentById[slice[i]].AgentCode < agentById[slice[j]].AgentCode
|
||||
})
|
||||
childrenMap[pid] = slice
|
||||
}
|
||||
|
||||
var build func(string) (types.AgentTeamTreeNode, error)
|
||||
build = func(agentId string) (types.AgentTeamTreeNode, error) {
|
||||
ag := agentById[agentId]
|
||||
if ag == nil {
|
||||
return types.AgentTeamTreeNode{}, errors.New("missing agent in tree")
|
||||
}
|
||||
mobile := ag.Mobile
|
||||
mobile, err = crypto.DecryptMobile(mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||
if err != nil {
|
||||
return types.AgentTeamTreeNode{}, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err)
|
||||
}
|
||||
|
||||
node := types.AgentTeamTreeNode{
|
||||
Id: ag.Id,
|
||||
AgentCode: ag.AgentCode,
|
||||
Level: ag.Level,
|
||||
LevelName: levelName(ag.Level),
|
||||
Mobile: mobile,
|
||||
IsAnchor: ag.Id == anchor.Id,
|
||||
Children: []types.AgentTeamTreeNode{},
|
||||
}
|
||||
|
||||
kids := childrenMap[agentId]
|
||||
for _, cid := range kids {
|
||||
childNode, err := build(cid)
|
||||
if err != nil {
|
||||
return types.AgentTeamTreeNode{}, err
|
||||
}
|
||||
node.Children = append(node.Children, childNode)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
root, err := build(teamRootId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AdminGetAgentTeamTreeResp{Root: root}, nil
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
@@ -31,9 +33,26 @@ func (l *AdminGetAgentUpgradeListLogic) AdminGetAgentUpgradeList(req *types.Admi
|
||||
builder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
|
||||
Where("del_state = ?", globalkey.DelStateNo)
|
||||
|
||||
if req.AgentId != nil {
|
||||
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||
if parseErr != nil {
|
||||
return &types.AdminGetAgentUpgradeListResp{
|
||||
Total: 0,
|
||||
Items: []types.AgentUpgradeListItem{},
|
||||
}, nil
|
||||
}
|
||||
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||
if findErr != nil || agent == nil {
|
||||
return &types.AdminGetAgentUpgradeListResp{
|
||||
Total: 0,
|
||||
Items: []types.AgentUpgradeListItem{},
|
||||
}, nil
|
||||
}
|
||||
builder = builder.Where("agent_id = ?", agent.Id)
|
||||
} else if req.AgentId != nil && *req.AgentId != "" {
|
||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||
}
|
||||
|
||||
if req.UpgradeType != nil {
|
||||
builder = builder.Where("upgrade_type = ?", *req.UpgradeType)
|
||||
}
|
||||
@@ -56,12 +75,21 @@ func (l *AdminGetAgentUpgradeListLogic) AdminGetAgentUpgradeList(req *types.Admi
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录列表失败, %v", err)
|
||||
}
|
||||
|
||||
agentIds := make([]string, 0, len(upgrades))
|
||||
for _, upgrade := range upgrades {
|
||||
if upgrade.AgentId != "" {
|
||||
agentIds = append(agentIds, upgrade.AgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
// 组装响应
|
||||
items := make([]types.AgentUpgradeListItem, 0, len(upgrades))
|
||||
for _, upgrade := range upgrades {
|
||||
items = append(items, types.AgentUpgradeListItem{
|
||||
Id: upgrade.Id,
|
||||
AgentId: upgrade.AgentId,
|
||||
AgentCode: agentCodeMap[upgrade.AgentId],
|
||||
FromLevel: upgrade.FromLevel,
|
||||
ToLevel: upgrade.ToLevel,
|
||||
UpgradeType: upgrade.UpgradeType,
|
||||
|
||||
@@ -2,9 +2,13 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/globalkey"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -27,23 +31,60 @@ func NewAdminGetAgentWithdrawalListLogic(ctx context.Context, svcCtx *svc.Servic
|
||||
|
||||
func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *types.AdminGetAgentWithdrawalListReq) (resp *types.AdminGetAgentWithdrawalListResp, err error) {
|
||||
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder()
|
||||
if req.AgentId != nil {
|
||||
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
|
||||
if req.AgentId != nil && strings.TrimSpace(*req.AgentId) != "" {
|
||||
builder = builder.Where(squirrel.Eq{"agent_id": strings.TrimSpace(*req.AgentId)})
|
||||
}
|
||||
if req.Status != nil {
|
||||
builder = builder.Where(squirrel.Eq{"status": *req.Status})
|
||||
if req.AgentCode != nil && strings.TrimSpace(*req.AgentCode) != "" {
|
||||
var code int64
|
||||
if _, scanErr := fmt.Sscanf(strings.TrimSpace(*req.AgentCode), "%d", &code); scanErr == nil {
|
||||
builder = builder.Where(
|
||||
"agent_id IN (SELECT id FROM agent WHERE agent_code = ? AND del_state = ?)",
|
||||
code,
|
||||
globalkey.DelStateNo,
|
||||
)
|
||||
}
|
||||
}
|
||||
if req.WithdrawNo != nil && *req.WithdrawNo != "" {
|
||||
builder = builder.Where(squirrel.Eq{"withdraw_no": *req.WithdrawNo})
|
||||
if strings.TrimSpace(req.Status) != "" {
|
||||
st, perr := strconv.ParseInt(strings.TrimSpace(req.Status), 10, 64)
|
||||
if perr == nil {
|
||||
builder = builder.Where(squirrel.Eq{"status": st})
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(req.WithdrawalType) != "" {
|
||||
wt, perr := strconv.ParseInt(strings.TrimSpace(req.WithdrawalType), 10, 64)
|
||||
if perr == nil {
|
||||
builder = builder.Where(squirrel.Eq{"withdrawal_type": wt})
|
||||
}
|
||||
}
|
||||
if req.WithdrawNo != nil && strings.TrimSpace(*req.WithdrawNo) != "" {
|
||||
pat := "%" + strings.TrimSpace(*req.WithdrawNo) + "%"
|
||||
builder = builder.Where("withdraw_no LIKE ?", pat)
|
||||
}
|
||||
if req.PayeeAccount != nil && strings.TrimSpace(*req.PayeeAccount) != "" {
|
||||
pat := "%" + strings.TrimSpace(*req.PayeeAccount) + "%"
|
||||
builder = builder.Where("payee_account LIKE ?", pat)
|
||||
}
|
||||
if req.PayeeName != nil && strings.TrimSpace(*req.PayeeName) != "" {
|
||||
pat := "%" + strings.TrimSpace(*req.PayeeName) + "%"
|
||||
builder = builder.Where("payee_name LIKE ?", pat)
|
||||
}
|
||||
list, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agentIds := make([]string, 0, len(list))
|
||||
for _, v := range list {
|
||||
if v.AgentId != "" {
|
||||
agentIds = append(agentIds, v.AgentId)
|
||||
}
|
||||
}
|
||||
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||
|
||||
items := make([]types.AgentWithdrawalListItem, 0, len(list))
|
||||
for _, v := range list {
|
||||
item := types.AgentWithdrawalListItem{}
|
||||
_ = copier.Copy(&item, v)
|
||||
item.AgentCode = agentCodeMap[v.AgentId]
|
||||
item.Remark = ""
|
||||
if v.Remark.Valid {
|
||||
item.Remark = v.Remark.String
|
||||
|
||||
@@ -2,6 +2,9 @@ package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/common/xerr"
|
||||
"qnc-server/pkg/lzkit/crypto"
|
||||
@@ -34,22 +37,18 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
||||
builder := l.svcCtx.AgentInviteCodeModel.SelectBuilder().
|
||||
Where("del_state = ?", globalkey.DelStateNo)
|
||||
|
||||
if req.Code != nil && *req.Code != "" {
|
||||
builder = builder.Where("code = ?", *req.Code)
|
||||
}
|
||||
if req.AgentId != nil && *req.AgentId != "" {
|
||||
if *req.AgentId == "0" {
|
||||
// agent_id = 0 表示查询平台发放的邀请码(agent_id为NULL)
|
||||
builder = builder.Where("agent_id IS NULL")
|
||||
} else {
|
||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||
}
|
||||
if req.Code != nil && strings.TrimSpace(*req.Code) != "" {
|
||||
pat := "%" + strings.TrimSpace(*req.Code) + "%"
|
||||
builder = builder.Where("code LIKE ?", pat)
|
||||
}
|
||||
if req.TargetLevel != nil {
|
||||
builder = builder.Where("target_level = ?", *req.TargetLevel)
|
||||
}
|
||||
if req.Status != nil {
|
||||
builder = builder.Where("status = ?", *req.Status)
|
||||
if strings.TrimSpace(req.Status) != "" {
|
||||
st, perr := strconv.ParseInt(strings.TrimSpace(req.Status), 10, 64)
|
||||
if perr == nil {
|
||||
builder = builder.Where("status = ?", st)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 分页查询
|
||||
@@ -58,15 +57,19 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码列表失败, %v", err)
|
||||
}
|
||||
|
||||
// 3. 批量查询代理信息(用于显示代理手机号)
|
||||
// 3. 批量查询代理信息(用于显示代理手机号、代理编号)
|
||||
agentIds := make(map[string]struct{})
|
||||
for _, v := range list {
|
||||
if v.AgentId.Valid && v.AgentId.String != "" {
|
||||
agentIds[v.AgentId.String] = struct{}{}
|
||||
}
|
||||
if v.UsedAgentId.Valid && v.UsedAgentId.String != "" {
|
||||
agentIds[v.UsedAgentId.String] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
agentMobileMap := make(map[string]string)
|
||||
agentCodeMap := make(map[string]int64)
|
||||
if len(agentIds) > 0 {
|
||||
agentIdList := make([]string, 0, len(agentIds))
|
||||
for id := range agentIds {
|
||||
@@ -75,6 +78,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
||||
agents, _ := l.svcCtx.AgentModel.FindAll(l.ctx, l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": agentIdList}), "")
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
for _, agent := range agents {
|
||||
agentCodeMap[agent.Id] = agent.AgentCode
|
||||
mobile, decErr := crypto.DecryptMobile(agent.Mobile, secretKey)
|
||||
if decErr == nil {
|
||||
agentMobileMap[agent.Id] = mobile
|
||||
@@ -100,6 +104,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
||||
if v.AgentId.Valid {
|
||||
item.AgentId = v.AgentId.String
|
||||
item.AgentMobile = agentMobileMap[v.AgentId.String]
|
||||
item.AgentCode = agentCodeMap[v.AgentId.String]
|
||||
}
|
||||
|
||||
if v.UsedUserId.Valid {
|
||||
@@ -107,6 +112,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
||||
}
|
||||
if v.UsedAgentId.Valid {
|
||||
item.UsedAgentId = v.UsedAgentId.String
|
||||
item.UsedAgentCode = agentCodeMap[v.UsedAgentId.String]
|
||||
}
|
||||
if v.UsedTime.Valid {
|
||||
item.UsedTime = v.UsedTime.Time.Format("2006-01-02 15:04:05")
|
||||
|
||||
@@ -163,12 +163,9 @@ func (l *AdminUpdateAgentConfigLogic) AdminUpdateAgentConfig(req *types.AdminUpd
|
||||
if err := updateConfig("commission_freeze_threshold", &req.CommissionFreeze.Threshold); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结阈值失败, %v", err)
|
||||
}
|
||||
// 更新解冻天数(整数类型)
|
||||
if req.CommissionFreeze.Days > 0 {
|
||||
daysFloat := float64(req.CommissionFreeze.Days)
|
||||
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
|
||||
}
|
||||
daysFloat := float64(req.CommissionFreeze.Days)
|
||||
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package admin_agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
// batchAgentCodesByIds 按代理主键批量查询 agent_code,用于列表软关联展示。
|
||||
func batchAgentCodesByIds(ctx context.Context, svcCtx *svc.ServiceContext, ids []string) map[string]int64 {
|
||||
out := make(map[string]int64)
|
||||
uniq := make([]string, 0, len(ids))
|
||||
seen := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
uniq = append(uniq, id)
|
||||
}
|
||||
if len(uniq) == 0 {
|
||||
return out
|
||||
}
|
||||
agents, err := svcCtx.AgentModel.FindAll(ctx, svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": uniq}), "")
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
for _, a := range agents {
|
||||
out[a.Id] = a.AgentCode
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package admin_feature
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/logic/productcost"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/xerr"
|
||||
@@ -60,6 +61,10 @@ func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatu
|
||||
"更新功能失败, err: %v, id: %d", err, req.Id)
|
||||
}
|
||||
|
||||
if err := productcost.SyncAllProductsUsingFeature(l.ctx, l.svcCtx, req.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 返回成功结果
|
||||
return &types.AdminUpdateFeatureResp{Success: true}, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package admin_notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
@@ -32,7 +33,10 @@ func (l *AdminGetNotificationListLogic) AdminGetNotificationList(req *types.Admi
|
||||
builder = builder.Where("title LIKE ?", "%"+*req.Title+"%")
|
||||
}
|
||||
if req.NotificationPage != nil {
|
||||
builder = builder.Where("notification_page = ?", *req.NotificationPage)
|
||||
s := strings.TrimSpace(*req.NotificationPage)
|
||||
if s != "" {
|
||||
builder = builder.Where("notification_page LIKE ?", "%"+s+"%")
|
||||
}
|
||||
}
|
||||
if req.Status != nil {
|
||||
builder = builder.Where("status = ?", *req.Status)
|
||||
|
||||
@@ -3,6 +3,7 @@ package admin_notification
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
@@ -33,12 +34,28 @@ func (l *AdminUpdateNotificationLogic) AdminUpdateNotification(req *types.AdminU
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
|
||||
}
|
||||
if req.StartDate != nil {
|
||||
startDate, _ := time.Parse("2006-01-02", *req.StartDate)
|
||||
notification.StartDate = sql.NullTime{Time: startDate, Valid: true}
|
||||
s := strings.TrimSpace(*req.StartDate)
|
||||
if s == "" {
|
||||
notification.StartDate = sql.NullTime{Valid: false}
|
||||
} else {
|
||||
startDate, err := time.Parse("2006-01-02", s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "start_date 格式错误: %v", err)
|
||||
}
|
||||
notification.StartDate = sql.NullTime{Time: startDate, Valid: true}
|
||||
}
|
||||
}
|
||||
if req.EndDate != nil {
|
||||
endDate, _ := time.Parse("2006-01-02", *req.EndDate)
|
||||
notification.EndDate = sql.NullTime{Time: endDate, Valid: true}
|
||||
s := strings.TrimSpace(*req.EndDate)
|
||||
if s == "" {
|
||||
notification.EndDate = sql.NullTime{Valid: false}
|
||||
} else {
|
||||
endDate, err := time.Parse("2006-01-02", s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "end_date 格式错误: %v", err)
|
||||
}
|
||||
notification.EndDate = sql.NullTime{Time: endDate, Valid: true}
|
||||
}
|
||||
}
|
||||
if req.Title != nil {
|
||||
notification.Title = *req.Title
|
||||
|
||||
@@ -2,6 +2,7 @@ package admin_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
@@ -54,6 +55,24 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
||||
if req.Status != "" {
|
||||
builder = builder.Where("status = ?", req.Status)
|
||||
}
|
||||
// 是否代理订单筛选
|
||||
if req.IsAgentOrder != nil {
|
||||
if *req.IsAgentOrder {
|
||||
// 筛选代理订单:订单ID在agent_order表中
|
||||
builder = builder.Where("id IN (SELECT order_id FROM agent_order WHERE del_state = 0)")
|
||||
} else {
|
||||
// 筛选非代理订单:订单ID不在agent_order表中
|
||||
builder = builder.Where("id NOT IN (SELECT order_id FROM agent_order WHERE del_state = 0)")
|
||||
}
|
||||
}
|
||||
// 代理编号筛选
|
||||
if req.AgentCode != "" {
|
||||
// 将字符串转换为int64进行查询
|
||||
var agentCode int64
|
||||
if _, err := fmt.Sscanf(req.AgentCode, "%d", &agentCode); err == nil {
|
||||
builder = builder.Where("id IN (SELECT order_id FROM agent_order ao JOIN agent a ON ao.agent_id = a.id WHERE a.agent_code = ? AND ao.del_state = 0)", agentCode)
|
||||
}
|
||||
}
|
||||
// 时间范围查询
|
||||
if req.CreateTimeStart != "" {
|
||||
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
|
||||
@@ -101,6 +120,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
||||
queryStateMap := make(map[string]string)
|
||||
agentOrderMap := make(map[string]bool) // 代理订单映射
|
||||
agentProcessStatusMap := make(map[string]string) // 代理处理状态映射
|
||||
agentCodeMap := make(map[string]string) // 代理编号映射
|
||||
|
||||
var mu sync.Mutex
|
||||
|
||||
@@ -169,8 +189,32 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
||||
}
|
||||
|
||||
// 记录代理订单
|
||||
agentIds := make([]string, 0, len(agentOrders))
|
||||
for _, agentOrder := range agentOrders {
|
||||
agentOrderMap[agentOrder.OrderId] = true
|
||||
agentIds = append(agentIds, agentOrder.AgentId)
|
||||
}
|
||||
|
||||
// 批量获取代理编号
|
||||
if len(agentIds) > 0 {
|
||||
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
|
||||
l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": agentIds}), "")
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理信息失败 err: %v", err)
|
||||
}
|
||||
|
||||
// 构建代理ID到代理编号的映射
|
||||
agentIdToCodeMap := make(map[string]string)
|
||||
for _, agent := range agents {
|
||||
agentIdToCodeMap[agent.Id] = fmt.Sprintf("%d", agent.AgentCode)
|
||||
}
|
||||
|
||||
// 为每个代理订单设置代理编号
|
||||
for _, agentOrder := range agentOrders {
|
||||
if code, exists := agentIdToCodeMap[agentOrder.AgentId]; exists {
|
||||
agentCodeMap[agentOrder.OrderId] = code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对于代理订单,查询代理处理状态
|
||||
@@ -277,6 +321,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
||||
if agentOrderMap[order.Id] {
|
||||
item.IsAgentOrder = true
|
||||
item.AgentProcessStatus = agentProcessStatusMap[order.Id]
|
||||
item.AgentCode = agentCodeMap[order.Id]
|
||||
} else {
|
||||
item.IsAgentOrder = false
|
||||
item.AgentProcessStatus = "not_agent"
|
||||
|
||||
@@ -156,6 +156,13 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
|
||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||
}
|
||||
|
||||
// 退款成功落库后冲正代理佣金/返佣(部分退款与全额退款均全额冲回本单分账;仅已退款时执行,不含「退款中」)
|
||||
if orderStatus == model.OrderStatusRefunded {
|
||||
if revErr := l.svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(ctx, session, order.Id); revErr != nil {
|
||||
return revErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,8 +30,17 @@ func NewAdminGetPlatformUserListLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
||||
|
||||
func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) {
|
||||
builder := l.svcCtx.UserModel.SelectBuilder()
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
if req.UserId != "" {
|
||||
builder = builder.Where("id = ?", req.UserId)
|
||||
}
|
||||
if req.Mobile != "" {
|
||||
builder = builder.Where("mobile = ?", req.Mobile)
|
||||
// 数据库存密文,搜索时把明文手机号加密后再查询
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机号加密失败: %v", err)
|
||||
}
|
||||
builder = builder.Where("mobile = ?", encryptedMobile)
|
||||
}
|
||||
if req.Nickname != "" {
|
||||
builder = builder.Where("nickname = ?", req.Nickname)
|
||||
@@ -55,16 +64,15 @@ func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.Admi
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户分页失败: %v", err)
|
||||
}
|
||||
var items []types.PlatformUserListItem
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
|
||||
for _, user := range users {
|
||||
mobile := user.Mobile
|
||||
if mobile.Valid {
|
||||
encryptedMobile, err := crypto.DecryptMobile(mobile.String, secretKey)
|
||||
DecryptMobile, err := crypto.DecryptMobile(mobile.String, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 解密手机号失败: %+v", err)
|
||||
}
|
||||
mobile = sql.NullString{String: encryptedMobile, Valid: true}
|
||||
mobile = sql.NullString{String: DecryptMobile, Valid: true}
|
||||
}
|
||||
itemData := types.PlatformUserListItem{
|
||||
Id: user.Id,
|
||||
|
||||
@@ -28,6 +28,7 @@ func NewAdminCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
||||
}
|
||||
}
|
||||
|
||||
// AdminCreateProduct 创建产品后,在同一事务内写入一条 agent_product_config(与代理产品配置列表一致)。
|
||||
func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) {
|
||||
// 1. 数据转换
|
||||
data := &model.Product{
|
||||
@@ -36,7 +37,7 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
|
||||
ProductEn: req.ProductEn,
|
||||
Description: req.Description,
|
||||
Notes: sql.NullString{String: req.Notes, Valid: req.Notes != ""},
|
||||
CostPrice: req.CostPrice,
|
||||
CostPrice: 0, // 成本由关联模块汇总,创建后为 0,保存模块关联后写入
|
||||
SellPrice: req.SellPrice,
|
||||
}
|
||||
|
||||
@@ -51,7 +52,14 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
|
||||
}
|
||||
productId = data.Id
|
||||
|
||||
// 2.2 同步创建代理产品配置(使用默认值)
|
||||
// 2.2 同步创建代理产品配置(使用默认值);已存在则跳过(幂等)
|
||||
if _, findErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, productId); findErr == nil {
|
||||
return nil
|
||||
} else if findErr != model.ErrNotFound {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"检查代理产品配置失败, err: %v, productId: %s", findErr, productId)
|
||||
}
|
||||
|
||||
agentProductConfig := &model.AgentProductConfig{
|
||||
Id: uuid.NewString(),
|
||||
ProductId: productId,
|
||||
|
||||
@@ -2,8 +2,10 @@ package admin_product
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -25,12 +27,13 @@ func NewAdminDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
||||
}
|
||||
}
|
||||
|
||||
// AdminDeleteProduct 软删除产品后,在同一事务内软删除对应 agent_product_config(与产品列表一致)。
|
||||
func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) {
|
||||
// 1. 查询记录是否存在
|
||||
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"查找产品失败, err: %v, id: %d", err, req.Id)
|
||||
"查找产品失败, err: %v, id: %s", err, req.Id)
|
||||
}
|
||||
|
||||
// 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
|
||||
@@ -39,20 +42,22 @@ func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProdu
|
||||
err = l.svcCtx.ProductModel.DeleteSoft(ctx, session, record)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"删除产品失败, err: %v, id: %d", err, req.Id)
|
||||
"删除产品失败, err: %v, id: %s", err, record.Id)
|
||||
}
|
||||
|
||||
// 2.2 同步软删除代理产品配置
|
||||
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, req.Id)
|
||||
if err != nil {
|
||||
// 如果代理产品配置不存在,记录日志但不影响主流程
|
||||
l.Infof("同步删除代理产品配置失败:代理产品配置不存在, productId: %d, err: %v", req.Id, err)
|
||||
} else {
|
||||
err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig)
|
||||
if err != nil {
|
||||
// 2.2 同步软删除代理产品配置(按产品主键 product_id)
|
||||
agentProductConfig, findErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, record.Id)
|
||||
switch {
|
||||
case findErr == nil:
|
||||
if err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"同步删除代理产品配置失败, err: %v, productId: %d", err, req.Id)
|
||||
"同步删除代理产品配置失败, err: %v, productId: %s", err, record.Id)
|
||||
}
|
||||
case findErr == model.ErrNotFound:
|
||||
l.Infof("产品无对应代理产品配置,跳过同步删除, productId: %s", record.Id)
|
||||
default:
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"查询代理产品配置失败, err: %v, productId: %s", findErr, record.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -32,14 +32,21 @@ func (l *AdminGetProductDetailLogic) AdminGetProductDetail(req *types.AdminGetPr
|
||||
"查找产品失败, err: %v, id: %d", err, req.Id)
|
||||
}
|
||||
|
||||
// 2. 构建响应
|
||||
// 2. 成本价为关联模块 cost_price 之和
|
||||
costSum, err := l.svcCtx.ProductFeatureModel.SumFeatureCostPriceByProductId(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"汇总产品模块成本失败, err: %v, id: %s", err, req.Id)
|
||||
}
|
||||
|
||||
// 3. 构建响应
|
||||
resp = &types.AdminGetProductDetailResp{
|
||||
Id: record.Id,
|
||||
ProductName: record.ProductName,
|
||||
ProductEn: record.ProductEn,
|
||||
Description: record.Description,
|
||||
Notes: record.Notes.String,
|
||||
CostPrice: record.CostPrice,
|
||||
CostPrice: costSum,
|
||||
SellPrice: record.SellPrice,
|
||||
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||
|
||||
@@ -105,6 +105,7 @@ func (l *AdminGetProductFeatureListLogic) AdminGetProductFeatureList(req *types.
|
||||
FeatureId: item.FeatureId,
|
||||
ApiId: feature.ApiId,
|
||||
Name: feature.Name,
|
||||
CostPrice: feature.CostPrice,
|
||||
Sort: item.Sort,
|
||||
Enable: item.Enable,
|
||||
IsImportant: item.IsImportant,
|
||||
|
||||
@@ -44,16 +44,26 @@ func (l *AdminGetProductListLogic) AdminGetProductList(req *types.AdminGetProduc
|
||||
"查询产品列表失败, err: %v, req: %+v", err, req)
|
||||
}
|
||||
|
||||
// 4. 构建响应列表
|
||||
// 4. 构建响应列表(成本价为关联模块 cost_price 之和,非库表字段直读)
|
||||
items := make([]types.ProductListItem, 0, len(list))
|
||||
productIds := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
productIds = append(productIds, item.Id)
|
||||
}
|
||||
costByProduct, err := l.svcCtx.ProductFeatureModel.SumFeatureCostPriceByProductIds(l.ctx, productIds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"汇总产品模块成本失败, err: %v, req: %+v", err, req)
|
||||
}
|
||||
for _, item := range list {
|
||||
cost := costByProduct[item.Id]
|
||||
listItem := types.ProductListItem{
|
||||
Id: item.Id,
|
||||
ProductName: item.ProductName,
|
||||
ProductEn: item.ProductEn,
|
||||
Description: item.Description,
|
||||
Notes: item.Notes.String,
|
||||
CostPrice: item.CostPrice,
|
||||
CostPrice: cost,
|
||||
SellPrice: item.SellPrice,
|
||||
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package admin_product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/google/uuid"
|
||||
"qnc-server/app/main/api/internal/logic/productcost"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type AdminUpdateProductFeaturesLogic struct {
|
||||
@@ -120,15 +122,15 @@ func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 新增关联
|
||||
newFeature := &model.ProductFeature{
|
||||
Id: uuid.NewString(),
|
||||
ProductId: req.ProductId,
|
||||
FeatureId: data.featureId,
|
||||
Sort: data.newItem.Sort,
|
||||
Enable: data.newItem.Enable,
|
||||
IsImportant: data.newItem.IsImportant,
|
||||
}
|
||||
// 新增关联
|
||||
newFeature := &model.ProductFeature{
|
||||
Id: uuid.NewString(),
|
||||
ProductId: req.ProductId,
|
||||
FeatureId: data.featureId,
|
||||
Sort: data.newItem.Sort,
|
||||
Enable: data.newItem.Enable,
|
||||
IsImportant: data.newItem.IsImportant,
|
||||
}
|
||||
_, err = l.svcCtx.ProductFeatureModel.Insert(ctx, session, newFeature)
|
||||
if err != nil {
|
||||
updateErr = errors.Wrapf(err, "新增产品功能关联失败, product_id: %d, feature_id: %d",
|
||||
@@ -156,6 +158,10 @@ func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.
|
||||
"更新产品功能关联失败, err: %v, req: %+v", err, req)
|
||||
}
|
||||
|
||||
if err := productcost.SyncProductCostFromModules(l.ctx, l.svcCtx, req.ProductId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 返回结果
|
||||
return &types.AdminUpdateProductFeaturesResp{Success: true}, nil
|
||||
}
|
||||
|
||||
@@ -47,9 +47,6 @@ func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProdu
|
||||
if req.Notes != nil {
|
||||
record.Notes = sql.NullString{String: *req.Notes, Valid: *req.Notes != ""}
|
||||
}
|
||||
if req.CostPrice != nil {
|
||||
record.CostPrice = *req.CostPrice
|
||||
}
|
||||
if req.SellPrice != nil {
|
||||
record.SellPrice = *req.SellPrice
|
||||
}
|
||||
|
||||
@@ -88,26 +88,29 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 验证提现金额
|
||||
if req.Amount <= 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
|
||||
}
|
||||
|
||||
// 6. 获取钱包信息
|
||||
// 5. 获取钱包信息
|
||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败, %v", err)
|
||||
}
|
||||
|
||||
// 7. 验证余额(包括检查是否为负数)
|
||||
if wallet.Balance < 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("账户存在欠款,请先补足欠款后再申请提现,当前余额:%.2f", wallet.Balance)), "")
|
||||
// 6. 提现金额与可用余额:仅可提「可用余额」内金额;可用余额须大于 0
|
||||
withdrawAmount := lzUtils.RoundMoney(req.Amount)
|
||||
if withdrawAmount <= 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
|
||||
}
|
||||
if wallet.Balance < req.Amount {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("余额不足,当前余额:%.2f", wallet.Balance)), "")
|
||||
bal := lzUtils.RoundMoney(wallet.Balance)
|
||||
if bal < 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("账户存在欠款,请先补足欠款后再申请提现,当前可用余额:%.2f", bal)), "")
|
||||
}
|
||||
if bal <= 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("可用余额须大于0才能申请提现"), "")
|
||||
}
|
||||
if withdrawAmount > bal {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("提现金额不能超过可用余额(不含冻结),当前可用:%.2f", bal)), "")
|
||||
}
|
||||
|
||||
// 8. 支付宝月度提现额度校验(仅针对支付宝提现)
|
||||
// 7. 支付宝月度提现额度校验(仅针对支付宝提现)
|
||||
if req.WithdrawalType == 1 {
|
||||
now := time.Now()
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
@@ -138,7 +141,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
)
|
||||
}
|
||||
|
||||
if req.Amount > remainQuota {
|
||||
if withdrawAmount > remainQuota {
|
||||
return nil, errors.Wrapf(
|
||||
xerr.NewErrMsg(fmt.Sprintf("本月支付宝最高可提现 %.2f 元,请调整提现金额或使用银行卡提现", remainQuota)),
|
||||
"",
|
||||
@@ -148,7 +151,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
|
||||
// 9. 计算税费
|
||||
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
||||
taxInfo, err := l.calculateTax(l.ctx, agent.Id, req.Amount, yearMonth)
|
||||
taxInfo, err := l.calculateTax(l.ctx, agent.Id, withdrawAmount, yearMonth)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "计算税费失败")
|
||||
}
|
||||
@@ -164,9 +167,26 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
// 11. 使用事务处理提现申请
|
||||
var withdrawalId string
|
||||
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||
// 提交前再次校验可用余额(防止并发入账/冲正与申请提现竞态)
|
||||
fresh, wErr := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, agent.Id)
|
||||
if wErr != nil {
|
||||
return errors.Wrapf(wErr, "事务内查询钱包失败")
|
||||
}
|
||||
freshBal := lzUtils.RoundMoney(fresh.Balance)
|
||||
if freshBal < 0 {
|
||||
return errors.New("账户存在欠款,无法申请提现")
|
||||
}
|
||||
if freshBal <= 0 {
|
||||
return errors.New("可用余额须大于0才能申请提现")
|
||||
}
|
||||
if withdrawAmount > freshBal {
|
||||
return fmt.Errorf("提现金额不能超过当前可用余额(%.2f)", freshBal)
|
||||
}
|
||||
wallet = fresh
|
||||
|
||||
// 11.1 冻结余额
|
||||
wallet.FrozenBalance += req.Amount
|
||||
wallet.Balance -= req.Amount
|
||||
wallet.FrozenBalance += withdrawAmount
|
||||
wallet.Balance -= withdrawAmount
|
||||
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
|
||||
return errors.Wrapf(err, "冻结余额失败")
|
||||
}
|
||||
@@ -179,7 +199,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
WithdrawalType: req.WithdrawalType,
|
||||
PayeeAccount: req.PayeeAccount,
|
||||
PayeeName: req.PayeeName,
|
||||
Amount: req.Amount,
|
||||
Amount: withdrawAmount,
|
||||
ActualAmount: taxInfo.ActualAmount,
|
||||
TaxAmount: taxInfo.TaxAmount,
|
||||
Status: 1, // 待审核
|
||||
@@ -206,7 +226,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
||||
AgentId: agent.Id,
|
||||
WithdrawalId: withdrawalId,
|
||||
YearMonth: yearMonth,
|
||||
WithdrawalAmount: req.Amount,
|
||||
WithdrawalAmount: withdrawAmount,
|
||||
TaxableAmount: taxInfo.TaxableAmount,
|
||||
TaxRate: taxInfo.TaxRate,
|
||||
TaxAmount: taxInfo.TaxAmount,
|
||||
@@ -293,8 +313,8 @@ func (l *ApplyWithdrawalLogic) calculateTax(ctx context.Context, agentId string,
|
||||
}
|
||||
|
||||
// 计算税费
|
||||
taxAmount := taxableAmount * taxRate
|
||||
actualAmount := amount - taxAmount
|
||||
taxAmount := lzUtils.RoundMoney(taxableAmount * taxRate)
|
||||
actualAmount := lzUtils.RoundMoney(amount - taxAmount)
|
||||
|
||||
return &TaxInfo{
|
||||
TaxableAmount: taxableAmount,
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"qnc-server/common/tool"
|
||||
"qnc-server/common/xerr"
|
||||
"qnc-server/pkg/lzkit/crypto"
|
||||
"qnc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
@@ -41,6 +42,12 @@ func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Ge
|
||||
}
|
||||
|
||||
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
|
||||
// 将 set_price 从 string 转为 float64
|
||||
setPrice, err := strconv.ParseFloat(req.SetPrice, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格格式不正确"), "")
|
||||
}
|
||||
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广链接失败, %v", err)
|
||||
@@ -71,14 +78,14 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
||||
}
|
||||
|
||||
basePrice := productConfig.BasePrice
|
||||
actualBasePrice := basePrice + float64(levelBonus)
|
||||
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||
systemMaxPrice := productConfig.SystemMaxPrice
|
||||
upliftAmount, err := l.getLevelMaxUpliftAmount(agentModel.Level)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取等级上调金额失败, %v", err)
|
||||
}
|
||||
levelMaxPrice := systemMaxPrice + upliftAmount
|
||||
if req.SetPrice < actualBasePrice || req.SetPrice > levelMaxPrice {
|
||||
levelMaxPrice := lzUtils.RoundMoney(systemMaxPrice + upliftAmount)
|
||||
if setPrice < actualBasePrice || setPrice > levelMaxPrice {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格必须在 %.2f 到 %.2f 之间"), "设定价格必须在 %.2f 到 %.2f 之间", actualBasePrice, levelMaxPrice)
|
||||
}
|
||||
|
||||
@@ -86,7 +93,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
||||
builder := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
|
||||
squirrel.Eq{"agent_id": agentModel.Id},
|
||||
squirrel.Eq{"product_id": req.ProductId},
|
||||
squirrel.Eq{"set_price": req.SetPrice},
|
||||
squirrel.Eq{"set_price": setPrice},
|
||||
squirrel.Eq{"del_state": globalkey.DelStateNo},
|
||||
})
|
||||
|
||||
@@ -115,7 +122,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
||||
var agentIdentifier types.AgentIdentifier
|
||||
agentIdentifier.AgentID = agentModel.Id
|
||||
agentIdentifier.ProductID = req.ProductId
|
||||
agentIdentifier.SetPrice = req.SetPrice
|
||||
agentIdentifier.SetPrice = setPrice
|
||||
agentIdentifierByte, err := json.Marshal(agentIdentifier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化标识失败, %v", err)
|
||||
@@ -140,7 +147,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
||||
UserId: userID,
|
||||
ProductId: req.ProductId,
|
||||
LinkIdentifier: encrypted,
|
||||
SetPrice: req.SetPrice,
|
||||
SetPrice: setPrice,
|
||||
ActualBasePrice: actualBasePrice,
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/ctxdata"
|
||||
"qnc-server/common/xerr"
|
||||
"qnc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -76,11 +77,11 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
|
||||
productBasePrice := productConfig.BasePrice
|
||||
|
||||
// 计算该产品的实际底价
|
||||
productActualBasePrice := productBasePrice + float64(levelBonus)
|
||||
productActualBasePrice := lzUtils.RoundMoney(productBasePrice + float64(levelBonus))
|
||||
|
||||
priceRangeMin := productActualBasePrice
|
||||
upliftAmount, _ := l.getLevelMaxUpliftAmount(agentModel.Level)
|
||||
priceRangeMax := productConfig.SystemMaxPrice + upliftAmount
|
||||
priceRangeMax := lzUtils.RoundMoney(productConfig.SystemMaxPrice + upliftAmount)
|
||||
|
||||
// 使用产品配置的提价阈值和手续费比例,如果为NULL则使用0
|
||||
productPriceThreshold := 0.0
|
||||
|
||||
@@ -292,7 +292,7 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
||||
|
||||
// 使用产品配置的底价计算实际底价
|
||||
basePrice := productConfig.BasePrice
|
||||
actualBasePrice := basePrice + float64(levelBonus)
|
||||
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||
|
||||
// 计算提价成本(使用产品配置)
|
||||
priceThreshold := 0.0
|
||||
@@ -306,11 +306,11 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
||||
|
||||
priceCost := 0.0
|
||||
if agentLinkModel.SetPrice > priceThreshold {
|
||||
priceCost = (agentLinkModel.SetPrice - priceThreshold) * priceFeeRate
|
||||
priceCost = lzUtils.RoundMoney((agentLinkModel.SetPrice - priceThreshold) * priceFeeRate)
|
||||
}
|
||||
|
||||
// 计算代理收益
|
||||
agentProfit := agentLinkModel.SetPrice - actualBasePrice - priceCost
|
||||
agentProfit := lzUtils.RoundMoney(agentLinkModel.SetPrice - actualBasePrice - priceCost)
|
||||
|
||||
// 创建代理订单记录
|
||||
agentOrder := model.AgentOrder{
|
||||
|
||||
@@ -107,6 +107,12 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st
|
||||
return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo)
|
||||
}
|
||||
|
||||
if status == refunddomestic.STATUS_SUCCESS {
|
||||
if err := l.svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(ctx, session, order.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
51
app/main/api/internal/logic/productcost/sync.go
Normal file
51
app/main/api/internal/logic/productcost/sync.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package productcost
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SyncProductCostFromModules 将产品 cost_price 更新为当前关联模块的 feature.cost_price 之和
|
||||
func SyncProductCostFromModules(ctx context.Context, c *svc.ServiceContext, productId string) error {
|
||||
sum, err := c.ProductFeatureModel.SumFeatureCostPriceByProductId(ctx, productId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"汇总产品模块成本失败, err: %v, productId: %s", err, productId)
|
||||
}
|
||||
record, err := c.ProductModel.FindOne(ctx, productId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"查找产品失败, err: %v, productId: %s", err, productId)
|
||||
}
|
||||
record.CostPrice = sum
|
||||
if _, err = c.ProductModel.Update(ctx, nil, record); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"同步产品成本失败, err: %v, productId: %s", err, productId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncAllProductsUsingFeature 在模块成本等变更后,刷新所有关联了该模块的产品的 cost_price
|
||||
func SyncAllProductsUsingFeature(ctx context.Context, c *svc.ServiceContext, featureId string) error {
|
||||
builder := c.ProductFeatureModel.SelectBuilder().Where("feature_id = ?", featureId)
|
||||
list, err := c.ProductFeatureModel.FindAll(ctx, builder, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||
"查询产品模块关联失败, err: %v, featureId: %s", err, featureId)
|
||||
}
|
||||
seen := make(map[string]struct{})
|
||||
for _, pf := range list {
|
||||
if _, ok := seen[pf.ProductId]; ok {
|
||||
continue
|
||||
}
|
||||
seen[pf.ProductId] = struct{}{}
|
||||
if err := SyncProductCostFromModules(ctx, c, pf.ProductId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
}
|
||||
}
|
||||
|
||||
// 通过加密后的手机号查找目标用户(手机号用户视为正式用户)
|
||||
// 通过加密后的手机号查找目标用户(手机号用户视为正式用户;可能命中 go-zero 二级缓存)
|
||||
targetUser, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找用户失败: %v", err)
|
||||
@@ -90,6 +90,13 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
if _, err := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{Id: uuid.NewString(), UserId: finalUserID, AuthType: model.UserAuthTypeMobile, AuthKey: encryptedMobile}); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
||||
}
|
||||
// 与 userModel_gen 中 FindOneByMobile 的 QueryRowIndexCtx key 规则一致,避免此前「未命中」缓存导致后续按手机号仍查不到
|
||||
userMobileCacheKey := fmt.Sprintf("cache:qnc:user:mobile:%v", sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if _, delErr := l.svcCtx.Redis.DelCtx(l.ctx, userMobileCacheKey); delErr != nil {
|
||||
l.Errorf("[BindMobile] 失效 FindOneByMobile 缓存失败 | key: %s | err: %v", userMobileCacheKey, delErr)
|
||||
} else {
|
||||
l.Infof("[BindMobile] 已失效 FindOneByMobile 缓存 | key: %s", userMobileCacheKey)
|
||||
}
|
||||
// 发放token(userType会根据mobile字段动态计算)
|
||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
||||
if err != nil {
|
||||
|
||||
@@ -56,7 +56,7 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
|
||||
var userID string
|
||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findUserErr)
|
||||
}
|
||||
if user == nil {
|
||||
// 用户不存在,自动注册新用户
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type PaySuccessNotifyUserHandler struct {
|
||||
@@ -233,12 +234,16 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
|
||||
}
|
||||
if refund.IsSuccess() {
|
||||
logx.Errorf("支付宝退款成功, orderID: %s", order.Id)
|
||||
// 更新订单状态为退款
|
||||
order.Status = "refunded"
|
||||
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
|
||||
if updateOrderErr != nil {
|
||||
logx.Errorf("更新订单状态失败,订单ID: %s, 错误: %v", order.Id, updateOrderErr)
|
||||
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
|
||||
transErr := l.svcCtx.OrderModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||
order.Status = "refunded"
|
||||
if err := l.svcCtx.OrderModel.UpdateWithVersion(transCtx, session, order); err != nil {
|
||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||
}
|
||||
return l.svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(transCtx, session, order.Id)
|
||||
})
|
||||
if transErr != nil {
|
||||
logx.Errorf("支付宝退款后更新订单或冲正代理分账失败, orderID: %s, err: %v", order.Id, transErr)
|
||||
return fmt.Errorf("更新订单状态或冲正代理分账失败: %v", transErr)
|
||||
}
|
||||
return asynq.SkipRetry
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"qnc-server/common/globalkey"
|
||||
"qnc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -120,7 +121,7 @@ func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) err
|
||||
|
||||
// 5.2 使用产品配置的底价计算实际底价
|
||||
basePrice := productConfig.BasePrice
|
||||
actualBasePrice := basePrice + float64(levelBonus)
|
||||
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||
|
||||
// 5.3 计算提价成本(使用产品配置)
|
||||
priceThreshold := 0.0
|
||||
@@ -134,7 +135,7 @@ func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) err
|
||||
priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
|
||||
|
||||
// 5.4 计算代理收益
|
||||
agentProfit := agentOrder.SetPrice - actualBasePrice - priceCost
|
||||
agentProfit := lzUtils.RoundMoney(agentOrder.SetPrice - actualBasePrice - priceCost)
|
||||
|
||||
// 5.5 更新代理订单记录
|
||||
agentOrder.ProcessStatus = 1
|
||||
@@ -200,7 +201,7 @@ func (s *AgentService) calculatePriceCost(setPrice, priceThreshold, priceFeeRate
|
||||
if setPrice <= priceThreshold {
|
||||
return 0
|
||||
}
|
||||
return (setPrice - priceThreshold) * priceFeeRate
|
||||
return lzUtils.RoundMoney((setPrice - priceThreshold) * priceFeeRate)
|
||||
}
|
||||
|
||||
// giveAgentCommission 发放代理佣金
|
||||
@@ -244,7 +245,7 @@ func (s *AgentService) giveAgentCommission(ctx context.Context, session sqlx.Ses
|
||||
}
|
||||
|
||||
// 计算冻结金额:订单单价的10%
|
||||
freezeAmountByPrice := orderPrice * freezeRatio
|
||||
freezeAmountByPrice := lzUtils.RoundMoney(orderPrice * freezeRatio)
|
||||
|
||||
// 冻结金额不能超过佣金金额
|
||||
if freezeAmountByPrice > amount {
|
||||
@@ -295,7 +296,7 @@ func (s *AgentService) giveAgentCommission(ctx context.Context, session sqlx.Ses
|
||||
}
|
||||
|
||||
// 实际到账金额 = 佣金金额 - 冻结金额
|
||||
actualAmount := amount - freezeAmount
|
||||
actualAmount := lzUtils.RoundMoney(amount - freezeAmount)
|
||||
|
||||
wallet.Balance += actualAmount
|
||||
wallet.FrozenBalance += freezeAmount
|
||||
@@ -431,7 +432,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
||||
// ========== 步骤2:计算剩余金额并分配给钻石上级 ==========
|
||||
// 剩余金额 = 总等级加成 - 已给黄金上级的金额
|
||||
// 例如:等级加成6元 - 给黄金上级3元 = 剩余3元
|
||||
remaining := amount - goldAmount
|
||||
remaining := lzUtils.RoundMoney(amount - goldAmount)
|
||||
if remaining > 0 {
|
||||
// 从黄金上级开始向上查找钻石上级
|
||||
// 场景示例:
|
||||
@@ -481,7 +482,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
||||
// ========== 步骤2:计算剩余金额 ==========
|
||||
// 剩余金额 = 总等级加成 - 已给直接上级的金额
|
||||
// 例如:等级加成6元 - 给直接上级2元 = 剩余4元
|
||||
remaining := amount - directAmount
|
||||
remaining := lzUtils.RoundMoney(amount - directAmount)
|
||||
if remaining <= 0 {
|
||||
// 如果没有剩余,直接返回(所有金额已分配给直接上级)
|
||||
return nil
|
||||
@@ -524,8 +525,8 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
||||
logx.Errorf("获取黄金等级加成配置失败,使用默认值3元: %v", err)
|
||||
goldBonus = 3 // 默认3元
|
||||
}
|
||||
// 计算等级加成的差
|
||||
bonusDiff := normalBonus - float64(goldBonus) // 例如:6元 - 3元 = 3元
|
||||
// 计算等级加成的差
|
||||
bonusDiff := lzUtils.RoundMoney(normalBonus - float64(goldBonus)) // 例如:6元 - 3元 = 3元
|
||||
|
||||
// 步骤A2:如果有黄金上级,给黄金上级一部分返佣
|
||||
// 规则说明:
|
||||
@@ -536,7 +537,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
||||
if goldParent != nil && bonusDiff > 0 {
|
||||
// 计算给黄金上级的金额 = 等级加成差 - 已给普通上级的金额
|
||||
// 例如:等级加成差3元 - 已给普通2元 = 给黄金1元
|
||||
goldRebateAmount := bonusDiff - directAmount
|
||||
goldRebateAmount := lzUtils.RoundMoney(bonusDiff - directAmount)
|
||||
|
||||
// 如果计算出的金额小于等于0,说明差已经被普通代理全部占用了,不给黄金
|
||||
// 例如:如果差是2元,已给普通2元,则 goldRebateAmount = 0,不给黄金
|
||||
@@ -554,7 +555,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
||||
|
||||
// 更新剩余金额(用于后续分配给钻石)
|
||||
// 例如:剩余4元 - 给黄金1元 = 剩余3元(给钻石)
|
||||
remaining = remaining - goldRebateAmount
|
||||
remaining = lzUtils.RoundMoney(remaining - goldRebateAmount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,3 +1074,150 @@ func (s *AgentService) GetUpgradeRebate(ctx context.Context, fromLevel, toLevel
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// ReverseAgentSettlementOnOrderRefund 订单退款成功后冲正本单代理销售佣金、冻结任务与返佣(幂等)。
|
||||
// 与退款是否部分/全额无关:凡触发本方法,均按本单佣金与返佣记录全额冲回。
|
||||
// session 非 nil 时与外层共用事务;为 nil 时在独立事务中执行。
|
||||
func (s *AgentService) ReverseAgentSettlementOnOrderRefund(ctx context.Context, session sqlx.Session, orderID string) error {
|
||||
run := func(c context.Context, sess sqlx.Session) error {
|
||||
return s.reverseAgentSettlementInTx(c, sess, orderID)
|
||||
}
|
||||
if session != nil {
|
||||
return run(ctx, session)
|
||||
}
|
||||
return s.AgentWalletModel.Trans(ctx, run)
|
||||
}
|
||||
|
||||
func (s *AgentService) reverseAgentSettlementInTx(ctx context.Context, session sqlx.Session, orderID string) error {
|
||||
cb := s.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"order_id": orderID,
|
||||
"del_state": globalkey.DelStateNo,
|
||||
})
|
||||
commissions, err := s.AgentCommissionModel.FindAll(ctx, cb, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "查询订单佣金失败 orderId=%s", orderID)
|
||||
}
|
||||
for _, c := range commissions {
|
||||
if err := s.reverseOneCommissionForRefund(ctx, session, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rb := s.AgentRebateModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"order_id": orderID,
|
||||
"del_state": globalkey.DelStateNo,
|
||||
})
|
||||
rebates, err := s.AgentRebateModel.FindAll(ctx, rb, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "查询订单返佣失败 orderId=%s", orderID)
|
||||
}
|
||||
for _, r := range rebates {
|
||||
if err := s.reverseOneRebateForRefund(ctx, session, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AgentService) reverseOneCommissionForRefund(ctx context.Context, session sqlx.Session, commission *model.AgentCommission) error {
|
||||
if commission.Status == 3 {
|
||||
return nil
|
||||
}
|
||||
A := lzUtils.RoundMoney(commission.Amount)
|
||||
if A <= 0 {
|
||||
commission.Status = 3
|
||||
return s.AgentCommissionModel.UpdateWithVersion(ctx, session, commission)
|
||||
}
|
||||
|
||||
tb := s.AgentFreezeTaskModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"commission_id": commission.Id,
|
||||
"del_state": globalkey.DelStateNo,
|
||||
})
|
||||
tasks, err := s.AgentFreezeTaskModel.FindAll(ctx, tb, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "查询佣金冻结任务失败 commissionId=%s", commission.Id)
|
||||
}
|
||||
|
||||
var takeFromFrozen float64
|
||||
for _, ft := range tasks {
|
||||
if ft.Status == 1 {
|
||||
takeFromFrozen = lzUtils.RoundMoney(takeFromFrozen + ft.FreezeAmount)
|
||||
ft.Status = 3
|
||||
if err := s.AgentFreezeTaskModel.UpdateWithVersion(ctx, session, ft); err != nil {
|
||||
return errors.Wrapf(err, "取消冻结任务失败 freezeTaskId=%s", ft.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
takeFromFrozen = lzUtils.RoundMoney(takeFromFrozen)
|
||||
|
||||
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "冲正佣金时查询钱包失败 agentId=%s", commission.AgentId)
|
||||
}
|
||||
|
||||
if takeFromFrozen > 0 {
|
||||
if wallet.FrozenBalance < takeFromFrozen {
|
||||
takeFromFrozen = lzUtils.RoundMoney(wallet.FrozenBalance)
|
||||
}
|
||||
wallet.FrozenBalance = lzUtils.RoundMoney(wallet.FrozenBalance - takeFromFrozen)
|
||||
}
|
||||
|
||||
takeFromBalance := lzUtils.RoundMoney(A - takeFromFrozen)
|
||||
if takeFromBalance < 0 {
|
||||
takeFromBalance = 0
|
||||
}
|
||||
// 可用余额不足时允许扣成负数(例如代理已提现),退款侧事务仍可完成
|
||||
wallet.Balance = lzUtils.RoundMoney(wallet.Balance - takeFromBalance)
|
||||
if wallet.Balance < 0 {
|
||||
logx.Infof("退款冲正佣金后代理可用余额为负: agentId=%s, orderId=%s, balance=%.2f",
|
||||
commission.AgentId, commission.OrderId, wallet.Balance)
|
||||
}
|
||||
wallet.TotalEarnings = lzUtils.RoundMoney(wallet.TotalEarnings - A)
|
||||
if wallet.TotalEarnings < 0 {
|
||||
wallet.TotalEarnings = 0
|
||||
}
|
||||
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||
return errors.Wrapf(err, "冲正佣金更新钱包失败 agentId=%s", commission.AgentId)
|
||||
}
|
||||
|
||||
commission.Status = 3
|
||||
if err := s.AgentCommissionModel.UpdateWithVersion(ctx, session, commission); err != nil {
|
||||
return errors.Wrapf(err, "更新佣金状态为已取消失败 commissionId=%s", commission.Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AgentService) reverseOneRebateForRefund(ctx context.Context, session sqlx.Session, rebate *model.AgentRebate) error {
|
||||
if rebate.Status == 3 {
|
||||
return nil
|
||||
}
|
||||
amt := lzUtils.RoundMoney(rebate.RebateAmount)
|
||||
if amt <= 0 {
|
||||
rebate.Status = 3
|
||||
return s.AgentRebateModel.UpdateWithVersion(ctx, session, rebate)
|
||||
}
|
||||
|
||||
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, rebate.AgentId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "冲正返佣时查询钱包失败 agentId=%s", rebate.AgentId)
|
||||
}
|
||||
// 可用余额不足时允许扣成负数(例如代理已提现)
|
||||
wallet.Balance = lzUtils.RoundMoney(wallet.Balance - amt)
|
||||
if wallet.Balance < 0 {
|
||||
logx.Infof("退款冲正返佣后代理可用余额为负: agentId=%s, orderId=%s, balance=%.2f",
|
||||
rebate.AgentId, rebate.OrderId, wallet.Balance)
|
||||
}
|
||||
wallet.TotalEarnings = lzUtils.RoundMoney(wallet.TotalEarnings - amt)
|
||||
if wallet.TotalEarnings < 0 {
|
||||
wallet.TotalEarnings = 0
|
||||
}
|
||||
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||
return errors.Wrapf(err, "冲正返佣更新钱包失败 agentId=%s", rebate.AgentId)
|
||||
}
|
||||
|
||||
rebate.Status = 3
|
||||
if err := s.AgentRebateModel.UpdateWithVersion(ctx, session, rebate); err != nil {
|
||||
return errors.Wrapf(err, "更新返佣状态为已取消失败 rebateId=%s", rebate.Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ type AdminCreateProductReq struct {
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes,optional"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
}
|
||||
|
||||
@@ -232,11 +231,12 @@ type AdminGenerateDiamondInviteCodeResp struct {
|
||||
}
|
||||
|
||||
type AdminGetAgentCommissionListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
}
|
||||
|
||||
type AdminGetAgentCommissionListResp struct {
|
||||
@@ -261,6 +261,7 @@ type AdminGetAgentLinkListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
ProductId *string `form:"product_id,optional"` // 产品ID(可选)
|
||||
LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选)
|
||||
}
|
||||
@@ -274,9 +275,12 @@ type AdminGetAgentListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
Mobile *string `form:"mobile,optional"` // 手机号(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编码(可选)
|
||||
Region *string `form:"region,optional"` // 区域(可选)
|
||||
Level *int64 `form:"level,optional"` // 等级(可选)
|
||||
TeamLeaderId *string `form:"team_leader_id,optional"` // 团队首领ID(可选)
|
||||
AgentId *string `form:"agent_id,optional"` // 代理主键UUID,精确查询;传入时不再按 team_leader 与其它条件组合筛选
|
||||
RealName *string `form:"real_name,optional"` // 实名姓名(可选,模糊匹配已三要素核验记录)
|
||||
}
|
||||
|
||||
type AdminGetAgentListResp struct {
|
||||
@@ -288,7 +292,9 @@ type AdminGetAgentOrderListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选)
|
||||
OrderStatus *string `form:"order_status,optional"` // 订单状态(可选):pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||
}
|
||||
@@ -298,6 +304,15 @@ type AdminGetAgentOrderListResp struct {
|
||||
Items []AgentOrderListItem `json:"items"` // 列表数据
|
||||
}
|
||||
|
||||
type AdminGetAgentOrderSettlementReq struct {
|
||||
OrderNo string `form:"order_no"` // 商户订单号
|
||||
}
|
||||
|
||||
type AdminGetAgentOrderSettlementResp struct {
|
||||
Commissions []AgentCommissionListItem `json:"commissions"` // 本单销售佣金
|
||||
Rebates []AgentRebateListItem `json:"rebates"` // 本单返佣
|
||||
}
|
||||
|
||||
type AdminGetAgentProductConfigListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
@@ -323,12 +338,14 @@ type AdminGetAgentRealNameListResp struct {
|
||||
}
|
||||
|
||||
type AdminGetAgentRebateListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
SourceAgentId *string `form:"source_agent_id,optional"` // 来源代理ID(可选)
|
||||
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
SourceAgentCode *string `form:"source_agent_code,optional"` // 来源代理编号(可选)
|
||||
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
||||
}
|
||||
|
||||
type AdminGetAgentRebateListResp struct {
|
||||
@@ -336,10 +353,19 @@ type AdminGetAgentRebateListResp struct {
|
||||
Items []AgentRebateListItem `json:"items"` // 列表数据
|
||||
}
|
||||
|
||||
type AdminGetAgentTeamTreeReq struct {
|
||||
AgentId string `form:"agent_id"` // 锚点代理ID(UUID)
|
||||
}
|
||||
|
||||
type AdminGetAgentTeamTreeResp struct {
|
||||
Root AgentTeamTreeNode `json:"root"`
|
||||
}
|
||||
|
||||
type AdminGetAgentUpgradeListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
UpgradeType *int64 `form:"upgrade_type,optional"` // 升级类型(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
}
|
||||
@@ -350,11 +376,15 @@ type AdminGetAgentUpgradeListResp struct {
|
||||
}
|
||||
|
||||
type AdminGetAgentWithdrawalListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选)
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
AgentId *string `form:"agent_id,optional"` // 代理主键 UUID(弹窗内按代理筛选时传入)
|
||||
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选,模糊匹配)
|
||||
Status string `form:"status,optional"` // 状态(可选):1=待审核,2=已通过,3=已拒绝,5=已打款
|
||||
WithdrawalType string `form:"withdrawal_type,optional"` // 提现方式(可选):1=支付宝,2=银行卡
|
||||
PayeeAccount *string `form:"payee_account,optional"` // 收款账户(可选,模糊)
|
||||
PayeeName *string `form:"payee_name,optional"` // 收款人(可选,模糊)
|
||||
}
|
||||
|
||||
type AdminGetAgentWithdrawalListResp struct {
|
||||
@@ -442,10 +472,9 @@ type AdminGetFeatureListResp struct {
|
||||
type AdminGetInviteCodeListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"pageSize"` // 每页数量
|
||||
Code *string `form:"code,optional"` // 邀请码(可选)
|
||||
AgentId *string `form:"agent_id,optional"` // 发放代理ID(可选,NULL表示平台发放)
|
||||
Code *string `form:"code,optional"` // 邀请码(可选,模糊匹配)
|
||||
TargetLevel *int64 `form:"target_level,optional"` // 目标等级(可选)
|
||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||
Status string `form:"status,optional"` // 状态(可选):0=未使用,1=已使用,2=已失效
|
||||
}
|
||||
|
||||
type AdminGetInviteCodeListResp struct {
|
||||
@@ -518,6 +547,8 @@ type AdminGetOrderListReq struct {
|
||||
PaymentScene string `form:"payment_scene,optional"` // 支付平台
|
||||
Amount float64 `form:"amount,optional"` // 金额
|
||||
Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||
IsAgentOrder *bool `form:"is_agent_order,optional"` // 是否代理订单
|
||||
AgentCode string `form:"agent_code,optional"` // 代理编号
|
||||
CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始
|
||||
CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束
|
||||
PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始
|
||||
@@ -548,6 +579,7 @@ type AdminGetPlatformUserDetailResp struct {
|
||||
type AdminGetPlatformUserListReq struct {
|
||||
Page int64 `form:"page,default=1"` // 页码
|
||||
PageSize int64 `form:"pageSize,default=20"` // 每页数量
|
||||
UserId string `form:"user_id,optional"` // 平台用户主键 ID,精确筛选
|
||||
Mobile string `form:"mobile,optional"` // 手机号
|
||||
Nickname string `form:"nickname,optional"` // 昵称
|
||||
Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否
|
||||
@@ -572,7 +604,7 @@ type AdminGetProductDetailResp struct {
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
@@ -583,16 +615,17 @@ type AdminGetProductFeatureListReq struct {
|
||||
}
|
||||
|
||||
type AdminGetProductFeatureListResp struct {
|
||||
Id string `json:"id"` // 关联ID
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
FeatureId string `json:"feature_id"` // 功能ID
|
||||
ApiId string `json:"api_id"` // API标识
|
||||
Name string `json:"name"` // 功能描述
|
||||
Sort int64 `json:"sort"` // 排序
|
||||
Enable int64 `json:"enable"` // 是否启用
|
||||
IsImportant int64 `json:"is_important"` // 是否重要
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
Id string `json:"id"` // 关联ID
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
FeatureId string `json:"feature_id"` // 功能ID
|
||||
ApiId string `json:"api_id"` // API标识
|
||||
Name string `json:"name"` // 功能描述
|
||||
CostPrice float64 `json:"cost_price"` // 模块成本价(元,来自 feature)
|
||||
Sort int64 `json:"sort"` // 排序
|
||||
Enable int64 `json:"enable"` // 是否启用
|
||||
IsImportant int64 `json:"is_important"` // 是否重要
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
}
|
||||
|
||||
type AdminGetProductListReq struct {
|
||||
@@ -920,7 +953,6 @@ type AdminUpdateProductReq struct {
|
||||
ProductEn *string `json:"product_en,optional"` // 英文名
|
||||
Description *string `json:"description,optional"` // 描述
|
||||
Notes *string `json:"notes,optional"` // 备注
|
||||
CostPrice *float64 `json:"cost_price,optional"` // 成本
|
||||
SellPrice *float64 `json:"sell_price,optional"` // 售价
|
||||
}
|
||||
|
||||
@@ -1005,7 +1037,9 @@ type AgentApplyResp struct {
|
||||
type AgentCommissionListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
Status int64 `json:"status"` // 状态
|
||||
@@ -1013,9 +1047,9 @@ type AgentCommissionListItem struct {
|
||||
}
|
||||
|
||||
type AgentGeneratingLinkReq struct {
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
SetPrice float64 `json:"set_price"` // 设定价格
|
||||
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
SetPrice string `json:"set_price"` // 设定价格
|
||||
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
||||
}
|
||||
|
||||
type AgentGeneratingLinkResp struct {
|
||||
@@ -1038,6 +1072,7 @@ type AgentInfoResp struct {
|
||||
type AgentLinkListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
SetPrice float64 `json:"set_price"` // 设定价格
|
||||
@@ -1047,29 +1082,32 @@ type AgentLinkListItem struct {
|
||||
}
|
||||
|
||||
type AgentListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
UserId string `json:"user_id"` // 用户ID
|
||||
Level int64 `json:"level"` // 等级:1=普通,2=黄金,3=钻石
|
||||
LevelName string `json:"level_name"` // 等级名称
|
||||
Region string `json:"region"` // 区域
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
RealName string `json:"real_name"` // 姓名(来自实名表)
|
||||
IdCard string `json:"id_card"` // 身份证(解密后明文返回)
|
||||
WechatId string `json:"wechat_id"` // 微信号
|
||||
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
||||
AgentCode int64 `json:"agent_code"`
|
||||
Balance float64 `json:"balance"` // 钱包余额
|
||||
TotalEarnings float64 `json:"total_earnings"` // 累计收益
|
||||
FrozenBalance float64 `json:"frozen_balance"` // 冻结余额
|
||||
WithdrawnAmount float64 `json:"withdrawn_amount"` // 提现总额
|
||||
IsRealName bool `json:"is_real_name"` // 是否已实名
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
Id string `json:"id"` // 主键
|
||||
UserId string `json:"user_id"` // 用户ID
|
||||
Level int64 `json:"level"` // 等级:1=普通,2=黄金,3=钻石
|
||||
LevelName string `json:"level_name"` // 等级名称
|
||||
Region string `json:"region"` // 区域
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
RealName string `json:"real_name"` // 姓名(来自实名表)
|
||||
IdCard string `json:"id_card"` // 身份证(解密后明文返回)
|
||||
WechatId string `json:"wechat_id"` // 微信号
|
||||
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
||||
SubordinateCount int64 `json:"subordinate_count"` // 直属下级代理数(team_leader_id 为本代理 id)
|
||||
AgentCode int64 `json:"agent_code"`
|
||||
Balance float64 `json:"balance"` // 钱包余额
|
||||
TotalEarnings float64 `json:"total_earnings"` // 累计收益
|
||||
FrozenBalance float64 `json:"frozen_balance"` // 冻结余额
|
||||
WithdrawnAmount float64 `json:"withdrawn_amount"` // 提现总额
|
||||
IsRealName bool `json:"is_real_name"` // 是否已实名
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
}
|
||||
|
||||
type AgentOrderListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
ProductId string `json:"product_id"` // 产品ID
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
OrderAmount float64 `json:"order_amount"` // 订单金额
|
||||
@@ -1101,6 +1139,7 @@ type AgentProductConfigResp struct {
|
||||
type AgentRealNameListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
Name string `json:"name"` // 姓名
|
||||
IdCard string `json:"id_card"` // 身份证号
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
@@ -1110,19 +1149,33 @@ type AgentRealNameListItem struct {
|
||||
}
|
||||
|
||||
type AgentRebateListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
||||
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
RebateType int64 `json:"rebate_type"` // 返佣类型
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 获得返佣代理编号
|
||||
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
||||
SourceAgentCode int64 `json:"source_agent_code"` // 来源代理编号
|
||||
OrderId string `json:"order_id"` // 订单ID
|
||||
OrderNo string `json:"order_no"` // 商户订单号
|
||||
RebateType int64 `json:"rebate_type"` // 返佣类型
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
}
|
||||
|
||||
type AgentTeamTreeNode struct {
|
||||
Id string `json:"id"`
|
||||
AgentCode int64 `json:"agent_code"`
|
||||
Level int64 `json:"level"`
|
||||
LevelName string `json:"level_name"`
|
||||
Mobile string `json:"mobile"`
|
||||
IsAnchor bool `json:"is_anchor"`
|
||||
Children []AgentTeamTreeNode `json:"children"`
|
||||
}
|
||||
|
||||
type AgentUpgradeListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
FromLevel int64 `json:"from_level"` // 原等级
|
||||
ToLevel int64 `json:"to_level"` // 目标等级
|
||||
UpgradeType int64 `json:"upgrade_type"` // 升级类型
|
||||
@@ -1135,6 +1188,7 @@ type AgentUpgradeListItem struct {
|
||||
type AgentWithdrawalListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
AgentId string `json:"agent_id"` // 代理ID
|
||||
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
||||
Amount float64 `json:"amount"` // 金额
|
||||
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
||||
@@ -1706,18 +1760,20 @@ type InviteCodeItem struct {
|
||||
}
|
||||
|
||||
type InviteCodeListItem struct {
|
||||
Id string `json:"id"` // 主键
|
||||
Code string `json:"code"` // 邀请码
|
||||
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
||||
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
||||
TargetLevel int64 `json:"target_level"` // 目标等级
|
||||
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
||||
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
||||
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
||||
UsedTime string `json:"used_time"` // 使用时间
|
||||
ExpireTime string `json:"expire_time"` // 过期时间
|
||||
Remark string `json:"remark"` // 备注
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
Id string `json:"id"` // 主键
|
||||
Code string `json:"code"` // 邀请码
|
||||
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
||||
AgentCode int64 `json:"agent_code"` // 发放代理编号(平台为0)
|
||||
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
||||
TargetLevel int64 `json:"target_level"` // 目标等级
|
||||
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
||||
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
||||
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
||||
UsedAgentCode int64 `json:"used_agent_code"` // 使用代理编号
|
||||
UsedTime string `json:"used_time"` // 使用时间
|
||||
ExpireTime string `json:"expire_time"` // 过期时间
|
||||
Remark string `json:"remark"` // 备注
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
}
|
||||
|
||||
type InviteItem struct {
|
||||
@@ -1841,6 +1897,7 @@ type OrderListItem struct {
|
||||
RefundTime string `json:"refund_time"` // 退款时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
|
||||
AgentCode string `json:"agent_code"` // 代理编号
|
||||
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理
|
||||
}
|
||||
|
||||
@@ -1924,7 +1981,7 @@ type ProductListItem struct {
|
||||
ProductEn string `json:"product_en"` // 英文名
|
||||
Description string `json:"description"` // 描述
|
||||
Notes string `json:"notes"` // 备注
|
||||
CostPrice float64 `json:"cost_price"` // 成本
|
||||
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||
SellPrice float64 `json:"sell_price"` // 售价
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
@@ -18,6 +21,8 @@ type (
|
||||
agentModel
|
||||
// UpdateInTransaction 在事务中更新刚插入的记录,避免 UpdateWithVersion 中的 FindOne 缓存问题
|
||||
UpdateInTransaction(ctx context.Context, session sqlx.Session, data *Agent) error
|
||||
// CountSubordinatesByTeamLeaderIds 按团队首领 id 统计未删除的直属代理数量(team_leader_id = 传入 id)
|
||||
CountSubordinatesByTeamLeaderIds(ctx context.Context, leaderIds []string) (map[string]int64, error)
|
||||
}
|
||||
|
||||
customAgentModel struct {
|
||||
@@ -63,3 +68,36 @@ func (m *customAgentModel) UpdateInTransaction(ctx context.Context, session sqlx
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type teamLeaderSubCountRow struct {
|
||||
TeamLeaderId string `db:"team_leader_id"`
|
||||
Cnt int64 `db:"cnt"`
|
||||
}
|
||||
|
||||
func (m *customAgentModel) CountSubordinatesByTeamLeaderIds(ctx context.Context, leaderIds []string) (map[string]int64, error) {
|
||||
out := make(map[string]int64)
|
||||
if len(leaderIds) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
// 不含「自己挂在自己名下」的记录,否则首领会在自己的下级列表里且下级数量虚高
|
||||
qb := squirrel.Select("team_leader_id", "COUNT(*) AS cnt").
|
||||
From(m.table).
|
||||
Where(squirrel.Eq{"del_state": globalkey.DelStateNo}).
|
||||
Where(squirrel.Eq{"team_leader_id": leaderIds}).
|
||||
Where("`id` <> `team_leader_id`").
|
||||
GroupBy("team_leader_id")
|
||||
query, args, err := qb.ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rows []teamLeaderSubCountRow
|
||||
if err := m.QueryRowsNoCacheCtx(ctx, &rows, query, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range rows {
|
||||
if r.TeamLeaderId != "" {
|
||||
out[r.TeamLeaderId] = r.Cnt
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"qnc-server/common/globalkey"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
@@ -12,6 +18,10 @@ type (
|
||||
// and implement the added methods in customProductFeatureModel.
|
||||
ProductFeatureModel interface {
|
||||
productFeatureModel
|
||||
// SumFeatureCostPriceByProductId 汇总该产品下已关联且未删除的模块成本(feature.cost_price 之和)
|
||||
SumFeatureCostPriceByProductId(ctx context.Context, productId string) (float64, error)
|
||||
// SumFeatureCostPriceByProductIds 批量汇总,未出现在结果中的产品视为 0
|
||||
SumFeatureCostPriceByProductIds(ctx context.Context, productIds []string) (map[string]float64, error)
|
||||
}
|
||||
|
||||
customProductFeatureModel struct {
|
||||
@@ -25,3 +35,48 @@ func NewProductFeatureModel(conn sqlx.SqlConn, c cache.CacheConf) ProductFeature
|
||||
defaultProductFeatureModel: newProductFeatureModel(conn, c),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *customProductFeatureModel) SumFeatureCostPriceByProductId(ctx context.Context, productId string) (float64, error) {
|
||||
query := `
|
||||
SELECT COALESCE(SUM(f.cost_price), 0)
|
||||
FROM product_feature pf
|
||||
INNER JOIN feature f ON f.id = pf.feature_id AND f.del_state = ?
|
||||
WHERE pf.product_id = ? AND pf.del_state = ?`
|
||||
var sum float64
|
||||
err := m.QueryRowNoCacheCtx(ctx, &sum, query, globalkey.DelStateNo, productId, globalkey.DelStateNo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func (m *customProductFeatureModel) SumFeatureCostPriceByProductIds(ctx context.Context, productIds []string) (map[string]float64, error) {
|
||||
out := make(map[string]float64)
|
||||
if len(productIds) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(productIds)), ",")
|
||||
query := fmt.Sprintf(`
|
||||
SELECT pf.product_id, COALESCE(SUM(f.cost_price), 0) AS total
|
||||
FROM product_feature pf
|
||||
INNER JOIN feature f ON f.id = pf.feature_id AND f.del_state = ?
|
||||
WHERE pf.del_state = ? AND pf.product_id IN (%s)
|
||||
GROUP BY pf.product_id`, placeholders)
|
||||
args := make([]interface{}, 0, 2+len(productIds))
|
||||
args = append(args, globalkey.DelStateNo, globalkey.DelStateNo)
|
||||
for _, id := range productIds {
|
||||
args = append(args, id)
|
||||
}
|
||||
var rows []struct {
|
||||
ProductId string `db:"product_id"`
|
||||
Total float64 `db:"total"`
|
||||
}
|
||||
err := m.QueryRowsNoCacheCtx(ctx, &rows, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range rows {
|
||||
out[rows[i].ProductId] = rows[i].Total
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -218,8 +218,11 @@ CREATE TABLE `agent_upgrade` (
|
||||
CREATE TABLE `agent_withdrawal` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`agent_id` bigint NOT NULL COMMENT '代理ID',
|
||||
`withdrawal_type` tinyint NOT NULL DEFAULT 1 COMMENT '提现方式:1=支付宝,2=银行卡',
|
||||
`withdraw_no` varchar(100) NOT NULL COMMENT '提现单号',
|
||||
`payee_account` varchar(100) NOT NULL COMMENT '收款账户',
|
||||
`bank_card_no` varchar(100) DEFAULT NULL COMMENT '银行卡号(银行卡提现时使用)',
|
||||
`bank_name` varchar(100) DEFAULT NULL COMMENT '开户行名称(银行卡提现时使用)',
|
||||
`payee_name` varchar(50) NOT NULL COMMENT '收款人姓名',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '提现金额',
|
||||
`actual_amount` decimal(10,2) NOT NULL COMMENT '实际到账金额(扣除税费后)',
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
package lzUtils
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// RoundMoney 将金额四舍五入到指定小数位数(默认2位)
|
||||
// 所有金额计算结果都应通过此函数处理,避免浮点精度问题
|
||||
func RoundMoney(v float64, precision ...int) float64 {
|
||||
p := 2
|
||||
if len(precision) > 0 {
|
||||
p = precision[0]
|
||||
}
|
||||
factor := math.Pow10(p)
|
||||
return math.Round(v*factor) / factor
|
||||
}
|
||||
|
||||
// RoundMoney2 将金额四舍五入到2位小数并返回字符串
|
||||
// 用于需要字符串格式的场景(如 API 返回、日志打印)
|
||||
func RoundMoney2(v float64) string {
|
||||
return fmt.Sprintf("%.2f", RoundMoney(v))
|
||||
}
|
||||
|
||||
// ToWechatAmount 将金额从元转换为微信支付 SDK 需要的分(int64 类型)
|
||||
func ToWechatAmount(amount float64) int64 {
|
||||
// 将金额从元转换为分,并四舍五入
|
||||
return int64(amount*100 + 0.5)
|
||||
return int64(RoundMoney(amount)*100 + 0.5)
|
||||
}
|
||||
|
||||
// ToAlipayAmount 将金额从元转换为支付宝支付 SDK 需要的字符串格式,保留两位小数
|
||||
func ToAlipayAmount(amount float64) string {
|
||||
// 格式化为字符串,保留两位小数
|
||||
return fmt.Sprintf("%.2f", amount)
|
||||
return RoundMoney2(amount)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user