From e8c5287a7dd3b2e82e56611a5537101e18c6252c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 4 Feb 2016 14:11:03 +0200 Subject: [PATCH 1/8] started rounding --- engine/calldesc.go | 2 +- engine/timespans.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 2ead17ea5..4706e63be 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -75,7 +75,7 @@ var ( storageLogger LogStorage cdrStorage CdrStorage debitPeriod = 10 * time.Second - globalRoundingDecimals = 5 + globalRoundingDecimals = 6 historyScribe history.Scribe pubSubServer rpcclient.RpcClientConnection userService UserService diff --git a/engine/timespans.go b/engine/timespans.go index b7f530d59..6c65b67f2 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -347,12 +347,15 @@ func (ts *TimeSpan) createIncrementsSlice() { ts.Increments = make([]*Increment, 0) // create rated units series _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) - // we will use the cost calculated cost and devide by nb of increments + // we will use the calculated cost and devide by nb of increments // because ts cost is rounded //incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds() nbIncrements := int(ts.GetDuration() / rateIncrement) incrementCost := ts.CalculateCost() / float64(nbIncrements) - incrementCost = utils.Round(incrementCost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) + // 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) for s := 0; s < nbIncrements; s++ { inc := &Increment{ Duration: rateIncrement, From d76006cf3f4b7abe9a5b4c90e71f069497616b0e Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Mar 2016 23:05:21 +0200 Subject: [PATCH 2/8] first rounding draft --- console/debit.go | 2 +- console/debit_fake.go | 67 ------------------ engine/account.go | 25 ------- engine/account_test.go | 31 +-------- engine/balances.go | 9 +++ engine/callcost.go | 50 ++++++++++++-- engine/calldesc.go | 142 ++++++++++++++++++++++++++------------- engine/calldesc_test.go | 57 ++++++++++++---- engine/responder.go | 37 +--------- engine/timespans.go | 31 ++++----- engine/timespans_test.go | 92 +++++++++++-------------- 11 files changed, 251 insertions(+), 292 deletions(-) delete mode 100644 console/debit_fake.go diff --git a/console/debit.go b/console/debit.go index bf5a2b185..19d8c308d 100644 --- a/console/debit.go +++ b/console/debit.go @@ -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} diff --git a/console/debit_fake.go b/console/debit_fake.go deleted file mode 100644 index 60c9e4efc..000000000 --- a/console/debit_fake.go +++ /dev/null @@ -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 -*/ - -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 -} diff --git a/engine/account.go b/engine/account.go index f33640cc9..87c1f9e3c 100644 --- a/engine/account.go +++ b/engine/account.go @@ -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 { diff --git a/engine/account_test.go b/engine/account_test.go index 3df23e059..cb87a69af 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -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()) diff --git a/engine/balances.go b/engine/balances.go index da0853858..2b75d7a49 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -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) diff --git a/engine/callcost.go b/engine/callcost.go index 85c8a3c4b..8ced3397f 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -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 diff --git a/engine/calldesc.go b/engine/calldesc.go index 022ce27fc..572261f8e 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -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, } } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 617a41e55..0ac51f88f 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -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() diff --git a/engine/responder.go b/engine/responder.go index a37c222f8..0fc5f6f36 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -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, diff --git a/engine/timespans.go b/engine/timespans.go index 6535a2a73..4f0a74de5 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -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, diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 225cba090..b797dff86 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -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}, }, }, }, From f71e14d8d452872c9d2c9c4f78037f38fe0a043a Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 3 Mar 2016 14:42:03 +0200 Subject: [PATCH 3/8] integrated rounding in session managers --- engine/callcost.go | 1 + engine/calldesc.go | 4 +-- engine/responder.go | 62 ++++++++++++++++++++++++++++++++++ sessionmanager/session.go | 22 ++++++------ sessionmanager/session_test.go | 3 ++ sessionmanager/smg_session.go | 20 +++++------ sessionmanager/smgeneric.go | 8 +++++ utils/consts.go | 1 + 8 files changed, 97 insertions(+), 24 deletions(-) diff --git a/engine/callcost.go b/engine/callcost.go index 8ced3397f..502bd0754 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -90,6 +90,7 @@ func (cc *CallCost) CreateCallDescriptor() *CallDescriptor { Subject: cc.Subject, Account: cc.Account, Destination: cc.Destination, + TOR: cc.TOR, } } diff --git a/engine/calldesc.go b/engine/calldesc.go index 572261f8e..12dd637cc 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -684,7 +684,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) cc.Round() rcd := cc.CreateCallDescriptor() rcd.Increments = cc.GetRoundIncrements() - rcd.Round() + rcd.RefundRounding() } //log.Printf("OUT CC: ", cc) return @@ -820,7 +820,7 @@ func (cd *CallDescriptor) RefundIncrements() error { return err } -func (cd *CallDescriptor) Round() error { +func (cd *CallDescriptor) RefundRounding() error { // get account list for locking // all must be locked in order to use cache accMap := make(utils.StringMap) diff --git a/engine/responder.go b/engine/responder.go index 0fc5f6f36..48f337ab2 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -233,6 +233,43 @@ func (rs *Responder) RefundIncrements(arg *CallDescriptor, reply *float64) (err return } +func (rs *Responder) RefundRounding(arg *CallDescriptor, reply *float64) (err error) { + if item, err := rs.getCache().Get(utils.REFUND_ROUND_CACHE_PREFIX + arg.CgrID + arg.RunID); err == nil && item != nil { + *reply = *(item.Value.(*float64)) + return item.Err + } + 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 { + *reply, err = rs.callMethod(arg, "Responder.RefundRounding") + } else { + err = arg.RefundRounding() + } + rs.getCache().Cache(utils.REFUND_ROUND_CACHE_PREFIX+arg.CgrID+arg.RunID, &cache2go.CacheItem{ + Value: reply, + Err: err, + }) + return +} + func (rs *Responder) GetMaxSessionTime(arg *CallDescriptor, reply *float64) (err error) { if arg.Subject == "" { arg.Subject = arg.Account @@ -683,6 +720,7 @@ type Connector interface { Debit(*CallDescriptor, *CallCost) error MaxDebit(*CallDescriptor, *CallCost) error RefundIncrements(*CallDescriptor, *float64) error + RefundRounding(*CallDescriptor, *float64) error GetMaxSessionTime(*CallDescriptor, *float64) error GetDerivedChargers(*utils.AttrDerivedChargers, *utils.DerivedChargers) error GetDerivedMaxSessionTime(*CDR, *float64) error @@ -714,6 +752,10 @@ func (rcc *RPCClientConnector) RefundIncrements(cd *CallDescriptor, resp *float6 return rcc.Client.Call("Responder.RefundIncrements", cd, resp) } +func (rcc *RPCClientConnector) RefundRounding(cd *CallDescriptor, resp *float64) error { + return rcc.Client.Call("Responder.RefundRounding", cd, resp) +} + func (rcc *RPCClientConnector) GetMaxSessionTime(cd *CallDescriptor, resp *float64) error { return rcc.Client.Call("Responder.GetMaxSessionTime", cd, resp) } @@ -829,6 +871,26 @@ func (cp ConnectorPool) RefundIncrements(cd *CallDescriptor, resp *float64) erro return utils.ErrTimedOut } +func (cp ConnectorPool) RefundRounding(cd *CallDescriptor, resp *float64) error { + for _, con := range cp { + c := make(chan error, 1) + var r float64 + + var timeout time.Duration + con.GetTimeout(0, &timeout) + + go func() { c <- con.RefundRounding(cd, &r) }() + select { + case err := <-c: + *resp = r + return err + case <-time.After(timeout): + // call timed out, continue + } + } + return utils.ErrTimedOut +} + func (cp ConnectorPool) GetMaxSessionTime(cd *CallDescriptor, resp *float64) error { for _, con := range cp { c := make(chan error, 1) diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 91fb952d2..0be6f663c 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -195,21 +195,12 @@ func (s *Session) Refund(lastCC *engine.CallCost, hangupTime time.Time) error { // show only what was actualy refunded (stopped in timespan) // utils.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { - cd := &engine.CallDescriptor{ - Direction: lastCC.Direction, - Tenant: lastCC.Tenant, - Category: lastCC.Category, - Subject: lastCC.Subject, - Account: lastCC.Account, - Destination: lastCC.Destination, - TOR: lastCC.TOR, - Increments: refundIncrements, - } + cd := lastCC.CreateCallDescriptor() + cd.Increments = refundIncrements cd.Increments.Compress() utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %+v", refundDuration, cd)) var response float64 - err := s.sessionManager.Rater().RefundIncrements(cd, &response) - if err != nil { + if err := s.sessionManager.Rater().RefundIncrements(cd, &response); err != nil { return err } } @@ -238,6 +229,13 @@ func (s *Session) SaveOperations() { } firstCC.Timespans.Compress() + firstCC.Round() + cd := firstCC.CreateCallDescriptor() + cd.Increments = firstCC.GetRoundIncrements() + var response float64 + if err := s.sessionManager.Rater().RefundRounding(cd, &response); err != nil { + utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + } var reply string err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{ CgrId: s.eventStart.GetCgrId(s.sessionManager.Timezone()), diff --git a/sessionmanager/session_test.go b/sessionmanager/session_test.go index c391ad41b..209577806 100644 --- a/sessionmanager/session_test.go +++ b/sessionmanager/session_test.go @@ -91,6 +91,9 @@ func (mc *MockConnector) RefundIncrements(cd *engine.CallDescriptor, reply *floa mc.refundCd = cd return nil } +func (mc *MockConnector) RefundRounding(cd *engine.CallDescriptor, reply *float64) error { + return nil +} func (mc *MockConnector) GetMaxSessionTime(*engine.CallDescriptor, *float64) error { return nil } func (mc *MockConnector) GetDerivedChargers(*utils.AttrDerivedChargers, *utils.DerivedChargers) error { return nil diff --git a/sessionmanager/smg_session.go b/sessionmanager/smg_session.go index d097b91e5..019cc5cbc 100644 --- a/sessionmanager/smg_session.go +++ b/sessionmanager/smg_session.go @@ -147,16 +147,8 @@ func (self *SMGSession) refund(refundDuration time.Duration) error { // show only what was actualy refunded (stopped in timespan) // utils.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { - cd := &engine.CallDescriptor{ - Direction: lastCC.Direction, - Tenant: lastCC.Tenant, - Category: lastCC.Category, - Subject: lastCC.Subject, - Account: lastCC.Account, - Destination: lastCC.Destination, - TOR: lastCC.TOR, - Increments: refundIncrements, - } + cd := lastCC.CreateCallDescriptor() + cd.Increments = refundIncrements cd.Increments.Compress() utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %s", initialRefundDuration, utils.ToJSON(cd))) var response float64 @@ -212,6 +204,14 @@ func (self *SMGSession) saveOperations() error { firstCC.Merge(cc) } firstCC.Timespans.Compress() + firstCC.Round() + cd := firstCC.CreateCallDescriptor() + cd.Increments = firstCC.GetRoundIncrements() + var response float64 + if err := self.rater.RefundRounding(cd, &response); err != nil { + return err + } + var reply string err := self.cdrsrv.LogCallCost(&engine.CallCostLog{ CgrId: self.eventStart.GetCgrId(self.timezone), diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index f611fb317..62736581c 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -277,6 +277,14 @@ func (self *SMGeneric) ChargeEvent(gev SMGenericEvent, clnt *rpc2.Client) (maxDu cc.Merge(ccSR) } } + cc.Round() + cd := cc.CreateCallDescriptor() + cd.Increments = cc.GetRoundIncrements() + var response float64 + if err := self.rater.RefundRounding(cd, &response); err != nil { + utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + } + var reply string if err := self.cdrsrv.LogCallCost(&engine.CallCostLog{ CgrId: gev.GetCgrId(self.timezone), diff --git a/utils/consts.go b/utils/consts.go index 579583ba8..a2324b8e0 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -234,6 +234,7 @@ const ( OSIPS_FLATSTORE = "opensips_flatstore" MAX_DEBIT_CACHE_PREFIX = "MAX_DEBIT_" REFUND_INCR_CACHE_PREFIX = "REFUND_INCR_" + REFUND_ROUND_CACHE_PREFIX = "REFUND_ROUND_" GET_SESS_RUNS_CACHE_PREFIX = "GET_SESS_RUNS_" LOG_CALL_COST_CACHE_PREFIX = "LOG_CALL_COSTS_" ALIAS_CONTEXT_RATING = "*rating" From ce8f7bf927c576152e0ebabc6deb7dffe0ca4e48 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Mar 2016 16:10:29 +0200 Subject: [PATCH 4/8] rounding unit tests --- engine/calldesc.go | 8 ++------ engine/calldesc_test.go | 37 ++++++++++++++++++++++++++++++++++++- engine/timespans.go | 2 +- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 12dd637cc..c70f8b4eb 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -777,7 +777,6 @@ func (cd *CallDescriptor) RefundIncrements() error { for _, increment := range cd.Increments { accMap[increment.BalanceInfo.AccountId] = true } - accountIDs := accMap.Slice() // start increment refunding loop _, err := Guardian.Guard(func() (interface{}, error) { accountsCache := make(map[string]*Account) @@ -816,7 +815,7 @@ func (cd *CallDescriptor) RefundIncrements() error { } } return 0, nil - }, 0, accountIDs...) + }, 0, accMap.Slice()...) return err } @@ -827,7 +826,6 @@ func (cd *CallDescriptor) RefundRounding() error { 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) @@ -851,14 +849,12 @@ func (cd *CallDescriptor) RefundRounding() error { 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...) + }, 0, accMap.Slice()...) return err } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 0ac51f88f..49b6cc26a 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -745,7 +745,7 @@ func TestMaxDebitUnknowDest(t *testing.T) { } } -func TestGetCostMaxDebitRoundingIssue(t *testing.T) { +func TestMaxDebitRoundingIssue(t *testing.T) { ap, _ := ratingStorage.GetActionPlan("TOPUP10_AT", false) for _, at := range ap.ActionTimings { at.accountIDs = ap.AccountIDs @@ -780,6 +780,41 @@ func TestGetCostMaxDebitRoundingIssue(t *testing.T) { } } +func TestDebitRoundingRefund(t *testing.T) { + ap, _ := ratingStorage.GetActionPlan("TOPUP10_AT", false) + for _, at := range ap.ActionTimings { + at.accountIDs = ap.AccountIDs + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "dy", + Account: "dy", + Destination: "0723123113", + TimeStart: time.Date(2016, 3, 4, 13, 50, 00, 0, time.UTC), + TimeEnd: time.Date(2016, 3, 4, 13, 53, 00, 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.Debit() + expected := 0.3 + if cc.Cost != expected || err != nil { + t.Log(utils.ToIJSON(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 { + t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err) + } +} + func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { ap, _ := ratingStorage.GetActionPlan("TOPUP10_AT", false) for _, at := range ap.ActionTimings { diff --git a/engine/timespans.go b/engine/timespans.go index 4f0a74de5..dea4af215 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,13 +33,13 @@ A unit in which a call will be split that has a specific price related interval type TimeSpan struct { TimeStart, TimeEnd time.Time Cost float64 - ratingInfo *RatingInfo RateInterval *RateInterval DurationIndex time.Duration // the call duration so far till TimeEnd Increments Increments RoundIncrements Increments MatchedSubject, MatchedPrefix, MatchedDestId, RatingPlanId string CompressFactor int + ratingInfo *RatingInfo } type Increment struct { From 47778f398d143135e7054c6205a14efbc091959a Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Mar 2016 20:56:09 +0200 Subject: [PATCH 5/8] integration tests fixes --- agents/dmtagent_it_test.go | 2 +- apier/v1/smgenericv1_it_test.go | 2 +- engine/callcost.go | 17 ++++++++++------- engine/calldesc.go | 9 ++++++--- engine/timespans.go | 2 +- sessionmanager/session.go | 13 ++++++++----- sessionmanager/smg_session.go | 13 ++++++++----- sessionmanager/smgeneric.go | 13 ++++++++----- 8 files changed, 43 insertions(+), 28 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 8ec97b1ba..12d467125 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -623,7 +623,7 @@ func TestDmtAgentCdrs(t *testing.T) { if cdrs[0].Usage != "610" { t.Errorf("Unexpected CDR Usage received, cdr: %+v ", cdrs[0]) } - if cdrs[0].Cost != 0.795 { + if cdrs[0].Cost != 0.7565 { t.Errorf("Unexpected CDR Cost received, cdr: %+v ", cdrs[0]) } } diff --git a/apier/v1/smgenericv1_it_test.go b/apier/v1/smgenericv1_it_test.go index 56c019d3e..6afd87eb4 100644 --- a/apier/v1/smgenericv1_it_test.go +++ b/apier/v1/smgenericv1_it_test.go @@ -151,7 +151,7 @@ func TestSMGV1GetMaxUsage(t *testing.T) { var maxTime float64 if err := smgV1Rpc.Call("SMGenericV1.GetMaxUsage", setupReq, &maxTime); err != nil { t.Error(err) - } else if maxTime != 2690 { + } else if maxTime != 2700 { t.Errorf("Calling ApierV2.MaxUsage got maxTime: %f", maxTime) } } diff --git a/engine/callcost.go b/engine/callcost.go index 502bd0754..fcfd59006 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -201,20 +201,23 @@ func (cc *CallCost) Round() { ts.RateInterval.Rating.RoundingMethod) correctionCost := roundedCost - cost //log.Print(cost, roundedCost, correctionCost) - roundInc := &Increment{ - Cost: correctionCost, - BalanceInfo: inc.BalanceInfo, + if correctionCost != 0 { + ts.RoundIncrement = &Increment{ + Cost: correctionCost, + BalanceInfo: inc.BalanceInfo, + } + totalCorrectionCost += correctionCost + ts.Cost += correctionCost } - 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...) + if ts.RoundIncrement != nil && ts.RoundIncrement.Cost != 0 { + roundIncrements = append(roundIncrements, ts.RoundIncrement) + } } return } diff --git a/engine/calldesc.go b/engine/calldesc.go index c70f8b4eb..ce2bd49e0 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -682,9 +682,12 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) } if cd.PerformRounding { cc.Round() - rcd := cc.CreateCallDescriptor() - rcd.Increments = cc.GetRoundIncrements() - rcd.RefundRounding() + roundIncrements := cc.GetRoundIncrements() + if len(roundIncrements) != 0 { + rcd := cc.CreateCallDescriptor() + rcd.Increments = roundIncrements + rcd.RefundRounding() + } } //log.Printf("OUT CC: ", cc) return diff --git a/engine/timespans.go b/engine/timespans.go index dea4af215..34aec558b 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -36,7 +36,7 @@ type TimeSpan struct { RateInterval *RateInterval DurationIndex time.Duration // the call duration so far till TimeEnd Increments Increments - RoundIncrements Increments + RoundIncrement *Increment MatchedSubject, MatchedPrefix, MatchedDestId, RatingPlanId string CompressFactor int ratingInfo *RatingInfo diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 0be6f663c..31942cfdf 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -230,11 +230,14 @@ func (s *Session) SaveOperations() { firstCC.Timespans.Compress() firstCC.Round() - cd := firstCC.CreateCallDescriptor() - cd.Increments = firstCC.GetRoundIncrements() - var response float64 - if err := s.sessionManager.Rater().RefundRounding(cd, &response); err != nil { - utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + roundIncrements := firstCC.GetRoundIncrements() + if len(roundIncrements) != 0 { + cd := firstCC.CreateCallDescriptor() + cd.Increments = roundIncrements + var response float64 + if err := s.sessionManager.Rater().RefundRounding(cd, &response); err != nil { + utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + } } var reply string err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{ diff --git a/sessionmanager/smg_session.go b/sessionmanager/smg_session.go index 019cc5cbc..7d6238ec5 100644 --- a/sessionmanager/smg_session.go +++ b/sessionmanager/smg_session.go @@ -205,11 +205,14 @@ func (self *SMGSession) saveOperations() error { } firstCC.Timespans.Compress() firstCC.Round() - cd := firstCC.CreateCallDescriptor() - cd.Increments = firstCC.GetRoundIncrements() - var response float64 - if err := self.rater.RefundRounding(cd, &response); err != nil { - return err + roundIncrements := firstCC.GetRoundIncrements() + if len(roundIncrements) != 0 { + cd := firstCC.CreateCallDescriptor() + cd.Increments = roundIncrements + var response float64 + if err := self.rater.RefundRounding(cd, &response); err != nil { + return err + } } var reply string diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go index 62736581c..f9e4e27a7 100644 --- a/sessionmanager/smgeneric.go +++ b/sessionmanager/smgeneric.go @@ -278,11 +278,14 @@ func (self *SMGeneric) ChargeEvent(gev SMGenericEvent, clnt *rpc2.Client) (maxDu } } cc.Round() - cd := cc.CreateCallDescriptor() - cd.Increments = cc.GetRoundIncrements() - var response float64 - if err := self.rater.RefundRounding(cd, &response); err != nil { - utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + roundIncrements := cc.GetRoundIncrements() + if len(roundIncrements) != 0 { + cd := cc.CreateCallDescriptor() + cd.Increments = roundIncrements + var response float64 + if err := self.rater.RefundRounding(cd, &response); err != nil { + utils.Logger.Err(fmt.Sprintf(" ERROR failed to refund rounding: %v", err)) + } } var reply string From 3123fed48052f8ff4357f81abff296975edf322f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Mar 2016 21:46:45 +0200 Subject: [PATCH 6/8] more integration tests fixes --- agents/dmtagent_it_test.go | 8 ++++---- general_tests/tutorial_local_test.go | 10 +++++----- sessionmanager/smg_it_test.go | 4 ++-- utils/utils_test.go | 13 +++++++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 12d467125..fa3463ed6 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -226,7 +226,7 @@ func TestDmtAgentSendCCRInit(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.484 + eAcntVal := 9.5008 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { @@ -265,7 +265,7 @@ func TestDmtAgentSendCCRUpdate(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.214 + eAcntVal := 9.2518 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { @@ -304,7 +304,7 @@ func TestDmtAgentSendCCRUpdate2(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 8.944000 + eAcntVal := 9.0028 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if utils.Round(acnt.BalanceMap[utils.MONETARY].GetTotalValue(), 5, utils.ROUNDING_MIDDLE) != eAcntVal { @@ -345,7 +345,7 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.205 + eAcntVal := 9.2435 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 991ee3428..9d67d1551 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -230,7 +230,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 1.3002 { + } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") @@ -266,7 +266,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 1.3002 { + } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart = time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) @@ -342,7 +342,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 0.327 { // + } else if cc.Cost != 0.3249 { // t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ @@ -357,7 +357,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 1.3002 { // + } else if cc.Cost != 1.3 { // t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ @@ -372,7 +372,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 0.354 { // + } else if cc.Cost != 0.3498 { // t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } } diff --git a/sessionmanager/smg_it_test.go b/sessionmanager/smg_it_test.go index f29f5a598..6dd2a282d 100644 --- a/sessionmanager/smg_it_test.go +++ b/sessionmanager/smg_it_test.go @@ -138,7 +138,7 @@ func TestSMGMonetaryRefund(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 8.699800 + eAcntVal := 8.70001 if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { @@ -268,7 +268,7 @@ func TestSMGMixedRefund(t *testing.T) { //var acnt *engine.Account //attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} eVoiceVal := 90.0 - eMoneyVal := 8.739 + eMoneyVal := 8.7399 if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.VOICE].GetTotalValue() != eVoiceVal || diff --git a/utils/utils_test.go b/utils/utils_test.go index adca2dfad..1edd0ada0 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -50,6 +50,19 @@ func TestRoundUp(t *testing.T) { } } +func TestRoundUpTwice(t *testing.T) { + result := Round(0.641666666667, 4, ROUNDING_UP) + expected := 0.6417 + if result != expected { + t.Errorf("Error rounding up: sould be %v was %v", expected, result) + } + result = Round(result, 4, ROUNDING_UP) + expected = 0.6417 + if result != expected { + t.Errorf("Error rounding up: sould be %v was %v", expected, result) + } +} + func TestRoundUpMiddle(t *testing.T) { result := Round(12.5, 0, ROUNDING_UP) expected := 13.0 From 25e8559568ca6e294187abd8fbba6d1c6e23985c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Mar 2016 21:53:26 +0200 Subject: [PATCH 7/8] small fixes --- agents/dmtagent_it_test.go | 2 +- general_tests/tutorial_local_test.go | 2 +- sessionmanager/smg_it_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index fa3463ed6..85cc3c3d6 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -345,7 +345,7 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.2435 + eAcntVal := 9.243500 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go index 9d67d1551..c48066596 100644 --- a/general_tests/tutorial_local_test.go +++ b/general_tests/tutorial_local_test.go @@ -194,7 +194,7 @@ func TestTutLocalGetCosts(t *testing.T) { } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) - } else if cc.Cost != 0.6425 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT + } else if cc.Cost != 0.6418 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") diff --git a/sessionmanager/smg_it_test.go b/sessionmanager/smg_it_test.go index 6dd2a282d..80df57462 100644 --- a/sessionmanager/smg_it_test.go +++ b/sessionmanager/smg_it_test.go @@ -138,7 +138,7 @@ func TestSMGMonetaryRefund(t *testing.T) { } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 8.70001 + eAcntVal := 8.700010 if err := smgRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { From ca0d29d605bad49c00d72548699afb0519337294 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 5 Mar 2016 11:23:03 +0200 Subject: [PATCH 8/8] GetTotalValue rounding --- agents/dmtagent_it_test.go | 2 +- engine/balances.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index 85cc3c3d6..4b014f85a 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -349,7 +349,7 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) { if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 - t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) + t.Errorf("Expected: %v, received: %v", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) } } diff --git a/engine/balances.go b/engine/balances.go index 2b75d7a49..f089c6253 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -617,6 +617,7 @@ func (bc Balances) GetTotalValue() (total float64) { total += b.GetValue() } } + total = utils.Round(total, globalRoundingDecimals, utils.ROUNDING_MIDDLE) return }