1293 lines
37 KiB
Go
1293 lines
37 KiB
Go
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
|
//go:build plus
|
|
|
|
package models
|
|
|
|
import (
|
|
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
|
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
|
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
|
|
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
|
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
|
|
"github.com/TeaOSLab/EdgeAPI/internal/utils/regexputils"
|
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
|
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
|
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
|
"github.com/iwind/TeaGo/dbs"
|
|
"github.com/iwind/TeaGo/lists"
|
|
"github.com/iwind/TeaGo/rands"
|
|
"github.com/iwind/TeaGo/types"
|
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type BillType = string
|
|
|
|
const (
|
|
BillTypeTraffic BillType = "traffic" // 按流量计费
|
|
BillTypeBandwidth BillType = "bandwidth" // 按带宽计费
|
|
BillTypeTrafficAndBandwidth BillType = "trafficAndBandwidth" // 但流量和带宽计费
|
|
)
|
|
|
|
const (
|
|
UserBillStateEnabled = 1
|
|
UserBillStateDisabled = 0
|
|
)
|
|
|
|
func init() {
|
|
dbs.OnReadyDone(func() {
|
|
goman.New(func() {
|
|
// 自动生成账单任务
|
|
var ticker = time.NewTicker(1 * time.Hour)
|
|
for range ticker.C {
|
|
if SharedAPINodeDAO.CheckAPINodeIsPrimaryWithoutErr() { // 只在主API节点上运行
|
|
// 如果是凌晨,则延迟,避免因为数据没有及时上传而导致的错误
|
|
var currentHour = time.Now().Hour()
|
|
if currentHour >= 1 {
|
|
err := SharedUserBillDAO.GenerateBills()
|
|
if err != nil {
|
|
remotelogs.Error("UserBillDAO", "generate bills failed: "+err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// 类型定义
|
|
type regionPrice struct {
|
|
regionId int64
|
|
price float64
|
|
amount float64
|
|
bandwidthMB float64
|
|
trafficGB float64
|
|
trafficPackageGB float64
|
|
userTrafficPackageIds []int64
|
|
}
|
|
|
|
// DeleteUserBill 删除账单
|
|
// 物理删除账单是为了避免账单表 unique key 冲突
|
|
func (this *UserBillDAO) DeleteUserBill(tx *dbs.Tx, billId int64) error {
|
|
return this.Query(tx).
|
|
Pk(billId).
|
|
DeleteQuickly()
|
|
}
|
|
|
|
// GenerateBills 生成月账单
|
|
func (this *UserBillDAO) GenerateBills() error {
|
|
var tx *dbs.Tx
|
|
|
|
// 默认计费方式
|
|
priceConfig, err := SharedSysSettingDAO.ReadUserPriceConfig(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if priceConfig == nil || !priceConfig.IsOn {
|
|
return nil
|
|
}
|
|
|
|
// 使用套餐计费
|
|
if priceConfig.EnablePlans {
|
|
return this.GenerateBillsWithPlans(tx, timeutil.Format("Ym", time.Now().AddDate(0, -1, 0)), priceConfig)
|
|
}
|
|
|
|
// 生成流量和带宽账单
|
|
// 默认识别两个月内有流量的用户
|
|
var trafficDayFrom = timeutil.Format("Ym01", time.Now().AddDate(0, -1, 0))
|
|
var trafficDayTo = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1))
|
|
var billMonth = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
|
|
var billDay = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1))
|
|
err = this.GenerateTrafficAndBandwidthBills(tx, trafficDayFrom, trafficDayTo, billMonth, billDay, priceConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateTrafficAndBandwidthBills 根据流量或带宽计算账单
|
|
// trafficDayFrom 和 trafficDayTo 之间的时间间隔需要长一些,因为,可能是按月生成的
|
|
func (this *UserBillDAO) GenerateTrafficAndBandwidthBills(tx *dbs.Tx, trafficDayFrom string, trafficDayTo string, billMonth string, billDay string, priceConfig *userconfigs.UserPriceConfig) error {
|
|
if priceConfig == nil || !priceConfig.IsOn || priceConfig.EnablePlans /** 使用套餐 **/ {
|
|
return nil
|
|
}
|
|
|
|
if !regexputils.YYYYMMDD.MatchString(trafficDayFrom) {
|
|
return errors.New("invalid 'trafficDayFrom': " + trafficDayFrom)
|
|
}
|
|
if !regexputils.YYYYMMDD.MatchString(trafficDayTo) {
|
|
return errors.New("invalid 'trafficDayTo':" + trafficDayTo)
|
|
}
|
|
|
|
if len(billMonth) > 0 && !regexputils.YYYYMM.MatchString(billMonth) {
|
|
return errors.New("invalid 'billMonth':" + billMonth)
|
|
}
|
|
if len(billDay) > 0 && !regexputils.YYYYMMDD.MatchString(billDay) {
|
|
return errors.New("invalid 'billDay':" + billDay)
|
|
}
|
|
|
|
// 带宽用户
|
|
bandwidthUserIds, err := SharedUserBandwidthStatDAO.FindDistinctUserIds(tx, trafficDayFrom, trafficDayTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 流量用户
|
|
trafficUserIds, err := SharedServerDailyStatDAO.FindDistinctUserIds(tx, trafficDayFrom, trafficDayTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 区域价格设置
|
|
regions, err := SharedNodeRegionDAO.FindAllEnabledRegionPrices(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var regionItemMaps = map[int64]map[int64]float64{} // regionId => { itemId => price }
|
|
var regionIds = []int64{0}
|
|
for _, region := range regions {
|
|
regionIds = append(regionIds, int64(region.Id))
|
|
regionItemMaps[int64(region.Id)] = region.DecodePriceMap()
|
|
}
|
|
|
|
// 价格区间
|
|
bandwidthPriceItems, err := SharedNodePriceItemDAO.FindAllEnabledAndOnRegionPrices(tx, userconfigs.PriceTypeBandwidth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
trafficPriceItems, err := SharedNodePriceItemDAO.FindAllEnabledAndOnRegionPrices(tx, userconfigs.PriceTypeTraffic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var userIds = this.mergeUserIds([][]int64{bandwidthUserIds, trafficUserIds})
|
|
for _, userId := range userIds {
|
|
err := func(userId int64) error {
|
|
var month = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
|
|
var day = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1))
|
|
|
|
if len(billMonth) > 0 {
|
|
month = billMonth
|
|
}
|
|
if len(billDay) > 0 {
|
|
day = billDay
|
|
}
|
|
|
|
// 生成用户账单
|
|
err := this.GenerateUserTrafficAndBandwidthBills(tx, userId, priceConfig, bandwidthPriceItems, trafficPriceItems, regionIds, regionItemMaps, month, day)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新用户状态
|
|
_, err = SharedUserDAO.RenewUserServersState(tx, userId)
|
|
return err
|
|
}(userId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateUserTrafficAndBandwidthBills 生成单个用户的流量和带宽账单
|
|
// month 强制生成某月
|
|
// day 强制生成某日
|
|
func (this *UserBillDAO) GenerateUserTrafficAndBandwidthBills(tx *dbs.Tx,
|
|
userId int64,
|
|
priceConfig *userconfigs.UserPriceConfig,
|
|
bandwidthPriceItems []*NodePriceItem,
|
|
trafficPriceItems []*NodePriceItem,
|
|
regionIds []int64,
|
|
regionItemMaps map[int64]map[int64]float64,
|
|
month string,
|
|
day string) error {
|
|
if len(month) > 0 && !regexputils.YYYYMM.MatchString(month) {
|
|
return errors.New("invalid 'month': " + month)
|
|
}
|
|
if len(day) > 0 && !regexputils.YYYYMMDD.MatchString(day) {
|
|
return errors.New("invalid 'day': " + day)
|
|
}
|
|
|
|
if len(month) == 0 {
|
|
month = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
|
|
}
|
|
|
|
if len(day) == 0 {
|
|
day = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1))
|
|
}
|
|
|
|
// 计费方式和周期
|
|
var priceType = priceConfig.DefaultPriceType
|
|
var pricePeriod = priceConfig.DefaultPricePeriod
|
|
|
|
userPriceType, userPricePeriod, err := SharedUserDAO.FindUserPriceInfo(tx, userId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if priceConfig.UserCanChangePriceType && (userPriceType == userconfigs.PriceTypeBandwidth || userPriceType == userconfigs.PriceTypeTraffic) {
|
|
priceType = userPriceType
|
|
}
|
|
if priceConfig.UserCanChangePricePeriod && userconfigs.IsValidPricePeriod(userPricePeriod) {
|
|
pricePeriod = userPricePeriod
|
|
}
|
|
|
|
// 用户最近一次账单
|
|
lastBill, err := this.FindUserLatestTrafficAndBandwidthBill(tx, userId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var lastBillMonth = ""
|
|
var lastBillDay = ""
|
|
var lastPricePeriod = ""
|
|
var lastBillIsFinished = false
|
|
var lastBillType = ""
|
|
var lastBillId int64
|
|
if lastBill != nil {
|
|
lastBillId = int64(lastBill.Id)
|
|
lastBillMonth = lastBill.Month
|
|
lastPricePeriod = lastBill.PricePeriod
|
|
lastBillIsFinished = lastBill.CanPay
|
|
lastBillType = lastBill.Type
|
|
|
|
switch lastBill.PricePeriod {
|
|
case "daily":
|
|
lastBillDay = lastBill.DayTo
|
|
}
|
|
|
|
// 是否同当前一致
|
|
if lastBillIsFinished && lastPricePeriod == pricePeriod && ((pricePeriod == userconfigs.PricePeriodDaily && lastBillDay == day) || (pricePeriod == userconfigs.PricePeriodMonthly && lastBillMonth == month)) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// 检查账单周期冲突
|
|
if pricePeriod == userconfigs.PricePeriodDaily { // 按天
|
|
if lastPricePeriod == userconfigs.PricePeriodMonthly && len(lastBillMonth) > 0 && strings.HasPrefix(day, lastBillMonth) /** 同月 **/ {
|
|
pricePeriod = userconfigs.PricePeriodMonthly
|
|
}
|
|
} else if pricePeriod == userconfigs.PricePeriodMonthly { // 按月
|
|
if lastPricePeriod == userconfigs.PricePeriodDaily && len(lastBillDay) > 0 && strings.HasPrefix(lastBillDay, month) /** 同月 **/ {
|
|
pricePeriod = userconfigs.PricePeriodDaily
|
|
}
|
|
}
|
|
|
|
var dayFrom string
|
|
var dayTo string
|
|
switch pricePeriod {
|
|
case userconfigs.PricePeriodDaily:
|
|
dayFrom = day
|
|
dayTo = day
|
|
|
|
// 如果和已生成的账单一致,则继续沿用计费方式
|
|
if lastBillDay == dayTo && lastPricePeriod == pricePeriod {
|
|
if lastBillIsFinished {
|
|
switch lastBillType {
|
|
case BillTypeTraffic:
|
|
priceType = userconfigs.PriceTypeTraffic
|
|
case BillTypeBandwidth:
|
|
priceType = userconfigs.PriceTypeBandwidth
|
|
}
|
|
} else {
|
|
err = this.DeleteUserBill(tx, lastBillId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
case userconfigs.PricePeriodMonthly:
|
|
dayFrom = month + "01"
|
|
dayTo, err = utils.FixMonthMaxDay(month + "31")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 如果和已生成的账单一致,则继续沿用计费方式
|
|
if lastBillMonth == month && lastPricePeriod == pricePeriod {
|
|
if lastBillIsFinished {
|
|
switch lastBillType {
|
|
case BillTypeTraffic:
|
|
priceType = userconfigs.PriceTypeTraffic
|
|
case BillTypeBandwidth:
|
|
priceType = userconfigs.PriceTypeBandwidth
|
|
}
|
|
} else {
|
|
err = this.DeleteUserBill(tx, lastBillId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
return errors.New("invalid price period '" + pricePeriod + "'")
|
|
}
|
|
|
|
switch priceType {
|
|
case userconfigs.PriceTypeBandwidth:
|
|
var bandwidthConfig = priceConfig.DefaultBandwidthPriceConfig
|
|
if bandwidthConfig == nil {
|
|
return nil
|
|
}
|
|
|
|
if bandwidthConfig.SupportRegions {
|
|
var totalAmount float64
|
|
var regionPrices []*regionPrice
|
|
for _, regionId := range regionIds {
|
|
var searchRegionId = regionId
|
|
if searchRegionId == 0 {
|
|
searchRegionId = -1 // 查询没有分区域的
|
|
}
|
|
stat, err := SharedUserBandwidthStatDAO.FindPercentileBetweenDays(tx, userId, searchRegionId, dayFrom, dayTo, int32(bandwidthConfig.Percentile), bandwidthConfig.SupportAvgBandwidth())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stat == nil || stat.Bytes == 0 {
|
|
continue
|
|
}
|
|
|
|
var sizeMB = float64(stat.Bytes*8) / (1 << 20)
|
|
var price float64
|
|
var amount float64
|
|
var itemId = SharedNodePriceItemDAO.SearchItemsWithBits(bandwidthPriceItems, int64(stat.Bytes*8))
|
|
if itemId == 0 {
|
|
price, amount = bandwidthConfig.LookupPrice(sizeMB)
|
|
} else {
|
|
var found = false
|
|
priceMap, ok := regionItemMaps[regionId]
|
|
if ok {
|
|
itemPrice, ok := priceMap[itemId]
|
|
if ok {
|
|
found = true
|
|
price = itemPrice
|
|
amount = itemPrice * sizeMB
|
|
}
|
|
}
|
|
if !found {
|
|
price, amount = bandwidthConfig.LookupPrice(sizeMB)
|
|
}
|
|
}
|
|
regionPrices = append(regionPrices, ®ionPrice{
|
|
regionId: regionId,
|
|
price: price,
|
|
amount: amount,
|
|
bandwidthMB: sizeMB,
|
|
trafficGB: 0,
|
|
})
|
|
totalAmount += amount
|
|
}
|
|
|
|
// 创建账单
|
|
billId, err := this.CreateUserTrafficBill(tx, userId, BillTypeBandwidth, pricePeriod, "带宽费用", totalAmount, month, dayFrom, dayTo, dayTo < timeutil.Format("Ymd"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedUserTrafficBillDAO.DisableTrafficBills(tx, billId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, regionPriceItem := range regionPrices {
|
|
err = SharedUserTrafficBillDAO.CreateTrafficBill(tx, billId, regionPriceItem.regionId, priceType, regionPriceItem.bandwidthMB, int32(bandwidthConfig.Percentile), 0, 0, nil, regionPriceItem.price, regionPriceItem.amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
stat, err := SharedUserBandwidthStatDAO.FindPercentileBetweenDays(tx, userId, 0, dayFrom, dayTo, int32(bandwidthConfig.Percentile), bandwidthConfig.SupportAvgBandwidth())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stat == nil || stat.Bytes == 0 {
|
|
_, err = this.CreateUserTrafficBill(tx, userId, BillTypeBandwidth, pricePeriod, "带宽费用", 0, month, dayFrom, dayTo, month < timeutil.Format("Ym"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var sizeMB = float64(stat.Bytes*8) / (1 << 20)
|
|
price, amount := bandwidthConfig.LookupPrice(sizeMB)
|
|
billId, err := this.CreateUserTrafficBill(tx, userId, BillTypeBandwidth, pricePeriod, "带宽费用", amount, month, dayFrom, dayTo, dayTo < timeutil.Format("Ymd"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 删除老的子订单
|
|
err = SharedUserTrafficBillDAO.DisableTrafficBills(tx, billId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 新的子账单
|
|
err = SharedUserTrafficBillDAO.CreateTrafficBill(tx, billId, 0, priceType, sizeMB, int32(bandwidthConfig.Percentile), 0, 0, nil, price, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case userconfigs.PriceTypeTraffic:
|
|
var trafficConfig = priceConfig.DefaultTrafficPriceConfig
|
|
if trafficConfig == nil {
|
|
return nil
|
|
}
|
|
|
|
if trafficConfig.SupportRegions || priceConfig.EnableTrafficPackages {
|
|
var totalAmount float64
|
|
var regionPrices []*regionPrice
|
|
for _, regionId := range regionIds {
|
|
var searchRegionId = regionId
|
|
if searchRegionId == 0 {
|
|
searchRegionId = -1 // 查询没有分区域的
|
|
}
|
|
totalBytes, err := SharedServerDailyStatDAO.SumUserTrafficBytesBetweenDays(tx, userId, searchRegionId, dayFrom, dayTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if totalBytes == 0 {
|
|
continue
|
|
}
|
|
|
|
var sizeGB = float64(totalBytes) / (1 << 30)
|
|
|
|
// 尝试从流量包中扣除
|
|
// 即使没有启用流量包,以往的流量包仍然可以使用
|
|
var trafficPackageGB float64
|
|
var userTrafficPackageIds []int64
|
|
if regionId > 0 && dayTo < timeutil.Format("Ymd") /** 只适用于过往日期 **/ { // TODO 将来支持不分区
|
|
userTrafficPackageIds2, leftBytes, err := SharedUserTrafficPackageDAO.CostUserPackages(tx, userId, regionId, totalBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(userTrafficPackageIds2) > 0 {
|
|
userTrafficPackageIds = userTrafficPackageIds2
|
|
trafficPackageGB = float64(totalBytes-leftBytes) / (1 << 30)
|
|
totalBytes = leftBytes
|
|
sizeGB = float64(totalBytes) / (1 << 30)
|
|
if totalBytes == 0 {
|
|
regionPrices = append(regionPrices, ®ionPrice{
|
|
regionId: regionId,
|
|
price: 0,
|
|
amount: 0,
|
|
bandwidthMB: 0,
|
|
trafficGB: sizeGB,
|
|
trafficPackageGB: trafficPackageGB,
|
|
userTrafficPackageIds: userTrafficPackageIds,
|
|
})
|
|
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
var price float64
|
|
var amount float64
|
|
var itemId int64
|
|
if trafficConfig.SupportRegions {
|
|
itemId = SharedNodePriceItemDAO.SearchItemsWithBits(trafficPriceItems, totalBytes*8)
|
|
}
|
|
if itemId == 0 {
|
|
price, amount = trafficConfig.LookupPrice(sizeGB)
|
|
} else {
|
|
var found = false
|
|
priceMap, ok := regionItemMaps[regionId]
|
|
if ok {
|
|
itemPrice, ok := priceMap[itemId]
|
|
if ok {
|
|
found = true
|
|
price = itemPrice
|
|
amount = itemPrice * sizeGB
|
|
}
|
|
}
|
|
if !found {
|
|
price, amount = trafficConfig.LookupPrice(sizeGB)
|
|
}
|
|
}
|
|
regionPrices = append(regionPrices, ®ionPrice{
|
|
regionId: regionId,
|
|
price: price,
|
|
amount: amount,
|
|
bandwidthMB: 0,
|
|
trafficGB: sizeGB,
|
|
trafficPackageGB: trafficPackageGB,
|
|
userTrafficPackageIds: userTrafficPackageIds,
|
|
})
|
|
totalAmount += amount
|
|
}
|
|
|
|
// 创建账单
|
|
billId, err := this.CreateUserTrafficBill(tx, userId, BillTypeTraffic, pricePeriod, "流量费用", totalAmount, month, dayFrom, dayTo, dayTo < timeutil.Format("Ymd"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedUserTrafficBillDAO.DisableTrafficBills(tx, billId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, regionPriceItem := range regionPrices {
|
|
err = SharedUserTrafficBillDAO.CreateTrafficBill(tx, billId, regionPriceItem.regionId, priceType, 0, 0, regionPriceItem.trafficGB, regionPriceItem.trafficPackageGB, regionPriceItem.userTrafficPackageIds, regionPriceItem.price, regionPriceItem.amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
totalBytes, err := SharedServerDailyStatDAO.SumUserTrafficBytesBetweenDays(tx, userId, 0, dayFrom, dayTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if totalBytes == 0 {
|
|
_, err = this.CreateUserTrafficBill(tx, userId, BillTypeTraffic, pricePeriod, "流量费用", 0, month, dayFrom, dayTo, month < timeutil.Format("Ym"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var sizeGB = float64(totalBytes) / (1 << 30)
|
|
price, amount := trafficConfig.LookupPrice(sizeGB)
|
|
billId, err := this.CreateUserTrafficBill(tx, userId, BillTypeTraffic, pricePeriod, "流量费用", amount, month, dayFrom, dayTo, dayTo < timeutil.Format("Ymd"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 删除老的子订单
|
|
err = SharedUserTrafficBillDAO.DisableTrafficBills(tx, billId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 新的子账单
|
|
err = SharedUserTrafficBillDAO.CreateTrafficBill(tx, billId, 0, priceType, 0, 0, sizeGB, 0, nil, price, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *UserBillDAO) mergeUserIds(userIdSlices [][]int64) []int64 {
|
|
var userIdMap = map[int64]bool{}
|
|
var userIds = []int64{}
|
|
for _, userIdSlice := range userIdSlices {
|
|
for _, userId := range userIdSlice {
|
|
if userId <= 0 {
|
|
continue
|
|
}
|
|
_, ok := userIdMap[userId]
|
|
if ok {
|
|
continue
|
|
}
|
|
userIds = append(userIds, userId)
|
|
userIdMap[userId] = true
|
|
}
|
|
}
|
|
|
|
// 排序
|
|
sort.Slice(userIds, func(i, j int) bool {
|
|
return userIds[i] < userIds[j]
|
|
})
|
|
|
|
return userIds
|
|
}
|
|
|
|
// GenerateBillsWithPlans 根据套餐计算账单
|
|
func (this *UserBillDAO) GenerateBillsWithPlans(tx *dbs.Tx, month string, userPriceConfig *userconfigs.UserPriceConfig) error {
|
|
// 区域价格
|
|
regions, err := SharedNodeRegionDAO.FindAllEnabledRegionPrices(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var priceItems []*NodePriceItem
|
|
if len(regions) > 0 {
|
|
priceItems, err = SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// 计算服务套餐费用
|
|
plans, err := SharedPlanDAO.FindAllEnabledPlans(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var planMap = map[int64]*Plan{}
|
|
for _, plan := range plans {
|
|
planMap[int64(plan.Id)] = plan
|
|
}
|
|
|
|
var dayFrom = month + "01"
|
|
var dayTo = month + "31"
|
|
userPlanIds, err := SharedUserPlanStatDAO.FindDistinctUserPlanIds(tx, dayFrom, dayTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var cacheMap = utils.NewCacheMap()
|
|
var userIds = []int64{}
|
|
|
|
// 套餐计费方式
|
|
for _, userPlanId := range userPlanIds {
|
|
if userPlanId <= 0 {
|
|
continue
|
|
}
|
|
|
|
userPlan, err := SharedUserPlanDAO.FindUserPlanWithoutState(tx, userPlanId, cacheMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if userPlan == nil {
|
|
continue
|
|
}
|
|
var userId = int64(userPlan.UserId)
|
|
if userId <= 0 {
|
|
continue
|
|
}
|
|
|
|
if !lists.ContainsInt64(userIds, userId) {
|
|
userIds = append(userIds, userId)
|
|
}
|
|
|
|
bandwidthAlgo, err := SharedUserDAO.FindUserBandwidthAlgoForFee(tx, userId, userPriceConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
plan, ok := planMap[int64(userPlan.PlanId)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// 总流量
|
|
totalTrafficBytes, err := SharedUserPlanBandwidthStatDAO.SumMonthlyBytes(tx, userPlanId, month)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch plan.PriceType {
|
|
case serverconfigs.PlanPriceTypePeriod:
|
|
// 已经在购买套餐的时候付过费,这里不再重复计费
|
|
var fee float64 = 0
|
|
|
|
// 百分位
|
|
var percentile = 95
|
|
percentileBytes, err := SharedUserPlanBandwidthStatDAO.FindMonthlyPercentile(tx, userPlanId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), 0, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case serverconfigs.PlanPriceTypeTraffic:
|
|
var config = plan.DecodeTrafficPrice()
|
|
var fee float64 = 0
|
|
if config != nil && config.Base > 0 {
|
|
fee = float64(totalTrafficBytes) / (1 << 30) * config.Base
|
|
}
|
|
|
|
// 百分位
|
|
var percentile = 95
|
|
percentileBytes, err := SharedUserPlanBandwidthStatDAO.FindMonthlyPercentile(tx, userPlanId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), 0, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case serverconfigs.PlanPriceTypeBandwidth:
|
|
// 百分位
|
|
var percentile = 95
|
|
var config = plan.DecodeBandwidthPrice()
|
|
if config != nil {
|
|
percentile = config.Percentile
|
|
if percentile <= 0 {
|
|
percentile = 95
|
|
} else if percentile > 100 {
|
|
percentile = 100
|
|
}
|
|
}
|
|
percentileBytes, err := SharedUserPlanBandwidthStatDAO.FindMonthlyPercentile(tx, userPlanId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var mb = float64(percentileBytes) / (1 << 20)
|
|
var amount float64
|
|
if config != nil {
|
|
_, amount = config.LookupPrice(mb)
|
|
}
|
|
var fee = amount
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), 0, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// 默认计费方式
|
|
for partitionIndex := 0; partitionIndex < SharedServerBandwidthStatDAO.CountPartitions(); partitionIndex++ {
|
|
serverIds, err := SharedServerBandwidthStatDAO.FindDistinctServerIdsWithoutPlanAtPartition(tx, partitionIndex, month)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, serverId := range serverIds {
|
|
userId, err := SharedServerDAO.FindServerUserId(tx, serverId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if userId <= 0 {
|
|
continue
|
|
}
|
|
if !lists.ContainsInt64(userIds, userId) {
|
|
userIds = append(userIds, userId)
|
|
}
|
|
|
|
bandwidthAlgo, err := SharedUserDAO.FindUserBandwidthAlgoForFee(tx, userId, userPriceConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 总流量
|
|
totalTrafficBytes, err := SharedServerBandwidthStatDAO.SumMonthlyBytes(tx, serverId, month, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if userPriceConfig != nil && userPriceConfig.IsOn { // 默认计费方式
|
|
switch userPriceConfig.DefaultPriceType {
|
|
case serverconfigs.PlanPriceTypeTraffic:
|
|
var config = userPriceConfig.DefaultTrafficPriceConfig
|
|
var fee float64 = 0
|
|
if config != nil && config.Base > 0 {
|
|
fee = float64(totalTrafficBytes) / (1 << 30) * config.Base
|
|
}
|
|
|
|
// 百分位
|
|
var percentile = 95
|
|
percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg, true, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, 0, 0, totalTrafficBytes, percentileBytes, percentile, userPriceConfig.DefaultPriceType, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case serverconfigs.PlanPriceTypeBandwidth:
|
|
// 百分位
|
|
var percentile = 95
|
|
var config = userPriceConfig.DefaultBandwidthPriceConfig
|
|
if config != nil {
|
|
percentile = config.Percentile
|
|
if percentile <= 0 {
|
|
percentile = 95
|
|
} else if percentile > 100 {
|
|
percentile = 100
|
|
}
|
|
}
|
|
|
|
percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg, true, 10)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var mb = float64(percentileBytes) / (1 << 20)
|
|
var amount float64
|
|
if config != nil {
|
|
_, amount = config.LookupPrice(mb)
|
|
}
|
|
var fee = amount
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, 0, 0, totalTrafficBytes, percentileBytes, percentile, userPriceConfig.DefaultPriceType, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else { // 区域流量计费
|
|
var fee float64
|
|
|
|
for _, region := range regions {
|
|
var regionId = int64(region.Id)
|
|
var pricesMap = region.DecodePriceMap()
|
|
if len(pricesMap) == 0 {
|
|
continue
|
|
}
|
|
|
|
trafficBytes, err := SharedServerBandwidthStatDAO.SumServerMonthlyWithRegion(tx, serverId, regionId, month, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if trafficBytes == 0 {
|
|
continue
|
|
}
|
|
var itemId = SharedNodePriceItemDAO.SearchItemsWithBits(priceItems, trafficBytes*8)
|
|
if itemId == 0 {
|
|
continue
|
|
}
|
|
price, ok := pricesMap[itemId]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if price <= 0 {
|
|
continue
|
|
}
|
|
var regionFee = float64(trafficBytes) / 1000 / 1000 / 1000 * 8 * price
|
|
fee += regionFee
|
|
}
|
|
|
|
// 百分位
|
|
var percentile = 95
|
|
percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile, bandwidthAlgo == systemconfigs.BandwidthAlgoAvg, true, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, 0, 0, totalTrafficBytes, percentileBytes, percentile, "", fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 计算用户费用
|
|
for _, userId := range userIds {
|
|
if userId == 0 {
|
|
continue
|
|
}
|
|
amount, err := SharedServerBillDAO.SumUserMonthlyAmount(tx, userId, month)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lastDayInMonth, err := utils.LastDayInMonth(month)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = SharedUserBillDAO.CreateUserTrafficBill(tx, userId, BillTypeTrafficAndBandwidth, userconfigs.PricePeriodMonthly, "流量带宽费用", amount, month, month+"01", lastDayInMonth, month < timeutil.Format("Ym"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CalculatePrice 计算价格
|
|
func (this *UserBillDAO) CalculatePrice(tx *dbs.Tx, priceType string, trafficGB float64, bandwidthMB float64, regionId int64) (amount float64, hasRegionPrice bool, err error) {
|
|
if !userconfigs.IsValidPriceType(priceType) {
|
|
return 0, false, errors.New("invalid priceType '" + priceType + "'")
|
|
}
|
|
|
|
// 整体配置
|
|
priceConfig, err := SharedSysSettingDAO.ReadUserPriceConfig(tx)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
if priceConfig == nil {
|
|
// 如果尚未设置计费就是0
|
|
return 0, false, nil
|
|
}
|
|
|
|
switch priceType {
|
|
case userconfigs.PriceTypeTraffic:
|
|
if trafficGB <= 0 {
|
|
return 0, false, nil
|
|
}
|
|
|
|
if priceConfig.DefaultTrafficPriceConfig == nil {
|
|
return 0, false, nil
|
|
}
|
|
_, amount = priceConfig.DefaultTrafficPriceConfig.LookupPrice(trafficGB)
|
|
case userconfigs.PriceTypeBandwidth:
|
|
if bandwidthMB <= 0 {
|
|
return 0, false, nil
|
|
}
|
|
|
|
if priceConfig.DefaultBandwidthPriceConfig == nil {
|
|
return 0, false, nil
|
|
}
|
|
_, amount = priceConfig.DefaultBandwidthPriceConfig.LookupPrice(bandwidthMB)
|
|
}
|
|
|
|
// 区域配置
|
|
if (priceType == userconfigs.PriceTypeTraffic && priceConfig.DefaultTrafficPriceConfig != nil && priceConfig.DefaultTrafficPriceConfig.SupportRegions) || (priceType == userconfigs.PriceTypeBandwidth && priceConfig.DefaultBandwidthPriceConfig != nil && priceConfig.DefaultBandwidthPriceConfig.SupportRegions) {
|
|
if regionId > 0 {
|
|
nodeRegion, err := SharedNodeRegionDAO.FindEnabledNodeRegion(tx, regionId)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
if nodeRegion == nil {
|
|
return 0, false, errors.New("invalid 'regionId': " + types.String(regionId))
|
|
}
|
|
|
|
var prices = nodeRegion.DecodePriceMap()
|
|
|
|
if len(prices) > 0 {
|
|
allPriceItems, err := SharedNodePriceItemDAO.FindAllEnabledAndOnRegionPrices(tx, priceType)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
|
|
var bits int64
|
|
switch priceType {
|
|
case userconfigs.PriceTypeTraffic:
|
|
bits = int64(trafficGB * (1 << 33)) // GBytes
|
|
case userconfigs.PriceTypeBandwidth:
|
|
bits = int64(bandwidthMB * (1 << 20)) // Mbps
|
|
}
|
|
var itemId = SharedNodePriceItemDAO.SearchItemsWithBits(allPriceItems, bits)
|
|
if itemId > 0 {
|
|
price, ok := prices[itemId]
|
|
if ok {
|
|
switch priceType {
|
|
case userconfigs.PriceTypeTraffic:
|
|
amount = numberutils.FloorFloat64(price*trafficGB, 2)
|
|
hasRegionPrice = true
|
|
case userconfigs.PriceTypeBandwidth:
|
|
amount = numberutils.FloorFloat64(price*bandwidthMB, 2)
|
|
hasRegionPrice = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// CreateUserTrafficBill 创建流量账单
|
|
// month YYYYMM
|
|
// dayFrom YYYYMMDD
|
|
// dayTo YYYYMMDD
|
|
func (this *UserBillDAO) CreateUserTrafficBill(tx *dbs.Tx, userId int64, billType BillType, pricePeriod userconfigs.PricePeriod, description string, amount float64, month string, dayFrom string, dayTo string, canPay bool) (billId int64, err error) {
|
|
if !regexputils.YYYYMMDD.MatchString(dayFrom) {
|
|
return 0, errors.New("invalid dayFrom '" + dayFrom + "'")
|
|
}
|
|
if !regexputils.YYYYMMDD.MatchString(dayTo) {
|
|
return 0, errors.New("invalid dayTo")
|
|
}
|
|
|
|
dayFrom, err = utils.FixMonthMaxDay(dayFrom)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dayTo, err = utils.FixMonthMaxDay(dayTo)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if pricePeriod == userconfigs.PricePeriodDaily {
|
|
month = dayTo[:6]
|
|
}
|
|
|
|
// 账单是否存在
|
|
oldBillId, err := this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Attr("userId", userId).
|
|
Attr("type", billType).
|
|
Attr("pricePeriod", pricePeriod).
|
|
Attr("month", month).
|
|
Attr("dayFrom", dayFrom).
|
|
Attr("dayTo", dayTo).
|
|
FindInt64Col(0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if oldBillId > 0 {
|
|
err = this.Query(tx).
|
|
Pk(oldBillId).
|
|
Set("amount", amount).
|
|
Set("canPay", canPay).
|
|
Set("description", description).
|
|
UpdateQuickly()
|
|
return oldBillId, err
|
|
}
|
|
|
|
// 生成账单号码
|
|
code, err := this.GenerateBillCode(tx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var op = NewUserBillOperator()
|
|
op.UserId = userId
|
|
op.Type = billType
|
|
op.PricePeriod = pricePeriod
|
|
op.Description = description
|
|
op.Amount = amount
|
|
op.Month = month
|
|
op.DayFrom = dayFrom
|
|
op.DayTo = dayTo
|
|
op.Code = code
|
|
op.IsPaid = amount == 0
|
|
op.CanPay = canPay
|
|
op.CreatedDay = timeutil.Format("Ymd")
|
|
op.State = UserStateEnabled
|
|
billId, err = this.SaveInt64(tx, op)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// 发送邮件通知
|
|
if canPay {
|
|
// 不提示错误
|
|
if len(dayFrom) == 8 && len(dayTo) == 8 {
|
|
_ = SharedUserDAO.NotifyEmail(tx, userId, userconfigs.UserNotificationTypeBill, "[${product.name}]账单出账通知", "已为你生成"+dayFrom[:4]+"-"+dayFrom[4:6]+"-"+dayFrom[6:]+"到"+dayTo[:4]+"-"+dayTo[4:6]+"-"+dayTo[6:]+"账单,请登录查询详情 <a href=\"${url.home}/finance/bills\" target=\"_blank\">${url.home}/finance/bills</a>。")
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// FindUserLatestTrafficAndBandwidthBill 查找最近一次账单
|
|
func (this *UserBillDAO) FindUserLatestTrafficAndBandwidthBill(tx *dbs.Tx, userId int64) (*UserBill, error) {
|
|
one, err := this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Attr("userId", userId).
|
|
Attr("type", []string{BillTypeTraffic, BillTypeBandwidth}).
|
|
Result("id", "type", "month", "pricePeriod", "dayFrom", "dayTo", "canPay").
|
|
DescPk().
|
|
Find()
|
|
if err != nil || one == nil {
|
|
return nil, err
|
|
}
|
|
|
|
var userBill = one.(*UserBill)
|
|
if len(userBill.DayFrom) > 0 {
|
|
userBill.DayFrom, err = utils.FixMonthMaxDay(userBill.DayFrom)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if len(userBill.DayTo) > 0 {
|
|
userBill.DayTo, err = utils.FixMonthMaxDay(userBill.DayTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return userBill, nil
|
|
}
|
|
|
|
// FindUserBill 查找单个账单
|
|
func (this *UserBillDAO) FindUserBill(tx *dbs.Tx, billId int64) (*UserBill, error) {
|
|
one, err := this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Pk(billId).
|
|
Find()
|
|
if err != nil || one == nil {
|
|
return nil, err
|
|
}
|
|
return one.(*UserBill), nil
|
|
}
|
|
|
|
// FindUserBillWithCode 根据账单号查找单个账单
|
|
func (this *UserBillDAO) FindUserBillWithCode(tx *dbs.Tx, code string) (*UserBill, error) {
|
|
if len(code) == 0 {
|
|
return nil, nil
|
|
}
|
|
one, err := this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Attr("code", code).
|
|
Find()
|
|
if err != nil || one == nil {
|
|
return nil, err
|
|
}
|
|
return one.(*UserBill), nil
|
|
}
|
|
|
|
// CountAllUserBills 计算账单数量
|
|
func (this *UserBillDAO) CountAllUserBills(tx *dbs.Tx, isPaid int32, userId int64, month string, trafficRelated bool, minDailyBillDays int32, minMonthlyBillDays int32) (int64, error) {
|
|
var query = this.Query(tx)
|
|
query.State(UserBillStateEnabled)
|
|
if isPaid == 0 {
|
|
query.Attr("isPaid", 0)
|
|
} else if isPaid > 0 {
|
|
query.Attr("isPaid", 1)
|
|
}
|
|
if userId > 0 {
|
|
query.Attr("userId", userId)
|
|
}
|
|
if len(month) > 0 {
|
|
query.Attr("month", month)
|
|
}
|
|
if trafficRelated {
|
|
query.Attr("type", []string{BillTypeBandwidth, BillTypeTraffic, BillTypeTrafficAndBandwidth})
|
|
}
|
|
if minDailyBillDays > 0 || minMonthlyBillDays > 0 {
|
|
query.Attr("canPay", true)
|
|
|
|
if minDailyBillDays > 0 && minMonthlyBillDays > 0 {
|
|
var dailyTillDay = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -int(minDailyBillDays)))
|
|
var monthTillDay = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -int(minMonthlyBillDays)))
|
|
query.Where("((pricePeriod='daily' AND createdDay<:dailyTillDay) OR (pricePeriod='monthly' AND createdDay<:monthlyTillDay))")
|
|
query.Param("dailyTillDay", dailyTillDay)
|
|
query.Param("monthlyTillDay", monthTillDay)
|
|
} else if minDailyBillDays > 0 {
|
|
var dailyTillDay = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -int(minDailyBillDays)))
|
|
query.Where("(pricePeriod='daily' AND createdDay<:dailyTillDay)")
|
|
query.Param("dailyTillDay", dailyTillDay)
|
|
} else if minMonthlyBillDays > 0 {
|
|
var monthTillDay = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -int(minMonthlyBillDays)))
|
|
query.Where("(pricePeriod='monthly' AND createdDay<:monthlyTillDay)")
|
|
query.Param("monthlyTillDay", monthTillDay)
|
|
}
|
|
}
|
|
|
|
return query.Count()
|
|
}
|
|
|
|
// ListUserBills 列出单页账单
|
|
func (this *UserBillDAO) ListUserBills(tx *dbs.Tx, isPaid int32, userId int64, month string, offset, size int64) (result []*UserBill, err error) {
|
|
var query = this.Query(tx)
|
|
if isPaid == 0 {
|
|
query.Attr("isPaid", 0)
|
|
} else if isPaid > 0 {
|
|
query.Attr("isPaid", 1)
|
|
}
|
|
if userId > 0 {
|
|
query.Attr("userId", userId)
|
|
}
|
|
if len(month) > 0 {
|
|
query.Attr("month", month)
|
|
}
|
|
_, err = query.
|
|
State(UserBillStateEnabled).
|
|
Offset(offset).
|
|
Limit(size).
|
|
Slice(&result).
|
|
DescPk().
|
|
FindAll()
|
|
return
|
|
}
|
|
|
|
// FindUnpaidBills 查找未支付订单
|
|
func (this *UserBillDAO) FindUnpaidBills(tx *dbs.Tx, size int64) (result []*UserBill, err error) {
|
|
if size <= 0 {
|
|
size = 10000
|
|
}
|
|
_, err = this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Attr("isPaid", false).
|
|
Where("((pricePeriod='monthly' AND `month`<:month) OR (pricePeriod='daily' AND dayTo<:day))").
|
|
Param("month", timeutil.Format("Ym")).
|
|
Param("day", timeutil.Format("Ymd")).
|
|
Where("(createdDay IS NULL OR createdDay>:minDay)").
|
|
Param("minDay", timeutil.Format("Ymd", time.Now().AddDate(0, -3, 0))). // 只读取最近 N 个月的账单,防止无限期等待
|
|
Limit(size).
|
|
DescPk().
|
|
Slice(&result).
|
|
FindAll()
|
|
return
|
|
}
|
|
|
|
// ExistBill 检查是否有当月账单
|
|
func (this *UserBillDAO) ExistBill(tx *dbs.Tx, userId int64, billType BillType, month string) (bool, error) {
|
|
return this.Query(tx).
|
|
Attr("userId", userId).
|
|
Attr("month", month).
|
|
Attr("type", billType).
|
|
State(UserBillStateEnabled).
|
|
Exist()
|
|
}
|
|
|
|
// UpdateUserBillIsPaid 设置账单已支付
|
|
func (this *UserBillDAO) UpdateUserBillIsPaid(tx *dbs.Tx, billId int64, isPaid bool) error {
|
|
err := this.Query(tx).
|
|
Pk(billId).
|
|
Set("isPaid", isPaid).
|
|
UpdateQuickly()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新用户状态
|
|
userId, err := this.Query(tx).
|
|
Pk(billId).
|
|
Result("userId").
|
|
FindInt64Col(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if userId > 0 {
|
|
_, err = SharedUserDAO.RenewUserServersState(tx, userId)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BillTypeName 获取账单类型名称
|
|
func (this *UserBillDAO) BillTypeName(billType BillType) string {
|
|
switch billType {
|
|
case BillTypeTraffic:
|
|
return "流量计费"
|
|
case BillTypeBandwidth:
|
|
return "带宽计费"
|
|
case BillTypeTrafficAndBandwidth:
|
|
return "流量带宽计费"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GenerateBillCode 生成账单编号
|
|
func (this *UserBillDAO) GenerateBillCode(tx *dbs.Tx) (string, error) {
|
|
var code = timeutil.Format("YmdHis") + types.String(rands.Int(100000, 999999))
|
|
exists, err := this.Query(tx).
|
|
Attr("code", code).
|
|
Exist()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !exists {
|
|
return code, nil
|
|
}
|
|
return this.GenerateBillCode(tx)
|
|
}
|
|
|
|
// CheckUserBill 检查用户账单
|
|
func (this *UserBillDAO) CheckUserBill(tx *dbs.Tx, userId int64, billId int64) error {
|
|
if userId <= 0 || billId <= 0 {
|
|
return ErrNotFound
|
|
}
|
|
exists, err := this.Query(tx).
|
|
Pk(billId).
|
|
State(UserBillStateEnabled).
|
|
Attr("userId", userId).
|
|
Exist()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SumUnpaidUserBill 计算某个用户未支付总额
|
|
func (this *UserBillDAO) SumUnpaidUserBill(tx *dbs.Tx, userId int64) (float64, error) {
|
|
sum, err := this.Query(tx).
|
|
State(UserBillStateEnabled).
|
|
Attr("userId", userId).
|
|
Attr("isPaid", 0).
|
|
Sum("amount", 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return sum, nil
|
|
}
|