mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
first rounding draft
This commit is contained in:
@@ -24,7 +24,7 @@ func init() {
|
||||
c := &CmdDebit{
|
||||
name: "debit",
|
||||
rpcMethod: "Responder.Debit",
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject", "DryRun"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012-2015 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
c := &CmdFakeDebit{
|
||||
name: "debit_fake",
|
||||
rpcMethod: "Responder.FakeDebit",
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdFakeDebit struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcParams(reset bool) interface{} {
|
||||
if reset || self.rpcParams == nil {
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) PostprocessRpcParams() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcResult() interface{} {
|
||||
return &engine.CallCost{}
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
@@ -532,31 +532,6 @@ func (ub *Account) GetDefaultMoneyBalance() *Balance {
|
||||
return defaultBalance
|
||||
}
|
||||
|
||||
func (ub *Account) refundIncrement(increment *Increment, cd *CallDescriptor, count bool) {
|
||||
var balance *Balance
|
||||
unitType := cd.TOR
|
||||
cc := cd.CreateCallCost()
|
||||
if increment.BalanceInfo.UnitBalanceUuid != "" {
|
||||
if balance = ub.BalanceMap[unitType].GetBalance(increment.BalanceInfo.UnitBalanceUuid); balance == nil {
|
||||
return
|
||||
}
|
||||
balance.AddValue(increment.Duration.Seconds())
|
||||
if count {
|
||||
ub.countUnits(-increment.Duration.Seconds(), unitType, cc, balance)
|
||||
}
|
||||
}
|
||||
// check money too
|
||||
if increment.BalanceInfo.MoneyBalanceUuid != "" {
|
||||
if balance = ub.BalanceMap[utils.MONETARY].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil {
|
||||
return
|
||||
}
|
||||
balance.AddValue(increment.Cost)
|
||||
if count {
|
||||
ub.countUnits(-increment.Cost, utils.MONETARY, cc, balance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scans the action trigers and execute the actions for which trigger is met
|
||||
func (acc *Account) ExecuteActionTriggers(a *Action) {
|
||||
if acc.executingTriggers {
|
||||
|
||||
@@ -1127,33 +1127,6 @@ func TestAccountUnitCountingOutboundInbound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountRefund(t *testing.T) {
|
||||
ub := &Account{
|
||||
BalanceMap: map[string]Balances{
|
||||
utils.MONETARY: Balances{
|
||||
&Balance{Uuid: "moneya", Value: 100},
|
||||
},
|
||||
utils.VOICE: Balances{
|
||||
&Balance{Uuid: "minutea", Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}},
|
||||
&Balance{Uuid: "minuteb", Value: 10, DestinationIDs: utils.StringMap{"RET": true}},
|
||||
},
|
||||
},
|
||||
}
|
||||
increments := Increments{
|
||||
&Increment{Cost: 2, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "", MoneyBalanceUuid: "moneya"}},
|
||||
&Increment{Cost: 2, Duration: 3 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minutea", MoneyBalanceUuid: "moneya"}},
|
||||
&Increment{Duration: 4 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minuteb", MoneyBalanceUuid: ""}},
|
||||
}
|
||||
for _, increment := range increments {
|
||||
ub.refundIncrement(increment, &CallDescriptor{TOR: utils.VOICE}, false)
|
||||
}
|
||||
if ub.BalanceMap[utils.MONETARY][0].GetValue() != 104 ||
|
||||
ub.BalanceMap[utils.VOICE][0].GetValue() != 13 ||
|
||||
ub.BalanceMap[utils.VOICE][1].GetValue() != 14 {
|
||||
t.Error("Error refunding money: ", ub.BalanceMap[utils.VOICE][1].GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitShared(t *testing.T) {
|
||||
cc := &CallCost{
|
||||
Tenant: "vdf",
|
||||
@@ -1422,7 +1395,7 @@ func TestDebitGenericBalance(t *testing.T) {
|
||||
if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testm" {
|
||||
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
|
||||
}
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.4999 ||
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 ||
|
||||
rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 {
|
||||
t.Logf("%+v", cc.Timespans[0].Increments[0])
|
||||
t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue())
|
||||
@@ -1465,7 +1438,7 @@ func TestDebitGenericBalanceWithRatingSubject(t *testing.T) {
|
||||
if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testm" {
|
||||
t.Error("Error setting balance id to increment: ", cc.Timespans[0])
|
||||
}
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.4999 ||
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 ||
|
||||
rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 {
|
||||
t.Logf("%+v", cc.Timespans[0].Increments[0])
|
||||
t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue())
|
||||
|
||||
@@ -357,6 +357,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
b.SubstractValue(amount)
|
||||
inc.BalanceInfo.UnitBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = nil
|
||||
inc.UnitInfo = &UnitInfo{cc.Destination, amount, cc.TOR}
|
||||
inc.Cost = 0
|
||||
inc.paid = true
|
||||
@@ -428,6 +429,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
cost, inc.Cost = 0.0, 0.0
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(cost, utils.MONETARY, cc, b)
|
||||
@@ -446,6 +448,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
b.SubstractValue(amount)
|
||||
inc.BalanceInfo.UnitBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = nil
|
||||
inc.UnitInfo = &UnitInfo{cc.Destination, amount, cc.TOR}
|
||||
if cost != 0 {
|
||||
inc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid
|
||||
@@ -535,6 +538,9 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
amount, inc.Cost = 0.0, 0.0
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
if b.RatingSubject != "" {
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
}
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(amount, utils.MONETARY, cc, b)
|
||||
@@ -550,6 +556,9 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
cd.MaxCostSoFar += amount
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
if b.RatingSubject != "" {
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
}
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(amount, utils.MONETARY, cc, b)
|
||||
|
||||
@@ -136,13 +136,12 @@ func (cc *CallCost) ToDataCost() (*DataCost, error) {
|
||||
dc.DataSpans[i].Increments = make([]*DataIncrement, len(ts.Increments))
|
||||
for j, incr := range ts.Increments {
|
||||
dc.DataSpans[i].Increments[j] = &DataIncrement{
|
||||
Amount: incr.Duration.Seconds(),
|
||||
Cost: incr.Cost,
|
||||
BalanceInfo: incr.BalanceInfo,
|
||||
BalanceRateInterval: incr.BalanceRateInterval,
|
||||
UnitInfo: incr.UnitInfo,
|
||||
CompressFactor: incr.CompressFactor,
|
||||
paid: incr.paid,
|
||||
Amount: incr.Duration.Seconds(),
|
||||
Cost: incr.Cost,
|
||||
BalanceInfo: incr.BalanceInfo,
|
||||
UnitInfo: incr.UnitInfo,
|
||||
CompressFactor: incr.CompressFactor,
|
||||
paid: incr.paid,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +181,43 @@ func (cc *CallCost) updateCost() {
|
||||
cc.Cost = cost
|
||||
}
|
||||
|
||||
func (cc *CallCost) Round() {
|
||||
if len(cc.Timespans) == 0 || cc.Timespans[0] == nil {
|
||||
return
|
||||
}
|
||||
var totalCorrectionCost float64
|
||||
for _, ts := range cc.Timespans {
|
||||
if len(ts.Increments) == 0 {
|
||||
continue // safe check
|
||||
}
|
||||
inc := ts.Increments[0]
|
||||
if inc.BalanceInfo.MoneyBalanceUuid == "" || inc.Cost == 0 {
|
||||
// this is a unit payied timespan, nothing to round
|
||||
continue
|
||||
}
|
||||
cost := ts.CalculateCost()
|
||||
roundedCost := utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals,
|
||||
ts.RateInterval.Rating.RoundingMethod)
|
||||
correctionCost := roundedCost - cost
|
||||
//log.Print(cost, roundedCost, correctionCost)
|
||||
roundInc := &Increment{
|
||||
Cost: correctionCost,
|
||||
BalanceInfo: inc.BalanceInfo,
|
||||
}
|
||||
totalCorrectionCost += correctionCost
|
||||
ts.Cost += correctionCost
|
||||
ts.RoundIncrements = append(ts.RoundIncrements, roundInc)
|
||||
}
|
||||
cc.Cost += totalCorrectionCost
|
||||
}
|
||||
|
||||
func (cc *CallCost) GetRoundIncrements() (roundIncrements Increments) {
|
||||
for _, ts := range cc.Timespans {
|
||||
roundIncrements = append(roundIncrements, ts.RoundIncrements...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cc *CallCost) MatchCCFilter(bf *BalanceFilter) bool {
|
||||
if bf == nil {
|
||||
return true
|
||||
|
||||
@@ -150,14 +150,16 @@ type CallDescriptor struct {
|
||||
TOR string // used unit balances selector
|
||||
ExtraFields map[string]string // Extra fields, mostly used for user profile matching
|
||||
// session limits
|
||||
MaxRate float64
|
||||
MaxRateUnit time.Duration
|
||||
MaxCostSoFar float64
|
||||
CgrID string
|
||||
RunID string
|
||||
ForceDuration bool // for Max debit if less than duration return err
|
||||
account *Account
|
||||
testCallcost *CallCost // testing purpose only!
|
||||
MaxRate float64
|
||||
MaxRateUnit time.Duration
|
||||
MaxCostSoFar float64
|
||||
CgrID string
|
||||
RunID string
|
||||
ForceDuration bool // for Max debit if less than duration return err
|
||||
PerformRounding bool // flag for rating info rounding
|
||||
DryRun bool
|
||||
account *Account
|
||||
testCallcost *CallCost // testing purpose only!
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) ValidateCallData() error {
|
||||
@@ -612,6 +614,7 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura
|
||||
}
|
||||
}
|
||||
totalDuration += incr.Duration
|
||||
//log.Print("INC: ", utils.ToJSON(incr))
|
||||
if totalDuration >= initialDuration {
|
||||
// we have enough, return
|
||||
//utils.Logger.Debug(fmt.Sprintf("2_INIT DUR %v, TOTAL DUR: %v", initialDuration, totalDuration))
|
||||
@@ -619,7 +622,8 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura
|
||||
}
|
||||
}
|
||||
}
|
||||
//utils.Logger.Debug(fmt.Sprintf("3_INIT DUR %v, TOTAL DUR: %v", initialDuration, totalDuration))
|
||||
utils.Logger.Debug(fmt.Sprintf("3_INIT DUR %v, TOTAL DUR: %v", initialDuration, totalDuration))
|
||||
|
||||
return utils.MinDuration(initialDuration, totalDuration), nil
|
||||
}
|
||||
|
||||
@@ -660,9 +664,6 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool)
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
if !dryRun {
|
||||
defer accountingStorage.SetAccount(account)
|
||||
}
|
||||
if cd.TOR == "" {
|
||||
cd.TOR = utils.VOICE
|
||||
}
|
||||
@@ -676,6 +677,15 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool)
|
||||
cc.updateCost()
|
||||
cc.UpdateRatedUsage()
|
||||
cc.Timespans.Compress()
|
||||
if !dryRun {
|
||||
accountingStorage.SetAccount(account)
|
||||
}
|
||||
if cd.PerformRounding {
|
||||
cc.Round()
|
||||
rcd := cc.CreateCallDescriptor()
|
||||
rcd.Increments = cc.GetRoundIncrements()
|
||||
rcd.Round()
|
||||
}
|
||||
//log.Printf("OUT CC: ", cc)
|
||||
return
|
||||
}
|
||||
@@ -689,26 +699,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) {
|
||||
} else {
|
||||
if memberIds, sgerr := account.GetUniqueSharedGroupMembers(cd); sgerr == nil {
|
||||
_, err = Guardian.Guard(func() (interface{}, error) {
|
||||
cc, err = cd.debit(account, false, true)
|
||||
return 0, err
|
||||
}, 0, memberIds.Slice()...)
|
||||
} else {
|
||||
return nil, sgerr
|
||||
}
|
||||
return cc, err
|
||||
}
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) FakeDebit() (cc *CallCost, err error) {
|
||||
cd.account = nil // make sure it's not cached
|
||||
// lock all group members
|
||||
if account, err := cd.getAccount(); err != nil || account == nil {
|
||||
utils.Logger.Err(fmt.Sprintf("Account: %s, not found", cd.GetAccountKey()))
|
||||
return nil, utils.ErrAccountNotFound
|
||||
} else {
|
||||
if memberIds, sgerr := account.GetUniqueSharedGroupMembers(cd); sgerr == nil {
|
||||
_, err = Guardian.Guard(func() (interface{}, error) {
|
||||
cc, err = cd.debit(account, true, true)
|
||||
cc, err = cd.debit(account, cd.DryRun, true)
|
||||
return 0, err
|
||||
}, 0, memberIds.Slice()...)
|
||||
} else {
|
||||
@@ -763,7 +754,8 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) {
|
||||
cd.TimeEnd = cd.TimeStart.Add(remainingDuration)
|
||||
cd.DurationIndex -= initialDuration - remainingDuration
|
||||
}
|
||||
cc, err = cd.debit(account, false, true)
|
||||
//log.Print("Remaining duration: ", remainingDuration)
|
||||
cc, err = cd.debit(account, cd.DryRun, true)
|
||||
//log.Print(balanceMap[0].Value, balanceMap[1].Value)
|
||||
return 0, err
|
||||
}, 0, memberIDs.Slice()...)
|
||||
@@ -777,21 +769,17 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) RefundIncrements() (left float64, err error) {
|
||||
cd.account = nil // make sure it's not cached
|
||||
func (cd *CallDescriptor) RefundIncrements() error {
|
||||
// get account list for locking
|
||||
// all must be locked in order to use cache
|
||||
accMap := make(map[string]struct{})
|
||||
var accountIDs []string
|
||||
accMap := make(utils.StringMap)
|
||||
cd.Increments.Decompress()
|
||||
for _, increment := range cd.Increments {
|
||||
accMap[increment.BalanceInfo.AccountId] = struct{}{}
|
||||
}
|
||||
for key := range accMap {
|
||||
accountIDs = append(accountIDs, key)
|
||||
accMap[increment.BalanceInfo.AccountId] = true
|
||||
}
|
||||
accountIDs := accMap.Slice()
|
||||
// start increment refunding loop
|
||||
Guardian.Guard(func() (interface{}, error) {
|
||||
_, err := Guardian.Guard(func() (interface{}, error) {
|
||||
accountsCache := make(map[string]*Account)
|
||||
for _, increment := range cd.Increments {
|
||||
account, found := accountsCache[increment.BalanceInfo.AccountId]
|
||||
@@ -808,11 +796,70 @@ func (cd *CallDescriptor) RefundIncrements() (left float64, err error) {
|
||||
continue
|
||||
}
|
||||
//utils.Logger.Info(fmt.Sprintf("Refunding increment %+v", increment))
|
||||
account.refundIncrement(increment, cd, true)
|
||||
var balance *Balance
|
||||
unitType := cd.TOR
|
||||
cc := cd.CreateCallCost()
|
||||
if increment.BalanceInfo.UnitBalanceUuid != "" {
|
||||
if balance = account.BalanceMap[unitType].GetBalance(increment.BalanceInfo.UnitBalanceUuid); balance == nil {
|
||||
return 0, nil
|
||||
}
|
||||
balance.AddValue(increment.Duration.Seconds())
|
||||
account.countUnits(-increment.Duration.Seconds(), unitType, cc, balance)
|
||||
}
|
||||
// check money too
|
||||
if increment.BalanceInfo.MoneyBalanceUuid != "" {
|
||||
if balance = account.BalanceMap[utils.MONETARY].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil {
|
||||
return 0, nil
|
||||
}
|
||||
balance.AddValue(increment.Cost)
|
||||
account.countUnits(-increment.Cost, utils.MONETARY, cc, balance)
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
return 0, nil
|
||||
}, 0, accountIDs...)
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) Round() error {
|
||||
// get account list for locking
|
||||
// all must be locked in order to use cache
|
||||
accMap := make(utils.StringMap)
|
||||
for _, inc := range cd.Increments {
|
||||
accMap[inc.BalanceInfo.AccountId] = true
|
||||
}
|
||||
accountIDs := accMap.Slice()
|
||||
// start increment refunding loop
|
||||
_, err := Guardian.Guard(func() (interface{}, error) {
|
||||
accountsCache := make(map[string]*Account)
|
||||
for _, increment := range cd.Increments {
|
||||
account, found := accountsCache[increment.BalanceInfo.AccountId]
|
||||
if !found {
|
||||
if acc, err := accountingStorage.GetAccount(increment.BalanceInfo.AccountId); err == nil && acc != nil {
|
||||
account = acc
|
||||
accountsCache[increment.BalanceInfo.AccountId] = account
|
||||
// will save the account only once at the end of the function
|
||||
defer accountingStorage.SetAccount(account)
|
||||
}
|
||||
}
|
||||
if account == nil {
|
||||
utils.Logger.Warning(fmt.Sprintf("Could not get the account to be refunded: %s", increment.BalanceInfo.AccountId))
|
||||
continue
|
||||
}
|
||||
cc := cd.CreateCallCost()
|
||||
if increment.BalanceInfo.MoneyBalanceUuid != "" {
|
||||
var balance *Balance
|
||||
if balance = account.BalanceMap[utils.MONETARY].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil {
|
||||
return 0, nil
|
||||
}
|
||||
//log.Print("BEFORE: ", balance.GetValue(), increment.Cost)
|
||||
balance.AddValue(-increment.Cost)
|
||||
//log.Print("AFTER: ", balance.GetValue(), increment.Cost)
|
||||
account.countUnits(increment.Cost, utils.MONETARY, cc, balance)
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}, 0, accountIDs...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cd *CallDescriptor) FlushCache() (err error) {
|
||||
@@ -855,7 +902,10 @@ func (cd *CallDescriptor) Clone() *CallDescriptor {
|
||||
FallbackSubject: cd.FallbackSubject,
|
||||
//RatingInfos: cd.RatingInfos,
|
||||
//Increments: cd.Increments,
|
||||
TOR: cd.TOR,
|
||||
TOR: cd.TOR,
|
||||
ForceDuration: cd.ForceDuration,
|
||||
PerformRounding: cd.PerformRounding,
|
||||
DryRun: cd.DryRun,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ func TestGetMaxSessiontWithBlocker(t *testing.T) {
|
||||
MaxCostSoFar: 0,
|
||||
}
|
||||
result, err := cd.GetMaxSessionDuration()
|
||||
expected := 985 * time.Second
|
||||
expected := 30 * time.Minute
|
||||
if result != expected || err != nil {
|
||||
t.Errorf("Expected %v was %v (%v)", expected, result, err)
|
||||
}
|
||||
@@ -630,7 +630,7 @@ func TestGetCostRoundingIssue(t *testing.T) {
|
||||
MaxCostSoFar: 0,
|
||||
}
|
||||
cc, err := cd.GetCost()
|
||||
expected := 0.39
|
||||
expected := 0.17
|
||||
if cc.Cost != expected || err != nil {
|
||||
t.Log(utils.ToIJSON(cc))
|
||||
t.Errorf("Expected %v was %+v", expected, cc)
|
||||
@@ -752,25 +752,27 @@ func TestGetCostMaxDebitRoundingIssue(t *testing.T) {
|
||||
at.Execute()
|
||||
}
|
||||
cd := &CallDescriptor{
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "dy",
|
||||
Account: "dy",
|
||||
Destination: "0723123113",
|
||||
TimeStart: time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
|
||||
TimeEnd: time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC),
|
||||
MaxCostSoFar: 0,
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "dy",
|
||||
Account: "dy",
|
||||
Destination: "0723123113",
|
||||
TimeStart: time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
|
||||
TimeEnd: time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC),
|
||||
MaxCostSoFar: 0,
|
||||
PerformRounding: true,
|
||||
}
|
||||
acc, err := accountingStorage.GetAccount("cgrates.org:dy")
|
||||
if err != nil || acc.BalanceMap[utils.MONETARY][0].Value != 1 {
|
||||
t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
|
||||
}
|
||||
|
||||
cc, err := cd.MaxDebit()
|
||||
expected := 0.39
|
||||
expected := 0.17
|
||||
if cc.Cost != expected || err != nil {
|
||||
t.Log(utils.ToIJSON(cc))
|
||||
t.Errorf("Expected %v was %+v", expected, cc)
|
||||
t.Errorf("Expected %v was %+v (%v)", expected, cc, err)
|
||||
}
|
||||
acc, err = accountingStorage.GetAccount("cgrates.org:dy")
|
||||
if err != nil || acc.BalanceMap[utils.MONETARY][0].Value != 1-expected {
|
||||
@@ -1402,6 +1404,35 @@ func TestCDDataGetCost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRefundIncrements(t *testing.T) {
|
||||
ub := &Account{
|
||||
ID: "test:ref",
|
||||
BalanceMap: map[string]Balances{
|
||||
utils.MONETARY: Balances{
|
||||
&Balance{Uuid: "moneya", Value: 100},
|
||||
},
|
||||
utils.VOICE: Balances{
|
||||
&Balance{Uuid: "minutea", Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}},
|
||||
&Balance{Uuid: "minuteb", Value: 10, DestinationIDs: utils.StringMap{"RET": true}},
|
||||
},
|
||||
},
|
||||
}
|
||||
accountingStorage.SetAccount(ub)
|
||||
increments := Increments{
|
||||
&Increment{Cost: 2, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "", MoneyBalanceUuid: "moneya", AccountId: ub.ID}},
|
||||
&Increment{Cost: 2, Duration: 3 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minutea", MoneyBalanceUuid: "moneya", AccountId: ub.ID}},
|
||||
&Increment{Duration: 4 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minuteb", MoneyBalanceUuid: "", AccountId: ub.ID}},
|
||||
}
|
||||
cd := &CallDescriptor{TOR: utils.VOICE, Increments: increments}
|
||||
cd.RefundIncrements()
|
||||
ub, _ = accountingStorage.GetAccount(ub.ID)
|
||||
if ub.BalanceMap[utils.MONETARY][0].GetValue() != 104 ||
|
||||
ub.BalanceMap[utils.VOICE][0].GetValue() != 13 ||
|
||||
ub.BalanceMap[utils.VOICE][1].GetValue() != 14 {
|
||||
t.Error("Error refunding money: ", ub.BalanceMap[utils.VOICE][1].GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
/*************** BENCHMARKS ********************/
|
||||
func BenchmarkStorageGetting(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
@@ -150,41 +150,6 @@ func (rs *Responder) Debit(arg *CallDescriptor, reply *CallCost) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *Responder) FakeDebit(arg *CallDescriptor, reply *CallCost) (err error) {
|
||||
if arg.Subject == "" {
|
||||
arg.Subject = arg.Account
|
||||
}
|
||||
// replace aliases
|
||||
if err := LoadAlias(
|
||||
&AttrMatchingAlias{
|
||||
Destination: arg.Destination,
|
||||
Direction: arg.Direction,
|
||||
Tenant: arg.Tenant,
|
||||
Category: arg.Category,
|
||||
Account: arg.Account,
|
||||
Subject: arg.Subject,
|
||||
Context: utils.ALIAS_CONTEXT_RATING,
|
||||
}, arg, utils.EXTRA_FIELDS); err != nil && err != utils.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
// replace user profile fields
|
||||
if err := LoadUserProfile(arg, utils.EXTRA_FIELDS); err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.Bal != nil {
|
||||
r, e := rs.getCallCost(arg, "Responder.FakeDebit")
|
||||
*reply, err = *r, e
|
||||
} else {
|
||||
r, e := arg.FakeDebit()
|
||||
if e != nil {
|
||||
return e
|
||||
} else if r != nil {
|
||||
*reply = *r
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *Responder) MaxDebit(arg *CallDescriptor, reply *CallCost) (err error) {
|
||||
if item, err := rs.getCache().Get(utils.MAX_DEBIT_CACHE_PREFIX + arg.CgrID + arg.RunID); err == nil && item != nil {
|
||||
*reply = *(item.Value.(*CallCost))
|
||||
@@ -259,7 +224,7 @@ func (rs *Responder) RefundIncrements(arg *CallDescriptor, reply *float64) (err
|
||||
if rs.Bal != nil {
|
||||
*reply, err = rs.callMethod(arg, "Responder.RefundIncrements")
|
||||
} else {
|
||||
*reply, err = arg.RefundIncrements()
|
||||
err = arg.RefundIncrements()
|
||||
}
|
||||
rs.getCache().Cache(utils.REFUND_INCR_CACHE_PREFIX+arg.CgrID+arg.RunID, &cache2go.CacheItem{
|
||||
Value: reply,
|
||||
|
||||
@@ -37,18 +37,18 @@ type TimeSpan struct {
|
||||
RateInterval *RateInterval
|
||||
DurationIndex time.Duration // the call duration so far till TimeEnd
|
||||
Increments Increments
|
||||
RoundIncrements Increments
|
||||
MatchedSubject, MatchedPrefix, MatchedDestId, RatingPlanId string
|
||||
CompressFactor int
|
||||
}
|
||||
|
||||
type Increment struct {
|
||||
Duration time.Duration
|
||||
Cost float64
|
||||
BalanceInfo *BalanceInfo // need more than one for units with cost
|
||||
BalanceRateInterval *RateInterval
|
||||
UnitInfo *UnitInfo
|
||||
CompressFactor int
|
||||
paid bool
|
||||
Duration time.Duration
|
||||
Cost float64
|
||||
BalanceInfo *BalanceInfo // need more than one for units with cost
|
||||
UnitInfo *UnitInfo
|
||||
CompressFactor int
|
||||
paid bool
|
||||
}
|
||||
|
||||
// Holds the minute information related to a specified timespan
|
||||
@@ -69,12 +69,14 @@ func (mi *UnitInfo) Equal(other *UnitInfo) bool {
|
||||
type BalanceInfo struct {
|
||||
UnitBalanceUuid string
|
||||
MoneyBalanceUuid string
|
||||
RateInterval *RateInterval
|
||||
AccountId string // used when debited from shared balance
|
||||
}
|
||||
|
||||
func (bi *BalanceInfo) Equal(other *BalanceInfo) bool {
|
||||
return bi.UnitBalanceUuid == other.UnitBalanceUuid &&
|
||||
bi.MoneyBalanceUuid == other.MoneyBalanceUuid &&
|
||||
reflect.DeepEqual(bi.RateInterval, other.RateInterval) &&
|
||||
bi.AccountId == other.AccountId
|
||||
}
|
||||
|
||||
@@ -213,11 +215,10 @@ func (tss *TimeSpans) Decompress() { // must be pointer receiver
|
||||
|
||||
func (incr *Increment) Clone() *Increment {
|
||||
nIncr := &Increment{
|
||||
Duration: incr.Duration,
|
||||
Cost: incr.Cost,
|
||||
BalanceRateInterval: incr.BalanceRateInterval,
|
||||
UnitInfo: incr.UnitInfo,
|
||||
BalanceInfo: incr.BalanceInfo,
|
||||
Duration: incr.Duration,
|
||||
Cost: incr.Cost,
|
||||
UnitInfo: incr.UnitInfo,
|
||||
BalanceInfo: incr.BalanceInfo,
|
||||
}
|
||||
return nIncr
|
||||
}
|
||||
@@ -226,7 +227,6 @@ func (incr *Increment) Equal(other *Increment) bool {
|
||||
return incr.Duration == other.Duration &&
|
||||
incr.Cost == other.Cost &&
|
||||
((incr.BalanceInfo == nil && other.BalanceInfo == nil) || incr.BalanceInfo.Equal(other.BalanceInfo)) &&
|
||||
((incr.BalanceRateInterval == nil && other.BalanceRateInterval == nil) || reflect.DeepEqual(incr.BalanceRateInterval, other.BalanceRateInterval)) &&
|
||||
((incr.UnitInfo == nil && other.UnitInfo == nil) || incr.UnitInfo.Equal(other.UnitInfo))
|
||||
}
|
||||
|
||||
@@ -352,10 +352,7 @@ func (ts *TimeSpan) createIncrementsSlice() {
|
||||
//incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds()
|
||||
nbIncrements := int(ts.GetDuration() / rateIncrement)
|
||||
incrementCost := ts.CalculateCost() / float64(nbIncrements)
|
||||
// no more rounding at increment leve as it deviates from intended cost
|
||||
// move rounding at highest point possible
|
||||
//incrementCost = utils.Round(incrementCost, ts.RateInterval.Rating.RoundingDecimals,
|
||||
// ts.RateInterval.Rating.RoundingMethod)
|
||||
incrementCost = utils.Round(incrementCost, globalRoundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
for s := 0; s < nbIncrements; s++ {
|
||||
inc := &Increment{
|
||||
Duration: rateIncrement,
|
||||
|
||||
@@ -742,7 +742,7 @@ func TestTSTimespanCreateIncrements(t *testing.T) {
|
||||
if len(ts.Increments) != 3 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20.07 {
|
||||
if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20.066667 {
|
||||
t.Error("Wrong second slice: ", ts.Increments[2].Cost)
|
||||
}
|
||||
}
|
||||
@@ -1510,39 +1510,34 @@ func TestTSIncrementsCompressDecompress(t *testing.T) {
|
||||
&TimeSpan{
|
||||
Increments: Increments{
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1562,39 +1557,34 @@ func TestTSMultipleIncrementsCompressDecompress(t *testing.T) {
|
||||
&TimeSpan{
|
||||
Increments: Increments{
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
&Increment{
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", "3"},
|
||||
BalanceRateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
Duration: time.Minute,
|
||||
Cost: 10.4,
|
||||
BalanceInfo: &BalanceInfo{"1", "2", &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, "3"},
|
||||
UnitInfo: &UnitInfo{"1", 2.3, utils.VOICE},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user