Merge branch 'rounding'

This commit is contained in:
Radu Ioan Fericean
2016-03-05 11:33:41 +02:00
21 changed files with 426 additions and 331 deletions

View File

@@ -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])
}
}

View File

@@ -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)
}
}

View File

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

View File

@@ -1,67 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2012-2015 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/engine"
func init() {
c := &CmdFakeDebit{
name: "debit_fake",
rpcMethod: "Responder.FakeDebit",
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdFakeDebit struct {
name string
rpcMethod string
rpcParams *engine.CallDescriptor
clientArgs []string
*CommandExecuter
}
func (self *CmdFakeDebit) Name() string {
return self.name
}
func (self *CmdFakeDebit) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdFakeDebit) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdFakeDebit) PostprocessRpcParams() error {
return nil
}
func (self *CmdFakeDebit) RpcResult() interface{} {
return &engine.CallCost{}
}
func (self *CmdFakeDebit) ClientArgs() []string {
return self.clientArgs
}

View File

@@ -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 {

View File

@@ -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())

View File

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

View File

@@ -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

View File

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

View File

@@ -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()

View File

@@ -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)

View File

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

View File

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

View File

@@ -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())
}
}

View File

@@ -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("<SM> ERROR failed to refund rounding: %v", err))
}
}
var reply string
err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{
CgrId: s.eventStart.GetCgrId(s.sessionManager.Timezone()),

View File

@@ -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

View File

@@ -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 ||

View File

@@ -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),

View File

@@ -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("<SM> ERROR failed to refund rounding: %v", err))
}
}
var reply string
if err := self.cdrsrv.LogCallCost(&engine.CallCostLog{
CgrId: gev.GetCgrId(self.timezone),

View File

@@ -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"

View File

@@ -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