This commit is contained in:
2026-05-13 14:43:10 +08:00
parent 2312f54e1e
commit 3399de0dc5
49 changed files with 1637 additions and 287 deletions

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"),
})
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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"),
})
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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"

View File

@@ -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
})
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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"),

View File

@@ -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,

View File

@@ -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"),

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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{

View File

@@ -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
})

View 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
}

View File

@@ -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)
}
// 发放tokenuserType会根据mobile字段动态计算
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
if err != nil {

View File

@@ -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 {
// 用户不存在,自动注册新用户