mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-20 06:38:45 +05:00
Merge branch 'rounding'
This commit is contained in:
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
c := &CmdDebit{
|
||||
name: "debit",
|
||||
rpcMethod: "Responder.Debit",
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject", "DryRun"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012-2015 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
c := &CmdFakeDebit{
|
||||
name: "debit_fake",
|
||||
rpcMethod: "Responder.FakeDebit",
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdFakeDebit struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcParams(reset bool) interface{} {
|
||||
if reset || self.rpcParams == nil {
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) PostprocessRpcParams() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) RpcResult() interface{} {
|
||||
return &engine.CallCost{}
|
||||
}
|
||||
|
||||
func (self *CmdFakeDebit) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
@@ -532,31 +532,6 @@ func (ub *Account) GetDefaultMoneyBalance() *Balance {
|
||||
return defaultBalance
|
||||
}
|
||||
|
||||
func (ub *Account) refundIncrement(increment *Increment, cd *CallDescriptor, count bool) {
|
||||
var balance *Balance
|
||||
unitType := cd.TOR
|
||||
cc := cd.CreateCallCost()
|
||||
if increment.BalanceInfo.UnitBalanceUuid != "" {
|
||||
if balance = ub.BalanceMap[unitType].GetBalance(increment.BalanceInfo.UnitBalanceUuid); balance == nil {
|
||||
return
|
||||
}
|
||||
balance.AddValue(increment.Duration.Seconds())
|
||||
if count {
|
||||
ub.countUnits(-increment.Duration.Seconds(), unitType, cc, balance)
|
||||
}
|
||||
}
|
||||
// check money too
|
||||
if increment.BalanceInfo.MoneyBalanceUuid != "" {
|
||||
if balance = ub.BalanceMap[utils.MONETARY].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil {
|
||||
return
|
||||
}
|
||||
balance.AddValue(increment.Cost)
|
||||
if count {
|
||||
ub.countUnits(-increment.Cost, utils.MONETARY, cc, balance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scans the action trigers and execute the actions for which trigger is met
|
||||
func (acc *Account) ExecuteActionTriggers(a *Action) {
|
||||
if acc.executingTriggers {
|
||||
|
||||
@@ -1127,33 +1127,6 @@ func TestAccountUnitCountingOutboundInbound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountRefund(t *testing.T) {
|
||||
ub := &Account{
|
||||
BalanceMap: map[string]Balances{
|
||||
utils.MONETARY: Balances{
|
||||
&Balance{Uuid: "moneya", Value: 100},
|
||||
},
|
||||
utils.VOICE: Balances{
|
||||
&Balance{Uuid: "minutea", Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}},
|
||||
&Balance{Uuid: "minuteb", Value: 10, DestinationIDs: utils.StringMap{"RET": true}},
|
||||
},
|
||||
},
|
||||
}
|
||||
increments := Increments{
|
||||
&Increment{Cost: 2, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "", MoneyBalanceUuid: "moneya"}},
|
||||
&Increment{Cost: 2, Duration: 3 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minutea", MoneyBalanceUuid: "moneya"}},
|
||||
&Increment{Duration: 4 * time.Second, BalanceInfo: &BalanceInfo{UnitBalanceUuid: "minuteb", MoneyBalanceUuid: ""}},
|
||||
}
|
||||
for _, increment := range increments {
|
||||
ub.refundIncrement(increment, &CallDescriptor{TOR: utils.VOICE}, false)
|
||||
}
|
||||
if ub.BalanceMap[utils.MONETARY][0].GetValue() != 104 ||
|
||||
ub.BalanceMap[utils.VOICE][0].GetValue() != 13 ||
|
||||
ub.BalanceMap[utils.VOICE][1].GetValue() != 14 {
|
||||
t.Error("Error refunding money: ", ub.BalanceMap[utils.VOICE][1].GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitShared(t *testing.T) {
|
||||
cc := &CallCost{
|
||||
Tenant: "vdf",
|
||||
@@ -1422,7 +1395,7 @@ func TestDebitGenericBalance(t *testing.T) {
|
||||
if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testm" {
|
||||
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
|
||||
}
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.4999 ||
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 ||
|
||||
rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 {
|
||||
t.Logf("%+v", cc.Timespans[0].Increments[0])
|
||||
t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue())
|
||||
@@ -1465,7 +1438,7 @@ func TestDebitGenericBalanceWithRatingSubject(t *testing.T) {
|
||||
if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testm" {
|
||||
t.Error("Error setting balance id to increment: ", cc.Timespans[0])
|
||||
}
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.4999 ||
|
||||
if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 ||
|
||||
rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 {
|
||||
t.Logf("%+v", cc.Timespans[0].Increments[0])
|
||||
t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue())
|
||||
|
||||
@@ -357,6 +357,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
b.SubstractValue(amount)
|
||||
inc.BalanceInfo.UnitBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = nil
|
||||
inc.UnitInfo = &UnitInfo{cc.Destination, amount, cc.TOR}
|
||||
inc.Cost = 0
|
||||
inc.paid = true
|
||||
@@ -428,6 +429,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
cost, inc.Cost = 0.0, 0.0
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(cost, utils.MONETARY, cc, b)
|
||||
@@ -446,6 +448,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
b.SubstractValue(amount)
|
||||
inc.BalanceInfo.UnitBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
inc.BalanceInfo.RateInterval = nil
|
||||
inc.UnitInfo = &UnitInfo{cc.Destination, amount, cc.TOR}
|
||||
if cost != 0 {
|
||||
inc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid
|
||||
@@ -535,6 +538,9 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
amount, inc.Cost = 0.0, 0.0
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
if b.RatingSubject != "" {
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
}
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(amount, utils.MONETARY, cc, b)
|
||||
@@ -550,6 +556,9 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala
|
||||
cd.MaxCostSoFar += amount
|
||||
inc.BalanceInfo.MoneyBalanceUuid = b.Uuid
|
||||
inc.BalanceInfo.AccountId = ub.ID
|
||||
if b.RatingSubject != "" {
|
||||
inc.BalanceInfo.RateInterval = ts.RateInterval
|
||||
}
|
||||
inc.paid = true
|
||||
if count {
|
||||
ub.countUnits(amount, utils.MONETARY, cc, b)
|
||||
@@ -608,6 +617,7 @@ func (bc Balances) GetTotalValue() (total float64) {
|
||||
total += b.GetValue()
|
||||
}
|
||||
}
|
||||
total = utils.Round(total, globalRoundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user