Files
stat_center/service/channelSer/settlement.go
T
rootandClaude Opus 5 4cdca06289 修复子渠道汇总到父渠道时未区分 appId 导致数据多加
ChannelStat 按 (channel, sumDate, appId) 三维存储,但子渠道累加到父渠道的
匹配条件只比了 ParentChannel 和 SumDate,漏了 AppId。

当父渠道在多个 app 下都有记录时(如 appId=[2,3,4] 查同一渠道码),一个子渠道
的数据会被同时累加进该 (channel, sumDate) 下所有 app 的父渠道行,选几个 app
就多加几倍。settlement 的三处子查询本身也没有 appId 约束,父列表虽锁定单个
appId,子列表却跨 app,同样会把其他 app 的子渠道数据算进结算金额。

5 处匹配条件统一补上 child.AppId == parent.AppId:
- stat.go:162          SearchChannelStat         (列表接口,多值 appId)
- settlement.go:200    ChannelSettlementCalc     (结算计算,影响金额)
- settlement.go:327    SearchChannelSettlementStat2 (原本父子共用同一 filter
                       而侥幸正确,补上以免后续改动破坏该前提)
- settlement.go:857    AddSettlementInfo2        (影响金额)
- settlement.go:1138   DetailSettlementInfo

本次只改匹配条件。settlement 三处 childFilter 缺少 appId 约束的冗余查询,
以及 SearchChannelStat 在无 start/end 时 resp.Total 硬编码为 1000000、
withChild 累加被 canSum 跳过的问题,均未处理。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-02 09:38:03 +08:00

1508 lines
54 KiB
Go

