diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go
index c9c500421..d05035ce5 100644
--- a/agents/dmtagent_it_test.go
+++ b/agents/dmtagent_it_test.go
@@ -258,7 +258,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 {
@@ -297,7 +297,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 {
@@ -336,7 +336,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 {
@@ -377,11 +377,11 @@ func TestDmtAgentSendCCRTerminate(t *testing.T) {
}
var acnt *engine.Account
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"}
- eAcntVal := 9.205
+ 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
- t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue())
+ t.Errorf("Expected: %v, received: %v", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue())
}
}
@@ -655,7 +655,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/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..f089c6253 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)
@@ -608,6 +617,7 @@ func (bc Balances) GetTotalValue() (total float64) {
total += b.GetValue()
}
}
+ total = utils.Round(total, globalRoundingDecimals, utils.ROUNDING_MIDDLE)
return
}
diff --git a/engine/callcost.go b/engine/callcost.go
index 85c8a3c4b..fcfd59006 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,
}
}
@@ -136,13 +137,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 +182,46 @@ 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)
+ if correctionCost != 0 {
+ ts.RoundIncrement = &Increment{
+ Cost: correctionCost,
+ BalanceInfo: inc.BalanceInfo,
+ }
+ totalCorrectionCost += correctionCost
+ ts.Cost += correctionCost
+ }
+ }
+ cc.Cost += totalCorrectionCost
+}
+
+func (cc *CallCost) GetRoundIncrements() (roundIncrements Increments) {
+ for _, ts := range cc.Timespans {
+ if ts.RoundIncrement != nil && ts.RoundIncrement.Cost != 0 {
+ roundIncrements = append(roundIncrements, ts.RoundIncrement)
+ }
+ }
+ return
+}
+
func (cc *CallCost) MatchCCFilter(bf *BalanceFilter) bool {
if bf == nil {
return true
diff --git a/engine/calldesc.go b/engine/calldesc.go
index 9d6e81c1d..ce2bd49e0 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
@@ -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,18 @@ 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()
+ roundIncrements := cc.GetRoundIncrements()
+ if len(roundIncrements) != 0 {
+ rcd := cc.CreateCallDescriptor()
+ rcd.Increments = roundIncrements
+ rcd.RefundRounding()
+ }
+ }
//log.Printf("OUT CC: ", cc)
return
}
@@ -689,26 +702,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 +757,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 +772,16 @@ 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
}
// 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 +798,67 @@ 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
- }, 0, accountIDs...)
- return 0, err
+ return 0, nil
+ }, 0, accMap.Slice()...)
+ return err
+}
+
+func (cd *CallDescriptor) RefundRounding() 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
+ }
+ // 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
+ }
+ balance.AddValue(-increment.Cost)
+ account.countUnits(increment.Cost, utils.MONETARY, cc, balance)
+ }
+ }
+ return 0, nil
+ }, 0, accMap.Slice()...)
+ return err
}
func (cd *CallDescriptor) FlushCache() (err error) {
@@ -855,7 +901,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..49b6cc26a 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)
@@ -745,32 +745,69 @@ 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
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 {
+ t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
+ }
+}
+
+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 {
@@ -1402,6 +1439,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..48f337ab2 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,
@@ -268,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
@@ -718,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
@@ -749,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)
}
@@ -864,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/engine/timespans.go b/engine/timespans.go
index 58769dc5a..34aec558b 100644
--- a/engine/timespans.go
+++ b/engine/timespans.go
@@ -33,22 +33,22 @@ 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
+ RoundIncrement *Increment
MatchedSubject, MatchedPrefix, MatchedDestId, RatingPlanId string
CompressFactor int
+ ratingInfo *RatingInfo
}
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))
}
@@ -347,12 +347,12 @@ 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)
+ 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},
},
},
},
diff --git a/general_tests/tutorial_local_test.go b/general_tests/tutorial_local_test.go
index 991ee3428..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")
@@ -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/session.go b/sessionmanager/session.go
index 91fb952d2..31942cfdf 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,16 @@ func (s *Session) SaveOperations() {
}
firstCC.Timespans.Compress()
+ firstCC.Round()
+ 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{
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_it_test.go b/sessionmanager/smg_it_test.go
index f29f5a598..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.699800
+ eAcntVal := 8.700010
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/sessionmanager/smg_session.go b/sessionmanager/smg_session.go
index d097b91e5..7d6238ec5 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,17 @@ func (self *SMGSession) saveOperations() error {
firstCC.Merge(cc)
}
firstCC.Timespans.Compress()
+ firstCC.Round()
+ 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
err := self.cdrsrv.LogCallCost(&engine.CallCostLog{
CgrId: self.eventStart.GetCgrId(self.timezone),
diff --git a/sessionmanager/smgeneric.go b/sessionmanager/smgeneric.go
index f611fb317..f9e4e27a7 100644
--- a/sessionmanager/smgeneric.go
+++ b/sessionmanager/smgeneric.go
@@ -277,6 +277,17 @@ func (self *SMGeneric) ChargeEvent(gev SMGenericEvent, clnt *rpc2.Client) (maxDu
cc.Merge(ccSR)
}
}
+ cc.Round()
+ 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
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"
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