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}, }, }, },