package channelSer
import (
"context"
"errors"
"fmt"
"os"
"path"
"stat/api/proto"
"stat/common/log"
"stat/common/mongos"
"stat/common/utils"
"stat/middleware/define"
"stat/models/appInfoMod"
"stat/models/channelmod"
"stat/models/channelsettlementrecord"
"strings"
"time"
"go.mongodb.org/mongo-driver/mongo"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SearchChannelSettlementStat 查询渠道结算纤细
func SearchChannelSettlementStat(ctx context.Context, req *proto.ChannelStatSettlementSearch) (*proto.ChannelStatSettlementSearchResp, error) {
resp := &proto.ChannelStatSettlementSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: *req.Channel},
},
}
} else {
filter = bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel}
}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelStat.AppId] = *req.AppId
}
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelStat.UserName] = *req.UserName
}
if req.Settlement != nil && *req.Settlement != "" {
filter[channelmod.FN.ChannelStat.Settlement] = *req.Settlement
}
// 开始时间和结束时间
endTime := utils.BeginOfToday().AddDate(0, 0, -1)
if req.Start != nil {
if req.End != nil {
if req.End.Before(utils.BeginOfToday()) {
endTime = *req.End
}
}
filter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *req.Start, "$lte": endTime}
} else {
if req.End != nil {
if req.End.Before(utils.BeginOfToday()) {
endTime = *req.End
}
}
filter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$lte": endTime}
}
filter[channelmod.FN.ChannelStat.IsPay] = bson.M{"$ne": true}
totalParentChannelStatInfo, err := channelmod.FindManyChannelStat(ctx, filter)
if err != nil {
return nil, err
}
if len(totalParentChannelStatInfo) == 0 {
return resp, nil
}
ChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter, opt)
if err != nil {
return nil, err
}
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
}
ruleInfo, _ := channelmod.FindOneChannelDeductionRule(ctx, ruleFilter)
if ruleInfo != nil {
resp.TotalInfo.Channel = ruleInfo.Channel
resp.TotalInfo.Settlement = ruleInfo.Settlement
resp.TotalInfo.AppId = ruleInfo.AppId
resp.TotalInfo.CPAPrice = ruleInfo.CPAPrice
resp.TotalInfo.CPSRate = ruleInfo.CPSRate
resp.TotalInfo.AppType = ruleInfo.AppType
resp.TotalInfo.PromoteMode = ruleInfo.PromoteMode
}
// 计算合计数据
for _, channelStatInfo := range totalParentChannelStatInfo {
channelmod.ChannelStatSummation(&resp.TotalInfo, channelStatInfo)
}
resp.Total = int64(len(totalParentChannelStatInfo))
resp.List = ChannelStatList
if ruleInfo != nil {
resp.Income, resp.Desc = channelmod.CalculateChannelIncome(resp.TotalInfo.CorrectValidData())
}
return resp, nil
}
// ChannelSettlementCalc 渠道结算项实时计算
func ChannelSettlementCalc(ctx context.Context, req *proto.SettlementCalcReq) (*proto.SettlementCalcResp, error) {
// 参数检查
if len(req.SettleIds) <= 0 {
return nil, errors.New("没有提交要结算的统计id数组")
}
// 在已结算表中检查是否有重复结算
settlementInfoFilter := bson.M{
channelsettlementrecord.FN.ChannelSettlementRecord.SettleIds: bson.M{"$in": req.SettleIds},
}
settlementInfoList, err := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, settlementInfoFilter)
if err != nil {
return nil, err
}
if len(settlementInfoList) > 0 {
return nil, errors.New("提交的统计记录列表中含有重复结算项")
}
// 获取扣量规则
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.ID: req.RuleId,
channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
}
channelRule, err1 := channelmod.FindOneChannelDeductionRuleEx(ctx, ruleFilter)
// 检查扣量规则
if err1 != nil {
return nil, fmt.Errorf("查找渠道结算规则失败:%s", err1.Error())
}
// 开始时间和结束时间
req.BeginTime, req.EndTime, err = CheckAndFixTime(req.BeginTime, req.EndTime)
if err != nil {
return nil, err
}
// 一级记录查找
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelStat.SumDate: 1})
parentFilter := bson.M{
channelmod.FN.ChannelStat.AppId: req.AppId, // 应用限定
channelmod.FN.ChannelStat.Channel: req.Channel, // 渠道限定
channelmod.FN.ChannelStat.Type: channelmod.ChannelTypeParent, // 类型限定
channelmod.FN.ChannelStat.IsPay: bson.M{"$ne": true}, // 未结算过
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": req.BeginTime, "$lte": req.EndTime}, // 时间限定
channelmod.FN.ChannelStat.ID: bson.M{"$in": req.SettleIds}, // 提交的IDs
}
ParentList, err := channelmod.FindManyChannelStat(ctx, parentFilter, opt)
if err != nil {
return nil, err
}
if len(ParentList) != len(req.SettleIds) {
return nil, fmt.Errorf("提交的统计记录条数[%d]和查找到的[%d]不一致", len(req.SettleIds), len(ParentList))
}
// 结算项检查
for _, stat := range ParentList {
if stat.Type != channelmod.ChannelTypeParent {
return nil, fmt.Errorf("子渠道[%s]不能单独参与结算,只需提交其父渠道即可", stat.Channel)
}
}
if len(ParentList) == 0 {
return nil, fmt.Errorf("没有找到渠道结算数据")
}
// 列表的起止时间
listBeginTime := ParentList[0].SumDate
listEndTime := ParentList[len(ParentList)-1].SumDate
// 查找二级渠道的数据
childTypes := channelmod.GetChildChannelTypes()
childFilter := bson.M{
channelmod.FN.ChannelStat.ParentChannel: req.Channel, // 限定父渠道
channelmod.FN.ChannelStat.Type: bson.M{"$in": childTypes}, // 限定子渠道
channelmod.FN.ChannelStat.IsPay: bson.M{"$ne": true}, // 未结算过
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": listBeginTime, "$lte": listEndTime}, // 时间限定
}
ChildList, err := channelmod.FindManyChannelStat(ctx, childFilter)
if err != nil {
return nil, err
}
// 把二级记录汇总到一级记录上面
for _, parent := range ParentList {
parent.CorrectValidData()
for _, child := range ChildList {
if child.ParentChannel == parent.Channel && child.AppId == parent.AppId && child.SumDate.Equal(parent.SumDate) {
channelmod.ChannelStatSummation(parent, child.CorrectValidData()) // 把子渠道的数据累加到父渠道上面
}
}
}
// 对一级记录进行统计汇总
resp := &proto.SettlementCalcResp{}
resp.TotalCount = int64(len(ParentList))
for _, statInfo := range ParentList {
channelmod.ChannelStatSummation(&(resp.TotalInfo), statInfo)
}
resp.TotalInfo.Channel = req.Channel
resp.TotalInfo.AppId = req.AppId
resp.TotalInfo.Settlement = channelRule.Settlement
resp.TotalInfo.CPAPrice = channelRule.CPAPrice
resp.TotalInfo.CPSRate = channelRule.CPSRate
resp.TotalInfo.AppType = channelRule.AppType
resp.TotalInfo.PromoteMode = channelRule.PromoteMode
// 根据扣量规则计算结算金额
resp.TotalInfo.CalcIncomeRate()
resp.TotalIncome, resp.TotalDesc = channelmod.CalculateChannelIncome(resp.TotalInfo.CorrectValidData())
return resp, nil
}
// SearchChannelSettlementStat2 查询渠道结算纤细
func SearchChannelSettlementStat2(ctx context.Context, req *proto.ChannelStatSettlementSearch2) (*proto.ChannelStatSettlementSearch2Resp, error) {
resp := &proto.ChannelStatSettlementSearch2Resp{}
if req.Channel == nil || *req.Channel == "" {
return nil, fmt.Errorf("渠道必须填写")
}
if req.AppId == nil {
return nil, fmt.Errorf("应用ID必须填写")
}
appInfo, err := appInfoMod.GetAppInfoByIdEx(ctx, *req.AppId)
if err != nil {
return nil, fmt.Errorf("应用不存在或查找失败:%s", err.Error())
}
channelInfo, err2 := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: *req.Channel})
if err2 != nil || channelInfo.Channel == "" {
return nil, fmt.Errorf("渠道[%s]不存在,或查找失败", *req.Channel)
}
if channelInfo.Type != channelmod.ChannelTypeParent {
return nil, fmt.Errorf("渠道[%s]不是一级渠道,只有一级渠道才能结算", *req.Channel)
}
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
}
ruleInfo, err1 := channelmod.FindOneChannelDeductionRuleEx(ctx, ruleFilter)
if err1 != nil {
return nil, fmt.Errorf("渠道[%s]的应用[%s]结算规则不存在或查找失败:%s", *req.Channel, appInfo.Name, err1.Error())
}
resp.RuleId = ruleInfo.ID
tmpStat := channelmod.ChannelStat{
Channel: ruleInfo.Channel,
AppId: ruleInfo.AppId,
Settlement: ruleInfo.Settlement,
CPAPrice: ruleInfo.CPAPrice,
CPSRate: ruleInfo.CPSRate,
AppType: ruleInfo.AppType,
PromoteMode: ruleInfo.PromoteMode,
}
resp.TotalInfo = tmpStat
resp.ValidInfo = tmpStat
resp.ValidInfo = tmpStat
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
opt.SetSort(bson.D{
{Key: channelmod.FN.ChannelStat.SumDate, Value: -1},
{Key: channelmod.FN.ChannelStat.CreatedAt, Value: -1},
})
filter := bson.M{}
// 渠道结算一定包含子渠道
filter[channelmod.FN.ChannelStat.AppId] = *req.AppId
// 返回渠道的钱包信息
resp.Wallet = channelInfo.Wallet
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelStat.UserName] = *req.UserName
}
// 开始时间和结束时间
req.Start, req.End, err = CheckAndFixTime(req.Start, req.End)
if err != nil {
return nil, err
}
filter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *req.Start, "$lte": *req.End}
// 已结算的是否显示
if req.IsPayed != nil {
if *req.IsPayed {
filter[channelmod.FN.ChannelStat.IsPay] = true
} else {
filter[channelmod.FN.ChannelStat.IsPay] = bson.M{"$ne": true}
}
}
// 找到所有一级记录
filter[channelmod.FN.ChannelStat.Channel] = *req.Channel
filter[channelmod.FN.ChannelStat.Type] = channelmod.ChannelTypeParent
ParentsChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter)
if err != nil {
return nil, err
}
if len(ParentsChannelStatList) == 0 {
return resp, nil
}
// 找到分页一级记录
PagedChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter, opt)
if err != nil {
return nil, err
}
// 找到所有子级记录
delete(filter, channelmod.FN.ChannelStat.Channel)
filter[channelmod.FN.ChannelStat.ParentChannel] = *req.Channel
childTypes := channelmod.GetChildChannelTypes()
filter[channelmod.FN.ChannelStat.Type] = bson.M{"$in": childTypes}
ChildChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter)
if err != nil {
return nil, err
}
// 把子级记录汇总到一级记录上面
for _, parent := range ParentsChannelStatList {
parent.CorrectValidData()
for _, child := range ChildChannelStatList {
if child.ParentChannel == parent.Channel && child.AppId == parent.AppId && child.SumDate.Equal(parent.SumDate) {
channelmod.ChannelStatSummation(parent, child) // 把子渠道的数据累加到父渠道上面
}
}
}
// 对一级记录进行统计汇总
for _, channelStatInfo := range ParentsChannelStatList {
resp.TotalCount++
channelmod.ChannelStatSummation(&(resp.TotalInfo), channelStatInfo)
if channelStatInfo.IsPay {
resp.InvalidCount++
channelmod.ChannelStatSummation(&(resp.InvalidInfo), channelStatInfo)
} else {
resp.ValidCount++
channelmod.ChannelStatSummation(&(resp.ValidInfo), channelStatInfo)
}
}
// 把Page列表项替换为带子渠道数据的项
for i, pageItem := range PagedChannelStatList {
for j, parentItem := range ParentsChannelStatList {
if pageItem.ID.String() == parentItem.ID.String() {
PagedChannelStatList[i] = ParentsChannelStatList[j]
break
}
}
}
// 计算金币的数值:金币等于渠道直推充值金额-购买VIP金额-扣量
for _, v := range PagedChannelStatList {
v.CorrectValidData()
v.CalcIncomeRate()
}
// 修正数据
resp.TotalInfo.CorrectValidData()
resp.ValidInfo.CorrectValidData()
resp.InvalidInfo.CorrectValidData()
// 收益率
resp.TotalInfo.CalcIncomeRate()
resp.ValidInfo.CalcIncomeRate()
resp.InvalidInfo.CalcIncomeRate()
// 结算数值及提示信息
resp.TotalIncome, resp.TotalDesc = channelmod.CalculateChannelIncome(&resp.TotalInfo)
resp.ValidIncome, resp.ValidDesc = channelmod.CalculateChannelIncome(&resp.ValidInfo)
resp.InvalidIncome, resp.InvalidDesc = channelmod.CalculateChannelIncome(&resp.InvalidInfo)
// 输出列表
resp.List = PagedChannelStatList
resp.Channel = channelInfo.Channel
resp.AppId = *req.AppId
return resp, nil
}
// SearchSettlementInfo 查询渠道信息
func GetSettlementApplicantList(ctx context.Context, req *proto.GetSettlementApplicantListReq) (*proto.GetSettlementApplicantListResp, error) {
resp := proto.GetSettlementApplicantListResp{}
match := bson.M{}
pipelines := []bson.M{
{"$match": match},
{"$group": bson.M{
"_id": bson.M{"applicant": "$applicant"},
"count": bson.M{"$sum": 1},
}},
{"$project": bson.M{
"applicant": "$_id.applicant",
"count": "$count",
"_id": 0,
}},
{"$sort": bson.D{{Key: "count", Value: -1}}},
}
type Data struct {
Applicant string `bson:"applicant" json:"applicant"`
Count int64 `bson:"count" json:"count"`
}
List := make([]Data, 0)
if err := channelsettlementrecord.AggregateChannelSettlementRecords(ctx, &List, pipelines); err != nil {
return nil, err
}
for _, item := range List {
resp.List = append(resp.List, item.Applicant)
}
return &resp, nil
}
// SearchApplyForm 查询渠道信息
func SearchApplyForm(ctx context.Context, req *proto.GetApplyFormReq, channel string) (
*proto.GetApplyFormResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.GetApplyFormResp{}
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Channel] = channel
if req.AppId != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt] = bson.M{"$gte": *req.Start, "$lt": *req.End}
}
if req.Status != nil {
if *req.Status == channelsettlementrecord.SettlementInit || *req.Status == channelsettlementrecord.SettlementSuccess {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = *req.Status
} else {
return nil, fmt.Errorf("未知状态")
}
} else {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = bson.M{"$in": []channelsettlementrecord.SettlementStatus{
channelsettlementrecord.SettlementInit, channelsettlementrecord.SettlementSuccess,
}}
}
total, err := channelsettlementrecord.CountChannelSettlementRecords(ctx, filter)
if err != nil {
return nil, err
}
if total == 0 {
return resp, nil
}
SettlementInfoList, err := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.List = SettlementInfoListToApplyForm(SettlementInfoList)
resp.Total = total
return resp, nil
}
// SearchSettlementInfo 查询渠道信息
func SearchSettlementInfo(ctx context.Context, req *proto.SettlementInfoSearch) (
*proto.SettlementInfoSearchResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.SettlementInfoSearchResp{}
if req.Channel != nil && *req.Channel != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt] = bson.M{"$gte": *req.Start, "$lt": *req.End}
}
if req.Remark != nil && *req.Remark != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Remark] = bson.M{"$regex": *req.Remark}
}
if len(req.Applicants) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Applicant] = bson.M{"$in": req.Applicants}
}
if len(req.Status) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = bson.M{"$in": req.Status}
} else {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = bson.M{"$in": []channelsettlementrecord.SettlementStatus{}}
}
total, serr := channelsettlementrecord.CountChannelSettlementRecords(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
SettlementInfoList, err := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.List = SettlementInfoList
resp.Total = total
return resp, nil
}
// SearchDeletedSettlementInfo 查询渠道信息
func SearchDeletedSettlementInfo(ctx context.Context, req *proto.SettlementInfoSearch) (
*proto.SettlementInfoSearchResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelsettlementrecord.FN.ChannelSettlementDeleted.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.SettlementInfoSearchResp{}
if req.Channel != nil && *req.Channel != "" {
filter[channelsettlementrecord.FN.ChannelSettlementDeleted.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelsettlementrecord.FN.ChannelSettlementDeleted.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelsettlementrecord.FN.ChannelSettlementDeleted.CreatedAt] = bson.M{"$gte": *req.Start, "$lt": *req.End}
}
if req.Remark != nil && *req.Remark != "" {
filter[channelsettlementrecord.FN.ChannelSettlementDeleted.Remark] = bson.M{"$regex": *req.Remark}
}
//if req.PayType != nil {
// switch *req.PayType {
// case 1:
// filter[channelsettlementrecord.FN.ChannelSettlementDeleted.ERC] = bson.M{"$gt": ""}
// case 2:
// filter[channelsettlementrecord.FN.ChannelSettlementDeleted.TRC] = bson.M{"$gt": ""}
// case 3:
// filter[channelsettlementrecord.FN.ChannelSettlementDeleted.BankCard] = bson.M{"$gt": ""}
// case 4:
// filter[channelsettlementrecord.FN.ChannelSettlementDeleted.Alipay] = bson.M{"$gt": ""}
// }
//}
total, serr := channelsettlementrecord.CountChannelSettlementDeletes(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
SettlementInfoList, err := channelsettlementrecord.FindManyChannelSettlementDeleted(ctx, filter, opt)
if serr != nil {
return nil, err
}
resp.List = SettlementInfoList
resp.Total = total
return resp, nil
}
// SearchSettlementInfoExport 导出渠道信息
func SearchSettlementInfoExport(ctx context.Context, req *proto.SettlementInfoSearch) (string, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt] = bson.M{"$gte": *req.Start, "$lt": *req.End}
}
if req.Remark != nil && *req.Remark != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Remark] = bson.M{"$regex": *req.Remark}
}
if len(req.Applicants) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Applicant] = bson.M{"$in": req.Applicants}
}
if len(req.Status) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = bson.M{"$in": req.Status}
}
appList, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return "", err
}
total, err1 := channelsettlementrecord.CountChannelSettlementRecords(ctx, filter)
if err1 != nil {
return "", err1
}
if total == 0 {
return "", nil
}
SettlementInfoList, err2 := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, filter, opt)
if err2 != nil {
return "", err2
}
filePath := fmt.Sprintf("./tmp/渠道结算列表-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if sErr := export(filePath, SettlementInfoList, appList); sErr != nil {
return "", sErr
}
return filePath, nil
}
// SearchSettlementInfoApplyExport 导出结算申请信息
func SearchSettlementInfoApplyExport(ctx context.Context, req *proto.SettlementInfoSearch) (string, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.CreatedAt] = bson.M{"$gte": *req.Start, "$lt": *req.End}
}
if req.Remark != nil && *req.Remark != "" {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Remark] = bson.M{"$regex": *req.Remark}
}
if len(req.Applicants) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Applicant] = bson.M{"$in": req.Applicants}
}
if len(req.Status) > 0 {
filter[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = bson.M{"$in": req.Status}
}
total, err1 := channelsettlementrecord.CountChannelSettlementRecords(ctx, filter)
if err1 != nil {
return "", err1
}
if total == 0 {
return "", nil
}
SettlementInfoList, err2 := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, filter, opt)
if err2 != nil {
return "", err2
}
if len(SettlementInfoList) <= 0 {
return "", nil
}
appList, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return "", err
}
filePath := fmt.Sprintf("./tmp/渠道结算申请列表-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if sErr := ExportSettleApplyForm(filePath, SettlementInfoList, appList); sErr != nil {
return "", sErr
}
return filePath, nil
}
// AddSettlementInfo 添加一个媒体视频
func AddSettlementInfo(ctx context.Context, req *proto.SettlementInfoAdd) error {
settlementInfoFilter := bson.M{
channelsettlementrecord.FN.ChannelSettlementRecord.Channel: req.Channel,
channelsettlementrecord.FN.ChannelSettlementRecord.AppId: req.AppId,
}
settlementInfoList, serr := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, settlementInfoFilter)
for _, valueInfo := range settlementInfoList {
if req.BeginTime.After(valueInfo.BeginTime) && req.BeginTime.Before(valueInfo.EndTime) {
return errors.New("重复结算")
}
if req.EndTime.After(valueInfo.BeginTime) && req.EndTime.Before(valueInfo.EndTime) {
return errors.New("重复结算")
}
}
request := &proto.ChannelStatSettlementSearch{
Channel: &req.Channel,
AppId: &req.AppId,
Start: &req.BeginTime,
End: &req.EndTime,
}
resp, serr := SearchChannelSettlementStat(ctx, request)
if serr != nil {
return serr
}
if resp.Income != req.TotalMoney {
return errors.New("金额不一致")
}
// 更新是否已经结算
channelStatFilter := bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: req.Channel},
},
channelmod.FN.ChannelStat.AppId: req.AppId,
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": req.BeginTime, "$lte": req.EndTime},
channelmod.FN.ChannelStat.IsPay: bson.M{"$ne": true},
}
set := bson.M{
"$set": bson.M{
channelmod.FN.ChannelStat.IsPay: true,
},
}
err := channelmod.UpdateManyChannelStat(ctx, channelStatFilter, set)
if err != nil {
return err
}
filter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
}
newRecord := &channelsettlementrecord.ChannelSettlementRecord{
Channel: req.Channel,
AppId: req.AppId,
BeginTime: req.BeginTime,
EndTime: req.EndTime,
TotalMoney: resp.Income,
NewDirectCount: resp.TotalInfo.NewDirectCount,
NewFissionCount: resp.TotalInfo.NewFissionCount,
NewDeductionCount: resp.TotalInfo.NewDeductionCount,
RechargeAmount: resp.TotalInfo.DirectAmount + resp.TotalInfo.FissionAmount + resp.TotalInfo.DeductionAmount,
VipPayMoney: resp.TotalInfo.VipPayMoney,
ValidDirectCount: resp.TotalInfo.ValidDirectCount,
ValidDeductionCount: resp.TotalInfo.ValidDeductionCount,
ValidPayMoney: resp.TotalInfo.ValidPayMoney,
ValidPayCoin: resp.TotalInfo.ValidPayCoin,
Status: channelsettlementrecord.SettlementInit,
CreatedAt: time.Now(),
}
ruleInfo, _ := channelmod.FindOneChannelDeductionRule(ctx, filter)
if ruleInfo != nil {
newRecord.AppType = ruleInfo.AppType
newRecord.PromoteMode = ruleInfo.PromoteMode
newRecord.Settlement = ruleInfo.Settlement
newRecord.CPAPrice = ruleInfo.CPAPrice
newRecord.CPSRate = ruleInfo.CPSRate
}
return channelsettlementrecord.InsertOneChannelSettlementRecord(ctx, newRecord)
}
// AddSettlementInfo 添加一个媒体视频
func AddSettlementInfo2(ctx context.Context, req *proto.SettlementInfoAdd2, accType define.ActType, account string) error {
// 参数检查
if len(req.SettleIds) <= 0 {
return errors.New("没有提交要结算的统计记录id数组")
}
if req.SettleMoney <= 0 {
return errors.New("结算的真实金额必须大于零")
}
// 在已结算表中检查是否有重复结算
settlementInfoFilter := bson.M{
channelsettlementrecord.FN.ChannelSettlementRecord.SettleIds: bson.M{"$in": req.SettleIds},
}
settlementInfoList, err := channelsettlementrecord.FindManyChannelSettlementRecord(ctx, settlementInfoFilter)
if err != nil {
return err
}
if len(settlementInfoList) > 0 {
return errors.New("重复结算")
}
// 获取扣量规则
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.ID: req.RuleId,
channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
}
channelRule, err1 := channelmod.FindOneChannelDeductionRuleEx(ctx, ruleFilter)
// 检查扣量规则
if err1 != nil {
return fmt.Errorf("查找渠道结算规则失败:%s", err1.Error())
}
// 开始时间和结束时间
req.BeginTime, req.EndTime, err = CheckAndFixTime(req.BeginTime, req.EndTime)
if err != nil {
return err
}
// ids数据查找
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelStat.SumDate: 1})
parentFilter := bson.M{
channelmod.FN.ChannelStat.AppId: req.AppId, // 应用限定
channelmod.FN.ChannelStat.Channel: req.Channel, // 渠道限定
channelmod.FN.ChannelStat.Type: channelmod.ChannelTypeParent, // 类型限定
channelmod.FN.ChannelStat.IsPay: bson.M{"$ne": true}, // 未结算过
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": req.BeginTime, "$lte": req.EndTime}, // 时间限定
channelmod.FN.ChannelStat.ID: bson.M{"$in": req.SettleIds}, // 提交的IDs
}
ParentList, err2 := channelmod.FindManyChannelStat(ctx, parentFilter, opt)
if err2 != nil {
return err2
}
if len(ParentList) != len(req.SettleIds) {
return fmt.Errorf("提交的统计记录条数[%d]和查找到的[%d]不一致", len(req.SettleIds), len(ParentList))
}
// 结算项检查
for _, stat := range ParentList {
if stat.Type != channelmod.ChannelTypeParent {
return fmt.Errorf("子渠道[%s]不能单独参与结算,只需提交其父渠道即可", stat.Channel)
}
}
if len(ParentList) == 0 {
return fmt.Errorf("没有找到渠道结算数据")
}
// 列表的起止时间
listBeginTime := ParentList[0].SumDate
listEndTime := ParentList[len(ParentList)-1].SumDate
// 查找子级渠道的数据
childTypes := channelmod.GetChildChannelTypes()
childFilter := bson.M{
channelmod.FN.ChannelStat.ParentChannel: req.Channel, // 限定父渠道
channelmod.FN.ChannelStat.Type: bson.M{"$in": childTypes}, // 限定子渠道
channelmod.FN.ChannelStat.IsPay: bson.M{"$ne": true}, // 未结算过
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": listBeginTime, "$lte": listEndTime}, // 时间限定
}
ChildList, err3 := channelmod.FindManyChannelStat(ctx, childFilter)
if err3 != nil {
return err3
}
// 把二级记录汇总到一级记录上面
for _, parent := range ParentList {
parent.CorrectValidData()
for _, child := range ChildList {
if child.ParentChannel == parent.Channel && child.AppId == parent.AppId && child.SumDate.Equal(parent.SumDate) {
channelmod.ChannelStatSummation(parent, child) // 把子渠道的数据累加到父渠道上面
}
}
}
// 对一级记录进行统计汇总
sumInfo := &channelmod.ChannelStat{
Channel: channelRule.Channel,
AppId: channelRule.AppId,
Settlement: channelRule.Settlement,
CPAPrice: channelRule.CPAPrice,
CPSRate: channelRule.CPSRate,
AppType: channelRule.AppType,
PromoteMode: channelRule.PromoteMode,
}
for _, statInfo := range ParentList {
channelmod.ChannelStatSummation(sumInfo, statInfo)
}
// 根据扣量规则计算结算金额
// TODO 这里按当前 Rule规则 扣量
income, _ := channelmod.CalculateChannelIncome(sumInfo)
// 检查提交的和计算的是否一致
if income != req.TotalMoney {
return fmt.Errorf("提交的结算金额[%d分]和服务器统计的[%d分]不一致", req.TotalMoney, income)
}
// 获取操作者名字
var accName string
if accName, err = GetAccountName(ctx, accType, account); err != nil {
return fmt.Errorf("获取账号[%s]的信息失败:%s", account, err.Error())
}
// 获取渠道信息
channelInfo, err4 := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel})
if err4 != nil {
return fmt.Errorf("渠道不存在")
}
// 渠道级别检查
if channelInfo.Type != channelmod.ChannelTypeParent {
return fmt.Errorf("输入的渠道不是一级渠道,只有一级渠道才能进行结算")
}
// 结算钱包检查
if req.WalletAddr1 != nil && *req.WalletAddr1 != channelInfo.Wallet.Addr1 {
return fmt.Errorf("钱包地址1,和渠道信息中填写的不一致")
}
if req.WalletAddr2 != nil && *req.WalletAddr2 != channelInfo.Wallet.Addr2 {
return fmt.Errorf("钱包地址2,和渠道信息中填写的不一致")
}
if req.WalletAddr1 == nil && req.WalletAddr2 == nil {
return fmt.Errorf("结算的收款地址信息必须至少填写一个")
}
// 下面两步用事务
fn := func(sctx mongo.SessionContext) error {
// 更新是否已经结算
parentFilter = bson.M{
channelmod.FN.ChannelStat.ID: bson.M{"$in": req.SettleIds}, // 提交的IDs
channelmod.FN.ChannelStat.IsPay: bson.M{"$in": bson.A{nil, false}},
}
set := bson.M{
"$set": bson.M{
channelmod.FN.ChannelStat.IsPay: true,
},
}
if count, err := channelmod.UpdateManyChannelStatWithResult(sctx, parentFilter, set); err != nil {
return err
} else {
if count != int64(len(req.SettleIds)) {
return fmt.Errorf("数据修改冲突,同一结算,请不要多次点击提交")
}
}
// 渠道备注
var remark string
if req.Remark != nil {
remark = *req.Remark
}
now := time.Now()
newRecord := &channelsettlementrecord.ChannelSettlementRecord{
Channel: req.Channel,
IncludeChild: true,
AppId: req.AppId,
BeginTime: listBeginTime,
EndTime: listEndTime,
TotalMoney: income,
SettleMoney: req.SettleMoney,
Settlement: channelRule.Settlement,
CPAPrice: channelRule.CPAPrice,
CPSRate: channelRule.CPSRate,
AppType: channelRule.AppType,
PromoteMode: channelRule.PromoteMode,
NewDirectCount: sumInfo.NewDirectCount,
NewFissionCount: sumInfo.NewFissionCount,
NewDeductionCount: sumInfo.NewDeductionCount,
RechargeAmount: sumInfo.DirectAmount + sumInfo.FissionAmount + sumInfo.DeductionAmount,
VipPayMoney: sumInfo.VipPayMoney,
VipPayCoin: sumInfo.VipPayCoin,
VipPayDeduction: sumInfo.VipPayDeduction,
ValidDirectCount: sumInfo.ValidDirectCount,
ValidDeductionCount: sumInfo.ValidDeductionCount,
ValidPayMoney: sumInfo.ValidPayMoney,
ValidPayCoin: sumInfo.ValidPayCoin,
Wallet: channelmod.Wallet{
Addr1: checkParam(req.WalletAddr1),
Addr2: checkParam(req.WalletAddr2),
},
PayMoney: 0,
OperateHistory: []channelsettlementrecord.SettlementOperateItem{
{
OperateTime: time.Now(),
BeforeStatus: channelsettlementrecord.SettlementInit,
AfterStatus: channelsettlementrecord.SettlementInit,
OperatorAccount: account,
OperatorAccountName: accName,
OperatorAccountType: accType,
Pictures: []string{},
Content: fmt.Sprintf("【%s】提交了结算申请", accName),
Remark: remark,
},
},
Status: channelsettlementrecord.SettlementInit,
Remark: remark,
SettleIds: req.SettleIds,
CreatedAt: now,
UpdatedAt: now,
Applicant: accName,
}
err = channelsettlementrecord.InsertOneChannelSettlementRecord(sctx, newRecord)
return err
}
if err := mongos.Transaction(ctx, fn); err != nil {
return err
}
return nil
}
func checkParam(req *string) string {
if req != nil {
return *req
} else {
return ""
}
}
// UpdateSettlementInfo 更新指定渠道
func UpdateSettlementInfo(ctx context.Context, info proto.SettlementInfoUpdate) error {
filter := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.ID: info.ID}
set := bson.M{}
if info.PayMoney != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.PayMoney] = info.PayMoney
}
if info.Remark != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Remark] = info.Remark
}
if info.Status != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = info.Status
}
if info.Usdt != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Usdt] = info.Usdt
}
//更新数据库
setDoc := bson.M{"$set": set}
serr := channelsettlementrecord.UpdateOneChannelSettlementRecord(ctx, filter, setDoc)
if serr != nil {
return serr
}
return nil
}
// DeleteSettlementInfo 更新指定渠道
func DeleteSettlementInfo(ctx context.Context, req proto.SettlementInfoDelete, accType define.ActType, account string) error {
filter := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.ID: req.ID}
settle, err := channelsettlementrecord.FindOneChannelSettlementRecordEx(ctx, filter)
if err != nil {
return err
}
// 获取操作者名字
var accName string
if accName, err = GetAccountName(ctx, accType, account); err != nil {
return fmt.Errorf("获取账号[%s]的信息失败:%s", account, err.Error())
}
// 状态为【发起申请】【打款失败】这两种状态的结算记录可以删除
if settle.Status == channelsettlementrecord.SettlementInit || settle.Status == channelsettlementrecord.SettlementFailed {
// 下面用事务
fn := func(sctx mongo.SessionContext) error {
// 结算中的统计项全部修改为未结算状态
statFilter := bson.M{
channelmod.FN.ChannelStat.ID: bson.M{"$in": settle.SettleIds}, // 提交的IDs
}
set := bson.M{
"$set": bson.M{
channelmod.FN.ChannelStat.IsPay: false,
},
}
err := channelmod.UpdateManyChannelStat(sctx, statFilter, set)
if err != nil {
return err
}
// 插入要删除副本到删除备份表
settle.OperateHistory = append(settle.OperateHistory, channelsettlementrecord.SettlementOperateItem{
OperateTime: time.Now(),
BeforeStatus: settle.Status,
AfterStatus: channelsettlementrecord.SettlementDeleted,
OperatorAccount: account,
OperatorAccountType: accType,
OperatorAccountName: accName,
Content: fmt.Sprintf("状态改变:(%s => %s)\n", settle.Status.String(), channelsettlementrecord.SettlementDeleted.String()),
Pictures: nil,
Remark: "结算记录被删除",
})
status := settle.Status
settle.Status = channelsettlementrecord.SettlementDeleted
err = channelsettlementrecord.InsertOneChannelSettlementDeleted(ctx, settle)
if err != nil {
return err
}
// 删除结算项
settleFilter := bson.M{
channelsettlementrecord.FN.ChannelSettlementRecord.ID: settle.ID,
channelsettlementrecord.FN.ChannelSettlementRecord.Status: status,
}
_, err = channelsettlementrecord.DelOneChannelSettlementRecord(ctx, settleFilter)
return err
}
if err := mongos.Transaction(ctx, fn); err != nil {
return err
}
return nil
} else {
return fmt.Errorf("当前结算项的状态[%s][%d]不能被删除!", channelsettlementrecord.GetSettStatus(settle.Status), settle.Status)
}
}
// DetailSettlementInfo 查看结算项详情
func DetailSettlementInfo(ctx context.Context, req proto.SettlementInfoDetail) (*proto.SettlementInfoDetailResp, error) {
resp := &proto.SettlementInfoDetailResp{}
settleFilter := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.ID: req.ID}
settle, err := channelsettlementrecord.FindOneChannelSettlementRecordEx(ctx, settleFilter)
if err != nil {
if err != mongo.ErrNoDocuments {
return nil, err
}
settle, err = channelsettlementrecord.FindOneChannelSettlementDeletedEx(ctx, settleFilter)
if err != nil {
return nil, err
}
}
// 找到所有一级记录
statFilter := bson.M{
channelmod.FN.ChannelStat.Type: channelmod.ChannelTypeParent,
channelmod.FN.ChannelStat.ID: bson.M{"$in": settle.SettleIds},
}
ParentsChannelStatList, err := channelmod.FindManyChannelStat(ctx, statFilter)
if err != nil {
return nil, err
}
if len(ParentsChannelStatList) == 0 {
return resp, nil
}
// 找到分页一级记录
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelStat.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
PagedChannelStatList, err := channelmod.FindManyChannelStat(ctx, statFilter, opt)
if err != nil {
return nil, err
}
// 找到所有子级记录
childTypes := channelmod.GetChildChannelTypes()
childFilter := bson.M{
channelmod.FN.ChannelStat.ParentChannel: settle.Channel,
channelmod.FN.ChannelStat.Type: bson.M{"$in": childTypes},
channelmod.FN.ChannelStat.SumDate: bson.M{"$gte": settle.BeginTime, "$lte": settle.EndTime}, // 时间限定
}
ChildsChannelStatList, err := channelmod.FindManyChannelStat(ctx, childFilter)
if err != nil {
return nil, err
}
// 把子级记录汇总到一级记录上面
for _, parent := range ParentsChannelStatList {
for _, child := range ChildsChannelStatList {
if child.ParentChannel == parent.Channel && child.AppId == parent.AppId && child.SumDate.Equal(parent.SumDate) {
channelmod.ChannelStatSummation(parent, child) // 把子渠道的数据累加到父渠道上面
}
}
}
// 把Page列表项替换为带子渠道数据的项
for i, pageItem := range PagedChannelStatList {
for j, parentItem := range ParentsChannelStatList {
if pageItem.ID.String() == parentItem.ID.String() {
PagedChannelStatList[i] = ParentsChannelStatList[j]
break
}
}
}
// 计算金币的数值:金币等于渠道直推充值金额-购买VIP金额-扣量
for _, v := range PagedChannelStatList {
//v.VipPayCoin = (v.DirectAmount + v.FissionAmount - v.VipPayMoney - v.VipPayDeduction)
v.CorrectValidData()
v.CalcIncomeRate()
}
resp.List = PagedChannelStatList
resp.Count = int64(len(ParentsChannelStatList))
return resp, nil
}
// UpdateSettlementInfo2 更新指定渠道
func UpdateSettlementInfo2(ctx context.Context, req proto.SettlementInfoUpdate2, accType define.ActType, account string) error {
chReq := proto.SettlementInfoUpdate2{}
filter := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.ID: req.ID}
settle, err := channelsettlementrecord.FindOneChannelSettlementRecordEx(ctx, filter)
if err != nil {
return err
}
// 获取操作者名字
var accName string
if accName, err = GetAccountName(ctx, accType, account); err != nil {
return fmt.Errorf("获取操作者账号[%s]的信息失败:%s", account, err.Error())
}
set := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.UpdatedAt: time.Now()}
if req.Remark != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Remark] = req.Remark
chReq.Remark = req.Remark
}
// 状态转换检查
switch settle.Status {
case channelsettlementrecord.SettlementInit: // 【发起】结算状态,可以修改和确认
{
if req.Status == nil { // 渠道可以修改要结算的钱数,及钱包地址
return fmt.Errorf("参数错误,要修改的状态字段为空")
} else {
if *req.Status == channelsettlementrecord.SettlementSuccess || *req.Status == channelsettlementrecord.SettlementFailed {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Status] = req.Status
chReq.Status = req.Status
if *req.Status == channelsettlementrecord.SettlementSuccess {
if req.Usdt != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.Usdt] = req.Usdt
chReq.Usdt = req.Usdt
}
if req.PayMoney != nil {
set[channelsettlementrecord.FN.ChannelSettlementRecord.PayMoney] = req.PayMoney
chReq.PayMoney = req.PayMoney
}
}
} else {
return fmt.Errorf("修改状态出错!因为结算状态为【发起申请】状态,只能修改为【打款成功】或【打款失败】")
}
}
}
case channelsettlementrecord.SettlementSuccess:
{
return fmt.Errorf("错误,已经成功的结算单不能修改")
}
case channelsettlementrecord.SettlementFailed:
{
return fmt.Errorf("错误,打款失败的结算单不能修改,只能删除后重新发起结算申请")
}
case channelsettlementrecord.SettlementDeleted:
{
return fmt.Errorf("错误,已删除的订单无法修改")
}
default:
{
return fmt.Errorf("状态无效")
}
}
opItem := GetUpdateSettlementOperateItem(*settle, chReq, accType, account, accName, req.Pictures)
push := bson.M{channelsettlementrecord.FN.ChannelSettlementRecord.OperateHistory: opItem}
//更新数据库
setDoc := bson.M{"$set": set, "$push": push}
serr := channelsettlementrecord.UpdateOneChannelSettlementRecord(ctx, filter, setDoc)
if serr != nil {
return serr
}
return nil
}
func ExportSettleApplyForm(rPath string, transInfos []*channelsettlementrecord.ChannelSettlementRecord, appList []appInfoMod.AppInfo) error {
f := excelize.NewFile()
sheetName := "Sheet1"
// 设置列宽
f.SetColWidth("Sheet1", "A", "W", 20)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 30)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center"
}
}`)
f.SetCellStyle("Sheet1", "A1", "W1", headStyleID)
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{
"A1": "申请人",
"B1": "产品",
"C1": "渠道码",
"D1": "分成",
"E1": "结算期间",
"F1": "",
"G1": "新增",
"H1": "",
"I1": "金币充值",
"J1": "充值金额",
"K1": "结算金额",
"L1": "ARPU",
"M1": "收益比例",
"N1": "打款日期",
"O1": "打款金额",
"P1": "实际打款",
"Q1": "汇率",
"R1": "U金额",
"S1": "方式",
"T1": "钱包",
"U1": "打款资料",
"V1": "打款资料",
"W1": "备注",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
f.SetCellValue(sheetName, "E2", "开:")
f.SetCellValue(sheetName, "F2", "结:")
f.SetCellValue(sheetName, "G2", "未")
f.SetCellValue(sheetName, "H2", "扣")
f.MergeCell(sheetName, "E1", "F1")
f.MergeCell(sheetName, "G1", "H1")
SetCellValue := func(file *excelize.File, sheet, axis string, value string) {
//fmt.Printf("sheet: %s, axis:%s, value:%s\n", sheet, axis, value)
file.SetCellValue(sheetName, axis, value)
}
for index, item := range transInfos {
col := 'A' // col 不能超过Z,即列输不能超过26列,超出将出现bug
row := index + 3
axis := func() string {
ret := fmt.Sprintf("%s%d", string(col), row)
col++
return ret
}
operator := ""
remark := ""
if len(item.OperateHistory) > 0 {
operator = item.OperateHistory[0].OperatorAccountName
remark = item.OperateHistory[0].Remark
if len(item.OperateHistory) > 1 {
if remark == "" {
remark = item.OperateHistory[len(item.OperateHistory)-1].Remark
} else {
s := item.OperateHistory[len(item.OperateHistory)-1].Remark
if s != "" {
remark += " / " + s
}
}
}
}
begin := item.BeginTime.Local().Format("2006-01-02")
end := item.EndTime.Local().Format("2006-01-02")
divide := ""
if item.Settlement == channelmod.SettlementCPA {
divide = fmt.Sprintf("%.2f", float64(item.CPAPrice)/100)
} else if item.Settlement == channelmod.SettlementCPS {
divide = fmt.Sprintf("%d%%", item.CPSRate)
}
payMoneyStr := ""
if item.Status == channelsettlementrecord.SettlementSuccess {
payMoneyStr = IndentationDisplay(item.SettleMoney)
}
realMoneyStr := ""
realUsdtStr := ""
if item.Status == channelsettlementrecord.SettlementSuccess {
realMoneyStr = IndentationDisplay(item.PayMoney)
realUsdtStr = IndentationDisplay(item.Usdt)
}
//fmt.Printf("----- ROW %d -----\n", row)
product := fmt.Sprintf("%s%s", GetAppName(appList, item.AppId), strings.ToUpper(string(item.Settlement)))
SetCellValue(f, sheetName, axis(), operator) //申请人
SetCellValue(f, sheetName, axis(), product) //产品
SetCellValue(f, sheetName, axis(), item.Channel) //渠道码
SetCellValue(f, sheetName, axis(), divide) //分成
SetCellValue(f, sheetName, axis(), begin) //开
SetCellValue(f, sheetName, axis(), end) //结
SetCellValue(f, sheetName, axis(), FenToYuanDisplay(item.NewDirectCount+item.NewFissionCount+item.NewDeductionCount)) //未
SetCellValue(f, sheetName, axis(), FenToYuanDisplay(item.NewDirectCount)) //扣
SetCellValue(f, sheetName, axis(), IndentationDisplay(item.VipPayCoin)) //金币充值
SetCellValue(f, sheetName, axis(), IndentationDisplay(item.VipPayMoney+item.VipPayDeduction)) //充值金额(这里显示的是:总充值-金币)
SetCellValue(f, sheetName, axis(), IndentationDisplay(item.SettleMoney)) //结算金额
SetCellValue(f, sheetName, axis(), "") //ARPU
SetCellValue(f, sheetName, axis(), "") //收益比例
SetCellValue(f, sheetName, axis(), "") //打款日期
SetCellValue(f, sheetName, axis(), payMoneyStr) //打款金额
SetCellValue(f, sheetName, axis(), realMoneyStr) //实际打款
SetCellValue(f, sheetName, axis(), "") //汇率
SetCellValue(f, sheetName, axis(), realUsdtStr) //U金额
SetCellValue(f, sheetName, axis(), "") //方式
SetCellValue(f, sheetName, axis(), "") //钱包
SetCellValue(f, sheetName, axis(), item.Wallet.Addr1) //打款资料
SetCellValue(f, sheetName, axis(), item.Wallet.Addr2) //打款资料
SetCellValue(f, sheetName, axis(), remark) //备注
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, rPath)
if err := f.SaveAs(fileName); err != nil {
log.Error("SettlementInfo:ExportSettleForm save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
func export(rPath string, transInfos []*channelsettlementrecord.ChannelSettlementRecord, appList []appInfoMod.AppInfo) error {
f := excelize.NewFile()
sheetName := "Sheet1"
// 设置列宽
f.SetColWidth("Sheet1", "A", "U", 20)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 30)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center"
}
}`)
f.SetCellStyle("Sheet1", "A1", "U1", headStyleID)
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{
"A1": "开始时间",
"B1": "结束时间",
"C1": "渠道",
"D1": "结算方式",
"E1": "收款信息1",
"F1": "收款信息2",
"G1": "单价",
"H1": "分成比列",
"I1": "应用名称",
"J1": "新增",
"K1": "扣量",
"L1": "充值金额",
"M1": "金币充值",
"N1": "统计结算金额",
"O1": "申请结算金额",
"P1": "打款金额(CNY)",
"Q1": "打款金额(USDT)",
"R1": "备注",
"S1": "状态",
"T1": "创建时间",
"U1": "申请结算人",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
for k, v := range transInfos {
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
begin := v.BeginTime.Local().Format("2006-01-02")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), begin) //开始时间
i++
end := v.EndTime.Local().Format("2006-01-02")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), end) //结束时间
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Channel) //渠道
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Settlement) //结算方式
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Wallet.Addr1) //打款地址1
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Wallet.Addr2) //打款地址2
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.CPAPrice)/100) //单价
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.CPSRate) //分成比列
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), GetAppName(appList, v.AppId)) //应用名称
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewDirectCount) //新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewDeductionCount) //扣量
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.VipPayMoney+v.VipPayDeduction)/100) //充值金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.VipPayCoin)/100) //金币充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TotalMoney)/100) //统计结算金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.SettleMoney)/100) //申请结算金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.PayMoney)/100) //打款金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.Usdt)/100)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Remark) //备注
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Status.String())
i++
timeStr := v.CreatedAt.Local().Format("2006-01-02")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), timeStr)
opName := ""
if len(v.OperateHistory) > 0 {
opName = v.OperateHistory[0].OperatorAccountName
}
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), opName)
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, rPath)
if err := f.SaveAs(fileName); err != nil {
log.Error("SettlementInfo:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
func GetAppName(appList []appInfoMod.AppInfo, appId int32) string {
for _, app := range appList {
if app.AppId == appId {
return app.Name
}
}
return "未知app"
}