// Package gen
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2026 LiXiujie
// @Author Lxj
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
// @AutoGenerate Version
package common
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
"strings"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/google/uuid"
)
type sCommonAgentBalance struct{}
func NewCommonAgentBalance() *sCommonAgentBalance {
return &sCommonAgentBalance{}
}
func init() {
service.RegisterCommonAgentBalance(NewCommonAgentBalance())
}
// Model 企业客户财务信息ORM模型
func (s *sCommonAgentBalance) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: true,
})
}
return handler.Model(dao.TAgentBalance.Ctx(ctx), option...)
}
// List 获取企业客户财务信息列表
func (s *sCommonAgentBalance) List(ctx context.Context, in *model.TAgentBalanceListParam) (list []*model.TAgentBalanceListModel, totalCount int, err error) {
mod := s.Model(ctx)
deptId := contexts.GetDeptId(ctx)
mod = mod.Fields(model.TAgentBalanceListModel{})
mod = mod.Where(dao.TAgentBalance.Columns().DeptId, deptId)
if in.Id > 0 {
mod = mod.Where(dao.TAgentBalance.Columns().Id, in.Id)
}
if in.WithdrawAmountStatus > 0 {
mod = mod.Where(dao.TAgentBalance.Columns().WithdrawAmountStatus, in.WithdrawAmountStatus)
}
if in.Status > 0 {
mod = mod.Where(dao.TAgentBalance.Columns().Status, in.Status)
}
if len(in.CreatedAt) == 2 {
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
}
if len(in.CreatedAt) == 2 {
mod = mod.WhereBetween(dao.TAgentBalance.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
}
mod = mod.Page(in.Page, in.PageSize)
mod = mod.OrderDesc(dao.TAgentBalance.Columns().Id)
mod = mod.Hook(hook.MemberSummary)
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取企业客户财务信息列表失败,请稍后重试!")
return
}
return
}
// Export 导出企业客户财务信息
func (s *sCommonAgentBalance) Export(ctx context.Context, in *model.TAgentBalanceListParam) (err error) {
list, totalCount, err := s.List(ctx, in)
if err != nil {
return
}
tags, err := convert.GetEntityDescTags(model.TAgentBalanceExportModel{})
if err != nil {
return
}
var (
fileName = "导出企业客户财务信息-" + gctx.CtxId(ctx)
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
exports []model.TAgentBalanceExportModel
)
if err = gconv.Scan(list, &exports); err != nil {
return
}
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
return
}
// Edit 修改/新增企业客户财务信息
func (s *sCommonAgentBalance) Edit(ctx context.Context, in *model.TAgentBalanceEditParam) (err error) {
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
if in.Id > 0 {
updatedBy := contexts.GetUserId(ctx)
in.UpdatedBy = &updatedBy
in.UpdatedAt = gtime.Now()
if _, err = s.Model(ctx).
Fields(model.TAgentBalanceUpdateFields{}).
WherePri(in.Id).Data(in).Update(); err != nil {
err = gerror.Wrap(err, "修改企业客户财务信息失败,请稍后重试!")
}
return
}
tenantId := contexts.GetTenantId(ctx)
in.TenantId = &tenantId
createdDept := contexts.GetDeptId(ctx)
in.CreatedDept = &createdDept
createdBy := contexts.GetUserId(ctx)
in.CreatedBy = &createdBy
in.CreatedAt = gtime.Now()
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(model.TAgentBalanceInsertFields{}).
Data(in).Insert(); err != nil {
err = gerror.Wrap(err, "新增企业客户财务信息失败,请稍后重试!")
}
return
})
}
// Delete 删除企业客户财务信息
func (s *sCommonAgentBalance) Delete(ctx context.Context, in *model.TAgentBalanceDeleteParam) (err error) {
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
err = gerror.Wrap(err, "删除企业客户财务信息失败,请稍后重试!")
return
}
return
}
// View 获取企业客户财务信息指定信息
func (s *sCommonAgentBalance) View(ctx context.Context, in *model.TAgentBalanceViewParam) (res *model.TAgentBalanceViewModel, err error) {
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
err = gerror.Wrap(err, "获取企业客户财务信息信息,请稍后重试!")
return
}
return
}
// Status 更新企业客户财务信息状态
func (s *sCommonAgentBalance) Status(ctx context.Context, in *model.TAgentBalanceStatusParam) (err error) {
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
dao.TAgentBalance.Columns().Status: in.Status,
dao.TAgentBalance.Columns().UpdatedBy: contexts.GetUserId(ctx),
}).Update(); err != nil {
err = gerror.Wrap(err, "更新企业客户财务信息状态失败,请稍后重试!")
return
}
return
}
// GetAccountBalance 获取当前登录企业的账户余额信息。
func (s *sCommonAgentBalance) GetAccountBalance(ctx context.Context, in *model.TAgentBalanceTrendParam) (res *model.TAgentBalanceChartData, err error) {
res = &model.TAgentBalanceChartData{}
deptId := contexts.GetDeptId(ctx)
months, startTime, endTime, err := agentBalanceMonthRange(in.StartMonth, in.EndMonth)
if err != nil {
return nil, err
}
balanceMap, err := s.getMonthlyLastBalance(ctx, dao.TBalanceDetail.Ctx(ctx),
dao.TBalanceDetail.Columns().TransactionTime, dao.TBalanceDetail.Columns().BalanceAfter,
dao.TBalanceDetail.Columns().DeptId, deptId, startTime, endTime, "余额趋势")
if err != nil {
return nil, err
}
withdrawableMap, err := s.getMonthlyLastBalance(ctx, dao.TFinWithdrawableChange.Ctx(ctx),
dao.TFinWithdrawableChange.Columns().CreatedAt, dao.TFinWithdrawableChange.Columns().AfterBalance,
dao.TFinWithdrawableChange.Columns().DeptId, deptId, startTime, endTime, "可提现金额趋势")
if err != nil {
return nil, err
}
creditMap, err := s.getMonthlyLastBalance(ctx, dao.TCreditDetail.Ctx(ctx),
dao.TCreditDetail.Columns().TransactionTime, dao.TCreditDetail.Columns().CreditBalanceAfter,
dao.TCreditDetail.Columns().DeptId, deptId, startTime, endTime, "信用额度趋势")
if err != nil {
return nil, err
}
balanceValues := make([]float64, 0, len(months))
withdrawableValues := make([]float64, 0, len(months))
creditValues := make([]float64, 0, len(months))
for _, month := range months {
balanceValues = append(balanceValues, balanceMap[month])
withdrawableValues = append(withdrawableValues, withdrawableMap[month])
creditValues = append(creditValues, creditMap[month])
}
res.Labels = months
res.Series = []*model.TAgentBalanceChartSeries{
{Name: "余额", Values: balanceValues},
{Name: "可提现金额", Values: withdrawableValues},
{Name: "信用额度", Values: creditValues},
}
return
}
// getMonthlyLastBalance 查询指定月份范围内每月最后一条记录的余额。
func (s *sCommonAgentBalance) getMonthlyLastBalance(ctx context.Context, mod *gdb.Model, timeColumn string, balanceColumn string, deptIdColumn string, deptId interface{}, startTime, endTime time.Time, errMsg string) (map[string]float64, error) {
type maxTimeRow struct {
AccountPeriod string `orm:"account_period"`
MaxTime string `orm:"max_time"`
}
var maxTimes []*maxTimeRow
if err := mod.Fields("DATE_FORMAT("+timeColumn+", '%Y-%m') as account_period, MAX("+timeColumn+") as max_time").
Where(deptIdColumn, deptId).
WhereGTE(timeColumn, startTime).
WhereLT(timeColumn, endTime.AddDate(0, 1, 0)).
Group("DATE_FORMAT(" + timeColumn + ", '%Y-%m')").
Scan(&maxTimes); err != nil {
return nil, gerror.Wrap(err, "查询"+errMsg+"失败,请稍后重试!")
}
result := make(map[string]float64)
if len(maxTimes) == 0 {
return result, nil
}
maxTimeList := make([]string, len(maxTimes))
for i, mt := range maxTimes {
maxTimeList[i] = mt.MaxTime
}
type balanceRow struct {
AccountPeriod string `orm:"account_period"`
BalanceAfter float64 `orm:"balance_after"`
}
var allResults []*balanceRow
if err := mod.Fields("DATE_FORMAT("+timeColumn+", '%Y-%m') as account_period, "+balanceColumn+" as balance_after").
Where(deptIdColumn, deptId).
WhereIn(timeColumn, maxTimeList).
Order(timeColumn + " ASC").
Scan(&allResults); err != nil {
return nil, gerror.Wrap(err, "查询"+errMsg+"失败,请稍后重试!")
}
for _, item := range allResults {
result[item.AccountPeriod] = item.BalanceAfter
}
return result, nil
}
// AccountTrend 获取收入金额、支出金额折线图。
func (s *sCommonAgentBalance) AccountTrend(ctx context.Context, in *model.TAgentBalanceTrendParam) (res *model.TAgentBalanceChartData, err error) {
months, startMonth, endMonth, err := agentBalanceMonthRange(in.StartMonth, in.EndMonth)
if err != nil {
return nil, err
}
deptId := contexts.GetDeptId(ctx)
type billRow struct {
AccountPeriod string `orm:"account_period"`
TotalIncome float64 `orm:"total_income"`
TotalExpense float64 `orm:"total_expense"`
}
var bills []*billRow
accountPeriodColumn := dao.TProfitAccountMonthBill.Columns().AccountPeriod
deptIdColumn := dao.TProfitAccountMonthBill.Columns().DeptId
totalIncomeColumn := dao.TProfitAccountMonthBill.Columns().TotalIncome
totalExpenseColumn := dao.TProfitAccountMonthBill.Columns().TotalExpense
mod := handler.Model(dao.TProfitAccountMonthBill.Ctx(ctx))
if in.AccountType == "2" {
accountPeriodColumn = dao.TConsumeAccountMonthBill.Columns().AccountPeriod
deptIdColumn = dao.TConsumeAccountMonthBill.Columns().DeptId
totalIncomeColumn = dao.TConsumeAccountMonthBill.Columns().TotalIncome
totalExpenseColumn = dao.TConsumeAccountMonthBill.Columns().TotalExpense
mod = handler.Model(dao.TConsumeAccountMonthBill.Ctx(ctx))
}
if err = mod.Fields(accountPeriodColumn, totalIncomeColumn, totalExpenseColumn).
Where(deptIdColumn, deptId).
WhereBetween(accountPeriodColumn, startMonth, endMonth).
Scan(&bills); err != nil {
return nil, gerror.Wrap(err, "查询账户总览趋势失败,请稍后重试!")
}
billMap := make(map[string]*billRow, len(bills))
for _, item := range bills {
billMap[item.AccountPeriod] = item
}
incomeValues := make([]float64, 0, len(months))
expenseValues := make([]float64, 0, len(months))
for _, month := range months {
if item, ok := billMap[month]; ok {
incomeValues = append(incomeValues, item.TotalIncome)
expenseValues = append(expenseValues, item.TotalExpense)
continue
}
incomeValues = append(incomeValues, 0)
expenseValues = append(expenseValues, 0)
}
return &model.TAgentBalanceChartData{
Labels: months,
Series: []*model.TAgentBalanceChartSeries{
{Name: "收入金额", Values: incomeValues},
{Name: "支出金额", Values: expenseValues},
},
}, nil
}
// agentBalanceMonthRange 返回月份范围。
func agentBalanceMonthRange(startMonth, endMonth string) (months []string, startTime time.Time, endTime time.Time, err error) {
now := time.Now()
if endMonth == "" {
endMonth = now.Format("2006-01")
}
if startMonth == "" {
tempEndTime, parseErr := time.Parse("2006-01", endMonth)
if parseErr != nil {
err = gerror.New("结束月份格式不正确,应为YYYY-MM")
return
}
startMonth = tempEndTime.AddDate(0, -11, 0).Format("2006-01")
}
startTime, err = time.Parse("2006-01", startMonth)
if err != nil {
err = gerror.New("开始月份格式不正确,应为YYYY-MM")
return
}
endTime, err = time.Parse("2006-01", endMonth)
if err != nil {
err = gerror.New("结束月份格式不正确,应为YYYY-MM")
return
}
if startTime.After(endTime) {
err = gerror.New("开始月份不能大于结束月份")
return
}
for t := startTime; !t.After(endTime); t = t.AddDate(0, 1, 0) {
months = append(months, t.Format("2006-01"))
}
return months, startTime, endTime, nil
}
const (
financeIncomeExpenseIncome = 0
financeIncomeExpenseExpense = 1
financeBalanceTransactionTypeTransferToWithdraw = 2
financeBalanceTransactionTypeManualAdjust = 4
financeBalanceTransactionChannelBalance = 3
financeWithdrawableChangeTypeCommission = 0
financeWithdrawableChangeTypeBalanceTransferIn = 3
financeApplyTypeBalanceToWithdraw = 2
financeAuditTypeFirst = 1
financeApplyStatusPending = 0
financeApplyStatusApproved = 1
financeApplyStatusRejected = 2
financeAuditResultPass = 0
financeAuditResultReject = 1
financeAccountStatusNormal = 0
financeWithdrawAmountStatusNormal = 0
financeCommissionStatusPending = 0
financeCommissionStatusSettled = 1
)
// BalanceTransferApply 发起余额转可提现申请。申请时先扣余额并写余额明细,申请单保持待审核。
func (s *sCommonAgentBalance) BalanceTransferApply(ctx context.Context, in *model.TAgentBalanceTransferApplyParam) (*model.TAgentBalanceTransferApplyModel, error) {
if in == nil {
in = &model.TAgentBalanceTransferApplyParam{}
}
if err := in.Filter(ctx); err != nil {
return nil, err
}
applyNo := "BWT" + strings.ReplaceAll(uuid.New().String(), "-", "")
deptId := contexts.GetDeptId(ctx)
tenantId := contexts.GetTenantId(ctx)
userId := contexts.GetUserId(ctx)
now := gtime.Now()
res := &model.TAgentBalanceTransferApplyModel{
ApplyNo: applyNo,
ApplyAmount: in.Amount,
}
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
account := &entity.TAgentBalance{}
columns := dao.TAgentBalance.Columns()
if err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
Where(columns.DeptId, deptId).
Where(columns.TenantId, tenantId).
Scan(account); err != nil {
return gerror.Wrap(err, "查询企业客户财务账户失败,请稍后重试!")
}
if account.Id == 0 {
return gerror.New("企业客户财务账户不存在")
}
if account.Status != financeAccountStatusNormal {
return gerror.New("企业客户财务账户状态异常")
}
if account.WithdrawAmountStatus != financeWithdrawAmountStatusNormal {
return gerror.New("可提现金额已冻结")
}
if account.Balance < in.Amount {
return gerror.New("账户余额不足")
}
balanceAfter := account.Balance - in.Amount
result, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
WherePri(account.Id).
Data(g.Map{
columns.Balance: balanceAfter,
columns.UpdatedBy: userId,
columns.UpdatedAt: now,
}).
Update()
if err != nil {
return gerror.Wrap(err, "扣减账户余额失败,请稍后重试!")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "获取账户余额更新结果失败")
}
if affected == 0 {
return gerror.New("账户余额未更新,请稍后重试")
}
if err = service.CommonTBalanceDetail().CreateBalanceTransferDetail(ctx, tx, &model.TBalanceTransferDetailCreateParam{
DeptId: deptId,
SerialNo: applyNo,
TransactionTime: now,
IncomeExpenseType: financeIncomeExpenseExpense,
TransactionType: financeBalanceTransactionTypeTransferToWithdraw,
TransactionChannel: financeBalanceTransactionChannelBalance,
Amount: in.Amount,
BeforeBalance: account.Balance,
BalanceAfter: balanceAfter,
OrderSn: applyNo,
Remark: "余额转可提现申请扣减余额",
TenantId: tenantId,
CreatedDept: deptId,
CreatedBy: userId,
CreatedAt: now,
}); err != nil {
return err
}
var topDept entity.SysDept
if err := dao.SysDept.Ctx(ctx).
Fields(dao.SysDept.Columns().DeptId, dao.SysDept.Columns().ParentId).
Where(dao.SysDept.Columns().ParentId, 0).
Scan(&topDept); err != nil {
return gerror.Wrap(err, "查询审核机构失败,请稍后重试!")
}
if topDept.DeptId == 0 {
return gerror.New("审核机构不存在")
}
if err = service.CommonTFinanceApply().CreateBalanceTransferApply(ctx, tx, &model.TFinanceApplyCreateBalanceTransferParam{
ApplyNo: applyNo,
ApplyDeptId: deptId,
AuditDeptId: topDept.DeptId,
AvailableAmountSnapshot: account.WithdrawAmount,
ApplyAmount: in.Amount,
Remark: in.Remark,
TenantId: tenantId,
CreatedDept: deptId,
CreatedBy: userId,
CreatedAt: now,
}); err != nil {
return err
}
res.BalanceAfter = balanceAfter
return nil
})
if err != nil {
return nil, gerror.Wrap(err, "余额转可提现申请失败,请稍后重试!")
}
return res, nil
}
// BalanceTransferAudit 审核余额转可提现申请。通过时加可提现金额,驳回时退回申请时扣掉的余额。
func (s *sCommonAgentBalance) BalanceTransferAudit(ctx context.Context, in *model.TAgentBalanceTransferAuditParam) error {
if in == nil {
in = &model.TAgentBalanceTransferAuditParam{}
}
if err := in.Filter(ctx); err != nil {
return err
}
auditNo, err := batchimport.NextRandomNo("BTA")
if err != nil {
return gerror.Wrap(err, "生成审核单号失败")
}
tenantId := contexts.GetTenantId(ctx)
currentDeptId := contexts.GetDeptId(ctx)
currentUserId := contexts.GetUserId(ctx)
if tenantId == "" {
return gerror.New("当前租户不能为空")
}
if currentDeptId <= 0 {
return gerror.New("当前登录机构不能为空")
}
now := gtime.Now()
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
apply, err := service.CommonTFinanceApply().GetPendingByApplyNo(ctx, tx, in.ApplyNo, tenantId)
if err != nil {
return err
}
if err = service.CommonTFinanceAuditRecord().CreateRecord(ctx, tx, &model.TFinanceAuditRecordCreateParam{
AuditNo: auditNo,
ApplyNo: apply.ApplyNo,
AuditDeptId: currentDeptId,
AuditorUserId: currentUserId,
AuditResult: in.AuditResult,
AuditOpinion: in.AuditOpinion,
AuditTime: now,
}); err != nil {
return err
}
applyStatus := financeApplyStatusApproved
if in.AuditResult == financeAuditResultReject {
applyStatus = financeApplyStatusRejected
}
if err = service.CommonTFinanceApply().UpdateAuditStatus(ctx, tx, &model.TFinanceApplyUpdateAuditStatusParam{
ApplyNo: apply.ApplyNo,
ApplyStatus: applyStatus,
AuditTime: now,
UpdatedBy: currentUserId,
UpdatedAt: now,
}); err != nil {
return err
}
account := &entity.TAgentBalance{}
columns := dao.TAgentBalance.Columns()
if err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
Where(columns.DeptId, apply.ApplyDeptId).
Where(columns.TenantId, apply.TenantId).
Scan(account); err != nil {
return gerror.Wrap(err, "查询企业客户财务账户失败,请稍后重试!")
}
if account.Id == 0 {
return gerror.New("企业客户财务账户不存在")
}
if account.Status != financeAccountStatusNormal {
return gerror.New("企业客户财务账户状态异常")
}
if in.AuditResult == financeAuditResultPass {
if account.WithdrawAmountStatus != financeWithdrawAmountStatusNormal {
return gerror.New("可提现金额已冻结")
}
before := account.WithdrawAmount
after := before + apply.ApplyAmount
result, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
WherePri(account.Id).
Data(g.Map{
columns.WithdrawAmount: after,
columns.UpdatedBy: currentUserId,
columns.UpdatedAt: now,
}).
Update()
if err != nil {
return gerror.Wrap(err, "增加可提现金额失败,请稍后重试!")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "获取账户余额更新结果失败")
}
if affected == 0 {
return gerror.New("账户余额未更新,请稍后重试")
}
return service.CommonTFinWithdrawableChange().CreateBalanceTransferInChange(ctx, tx, &model.TFinWithdrawableBalanceTransferInParam{
DeptId: apply.ApplyDeptId,
ChangeAmount: apply.ApplyAmount,
BeforeBalance: before,
AfterBalance: after,
AccountType: apply.AccountType,
BankName: apply.BankName,
BankAccount: apply.BankAccount,
AccountName: apply.AccountName,
IdentityCard: apply.IdentityCard,
SourceNo: apply.ApplyNo,
Remark: "余额转可提现审核通过",
AccountPeriod: now.Format("Y-m"),
TenantId: apply.TenantId,
CreatedDept: apply.ApplyDeptId,
CreatedBy: currentUserId,
CreatedAt: now,
})
}
before := account.Balance
after := before + apply.ApplyAmount
result, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
WherePri(account.Id).
Data(g.Map{
columns.Balance: after,
columns.UpdatedBy: currentUserId,
columns.UpdatedAt: now,
}).
Update()
if err != nil {
return gerror.Wrap(err, "退回账户余额失败,请稍后重试!")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "获取账户余额更新结果失败")
}
if affected == 0 {
return gerror.New("账户余额未更新,请稍后重试")
}
remark := "余额转可提现申请驳回退款"
if in.AuditOpinion != "" {
remark += ":" + in.AuditOpinion
}
return service.CommonTBalanceDetail().CreateBalanceTransferDetail(ctx, tx, &model.TBalanceTransferDetailCreateParam{
DeptId: apply.ApplyDeptId,
SerialNo: apply.ApplyNo,
TransactionTime: now,
IncomeExpenseType: financeIncomeExpenseIncome,
TransactionType: financeBalanceTransactionTypeManualAdjust,
TransactionChannel: financeBalanceTransactionChannelBalance,
Amount: apply.ApplyAmount,
BeforeBalance: before,
BalanceAfter: after,
OrderSn: apply.ApplyNo,
Remark: remark,
TenantId: apply.TenantId,
CreatedDept: apply.ApplyDeptId,
CreatedBy: currentUserId,
CreatedAt: now,
})
})
}
// SettleCommissionByPeriod 按账期区间结算待结算分润,同步到企业账户可提现金额并写入可提现明细。
func (s *sCommonAgentBalance) SettleCommissionByPeriod(ctx context.Context, in *model.TAgentBalanceCommissionSettlementParam) (*model.TAgentBalanceCommissionSettlementResult, error) {
if in == nil {
in = &model.TAgentBalanceCommissionSettlementParam{}
}
if err := in.Filter(ctx); err != nil {
return nil, err
}
res := &model.TAgentBalanceCommissionSettlementResult{
StartPeriod: in.StartPeriod,
EndPeriod: in.EndPeriod,
}
endPeriodTime, _ := time.Parse("2006-01", in.EndPeriod)
endNextPeriod := endPeriodTime.AddDate(0, 1, 0).Format("2006-01")
var lastId int64
tenantId := contexts.GetTenantId(ctx)
commissionColumns := dao.TFinCommissionDetail.Columns()
for {
var details []*entity.TFinCommissionDetail
mod := handler.Model(dao.TFinCommissionDetail.Ctx(ctx), &handler.Option{
FilterTenant: false,
FilterAuth: false,
}).
Fields(
commissionColumns.Id,
commissionColumns.BenefitDeptId,
commissionColumns.CommissionAmount,
commissionColumns.AccountPeriod,
commissionColumns.TenantId,
commissionColumns.CreatedDept,
).
Where(commissionColumns.CommissionStatus, financeCommissionStatusPending).
WhereGTE(commissionColumns.AccountPeriod, in.StartPeriod).
WhereLT(commissionColumns.AccountPeriod, endNextPeriod).
WhereGT(commissionColumns.Id, lastId).
OrderAsc(commissionColumns.Id).
Limit(1000)
if tenantId != "" {
mod = mod.Where(commissionColumns.TenantId, tenantId)
}
if err := mod.Scan(&details); err != nil {
return res, gerror.Wrap(err, "扫描待结算分润明细失败,请稍后重试!")
}
if len(details) == 0 {
break
}
res.ScanCount += len(details)
for _, item := range details {
if item.Id > lastId {
lastId = item.Id
}
}
groups := make(map[model.TAgentBalanceCommissionSettlementGroupKey]*model.TAgentBalanceCommissionSettlementGroup)
for _, item := range details {
if item.BenefitDeptId <= 0 || item.AccountPeriod == "" || item.CommissionAmount <= 0 {
continue
}
key := model.TAgentBalanceCommissionSettlementGroupKey{
DeptId: item.BenefitDeptId,
AccountPeriod: item.AccountPeriod,
TenantId: item.TenantId,
}
group := groups[key]
if group == nil {
group = &model.TAgentBalanceCommissionSettlementGroup{
DeptId: item.BenefitDeptId,
AccountPeriod: item.AccountPeriod,
TenantId: item.TenantId,
CreatedDept: item.CreatedDept,
}
groups[key] = group
}
group.Ids = append(group.Ids, item.Id)
group.Amount += item.CommissionAmount
}
for _, group := range groups {
if err := s.settleCommissionGroup(ctx, group); err != nil {
res.FailGroupCount++
g.Log().Errorf(ctx, "分润结算失败 deptId:%d accountPeriod:%s amount:%v err:%+v", group.DeptId, group.AccountPeriod, group.Amount, err)
continue
}
res.SuccessGroupCount++
res.SettledCount += len(group.Ids)
res.SettledAmount += group.Amount
}
}
return res, nil
}
func (s *sCommonAgentBalance) settleCommissionGroup(ctx context.Context, group *model.TAgentBalanceCommissionSettlementGroup) error {
if group == nil || len(group.Ids) == 0 {
return nil
}
now := gtime.Now()
userId := contexts.GetUserId(ctx)
accountColumns := dao.TAgentBalance.Columns()
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
account := &entity.TAgentBalance{}
if err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
Where(accountColumns.DeptId, group.DeptId).
Where(accountColumns.TenantId, group.TenantId).
LockUpdate().
Scan(account); err != nil {
return gerror.Wrap(err, "锁定企业客户财务账户失败,请稍后重试!")
}
if account.Id == 0 {
return gerror.New("企业客户财务账户不存在")
}
if account.Status != financeAccountStatusNormal {
return gerror.New("企业客户财务账户状态异常")
}
if account.WithdrawAmountStatus != financeWithdrawAmountStatusNormal {
return gerror.New("可提现金额已冻结")
}
if err := service.CommonTFinCommissionDetail().MarkSettled(ctx, tx, &model.TFinCommissionSettleUpdateParam{
Ids: group.Ids,
SettlementTime: now,
}); err != nil {
return err
}
before := account.WithdrawAmount
after := before + group.Amount
sourceNo := fmt.Sprintf("CMS%sD%d%s", strings.ReplaceAll(group.AccountPeriod, "-", ""), group.DeptId, now.Format("150405"))
if err := service.CommonTFinWithdrawableChange().CreateCommissionSettlementChange(ctx, tx, &model.TFinWithdrawableCommissionSettlementParam{
DeptId: group.DeptId,
ChangeAmount: group.Amount,
BeforeBalance: before,
AfterBalance: after,
SourceNo: sourceNo,
Remark: "分润结算转可提现",
AccountPeriod: group.AccountPeriod,
TenantId: group.TenantId,
CreatedDept: group.CreatedDept,
CreatedBy: userId,
CreatedAt: now,
}); err != nil {
return err
}
result, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).
WherePri(account.Id).
Data(g.Map{
accountColumns.WithdrawAmount: after,
accountColumns.UpdatedBy: userId,
accountColumns.UpdatedAt: now,
}).
Update()
if err != nil {
return gerror.Wrap(err, "更新企业客户可提现金额失败,请稍后重试!")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "获取企业客户可提现金额更新结果失败")
}
if affected == 0 {
return gerror.New("企业客户可提现金额未更新,请稍后重试")
}
return nil
})
}