AccountS - debitUsageFromConcrete implementation with tests

This commit is contained in:
DanB
2021-01-24 15:29:31 +01:00
parent 1ed0360f07
commit 7d084398e7
4 changed files with 186 additions and 62 deletions

View File

@@ -130,9 +130,9 @@ func (aB *abstractBalance) rateSCostForEvent(cgrEv *utils.CGREvent) (rplyCost *e
}
// debitUsageFromConcrete attempts to debit the usage out of concrete balances
// returns error if complete usage cannot be debitted
func (aB *abstractBalance) debitUsageFromConcrete(usage *utils.Decimal, costIcrm *utils.CostIncrement,
cgrEv *utils.CGREvent) (err error) {
// returns utils.ErrInsufficientCredit if complete usage cannot be debitted
func (aB *abstractBalance) debitUsageFromConcrete(cBs []*concreteBalance, usage *utils.Decimal,
costIcrm *utils.CostIncrement, cgrEv *utils.CGREvent) (err error) {
if costIcrm.RecurrentFee.Cmp(decimal.New(-1, 0)) == 0 &&
costIcrm.FixedFee == nil {
var rplyCost *engine.RateProfileCost
@@ -141,19 +141,36 @@ func (aB *abstractBalance) debitUsageFromConcrete(usage *utils.Decimal, costIcrm
}
costIcrm.FixedFee = utils.NewDecimalFromFloat64(rplyCost.Cost)
}
var tCost *utils.Decimal
var tCost *decimal.Big
if costIcrm.FixedFee != nil {
tCost = costIcrm.FixedFee
tCost = costIcrm.FixedFee.Big
}
// RecurrentFee is configured, used it with increments
if costIcrm.RecurrentFee.Cmp(decimal.New(-1, 0)) != 0 {
if costIcrm.RecurrentFee.Big.Cmp(decimal.New(-1, 0)) != 0 {
rcrntCost := utils.MultiplyBig(
utils.DivideBig(usage.Big, costIcrm.Increment.Big),
costIcrm.RecurrentFee.Big)
tCost = &utils.Decimal{utils.SumBig(tCost.Big, rcrntCost)}
if tCost == nil {
tCost = rcrntCost
} else {
tCost = utils.SumBig(tCost, rcrntCost)
}
}
fmt.Println(tCost)
return
for _, cB := range cBs {
ev := utils.MapStorage{
utils.MetaOpts: cgrEv.Opts,
utils.MetaReq: cgrEv.Event,
}
var dbted *utils.Decimal
if dbted, _, err = cB.debitUnits(&utils.Decimal{tCost}, cgrEv.Tenant, ev); err != nil {
return
}
tCost = utils.SubstractBig(tCost, dbted.Big)
if tCost.Cmp(decimal.New(0, 0)) <= 0 {
return // have debitted all, total is smaller or equal to 0
}
}
return utils.ErrInsufficientCredit
}
// debitUsage implements the balanceOperator interface

View File

@@ -0,0 +1,106 @@
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
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 accounts
import (
"testing"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func TestABDebitUsageFromConcrete(t *testing.T) {
cBs := []*concreteBalance{
{
blnCfg: &utils.Balance{
ID: "CB1",
Type: utils.MetaConcrete,
Opts: map[string]interface{}{
utils.MetaBalanceLimit: utils.NewDecimal(-200, 0),
},
UnitFactors: []*utils.UnitFactor{
{
Factor: utils.NewDecimal(100, 0), // EuroCents
},
},
Units: utils.NewDecimal(500, 0), // 500 EURcents
},
fltrS: new(engine.FilterS),
},
{
blnCfg: &utils.Balance{
ID: "CB2",
Type: utils.MetaConcrete,
Opts: map[string]interface{}{
utils.MetaBalanceLimit: utils.NewDecimal(-1, 0),
},
Units: utils.NewDecimal(125, 2),
},
fltrS: new(engine.FilterS),
},
}
// consume only from first balance
if err := new(abstractBalance).debitUsageFromConcrete(cBs,
utils.NewDecimal(int64(time.Duration(5*time.Minute)), 0),
&utils.CostIncrement{
Increment: utils.NewDecimal(int64(time.Duration(time.Minute)), 0),
RecurrentFee: utils.NewDecimal(1, 0)},
new(utils.CGREvent)); err != nil {
t.Error(err)
} else if cBs[0].blnCfg.Units.Compare(utils.NewDecimal(0, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", cBs[0].blnCfg.Units)
} else if cBs[1].blnCfg.Units.Compare(utils.NewDecimal(125, 2)) != 0 {
t.Errorf("Unexpected units in first balance: %s", cBs[1].blnCfg.Units)
}
// consume from second also, remaining in second
cBs[0].blnCfg.Units = utils.NewDecimal(500, 0)
cBs[1].blnCfg.Units = utils.NewDecimal(125, 2)
if err := new(abstractBalance).debitUsageFromConcrete(cBs,
utils.NewDecimal(int64(time.Duration(9*time.Minute)), 0),
&utils.CostIncrement{
Increment: utils.NewDecimal(int64(time.Duration(time.Minute)), 0),
RecurrentFee: utils.NewDecimal(1, 0)},
new(utils.CGREvent)); err != nil {
t.Error(err)
} else if cBs[0].blnCfg.Units.Compare(utils.NewDecimal(-200, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", cBs[0].blnCfg.Units)
} else if cBs[1].blnCfg.Units.Compare(utils.NewDecimal(-75, 2)) != 0 {
t.Errorf("Unexpected units in second balance: %s", cBs[1].blnCfg.Units)
}
// not enough balance
cBs[0].blnCfg.Units = utils.NewDecimal(500, 0)
cBs[1].blnCfg.Units = utils.NewDecimal(125, 2)
if err := new(abstractBalance).debitUsageFromConcrete(cBs,
utils.NewDecimal(int64(time.Duration(10*time.Minute)), 0),
&utils.CostIncrement{
Increment: utils.NewDecimal(int64(time.Duration(time.Minute)), 0),
RecurrentFee: utils.NewDecimal(1, 0)},
new(utils.CGREvent)); err == nil || err != utils.ErrInsufficientCredit {
t.Error(err)
} else if cBs[0].blnCfg.Units.Compare(utils.NewDecimal(-200, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", cBs[0].blnCfg.Units)
} else if cBs[1].blnCfg.Units.Compare(utils.NewDecimal(-1, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", cBs[1].blnCfg.Units)
}
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package accounts
import (
"fmt"
"time"
"github.com/cgrates/cgrates/engine"
@@ -113,8 +112,8 @@ func (cB *concreteBalance) debitUsage(usage *utils.Decimal, startTime time.Time,
}
// debitUnits is a direct debit of balance units
func (cB *concreteBalance) debitUnits(dUnts *utils.Decimal, incrm *utils.Decimal,
tnt string, ev utils.DataProvider) (dbted *utils.Decimal, uF *utils.UnitFactor, err error) {
func (cB *concreteBalance) debitUnits(dUnts *utils.Decimal, tnt string,
ev utils.DataProvider) (dbted *utils.Decimal, uF *utils.UnitFactor, err error) {
// pass the general balance filters
var pass bool
@@ -125,47 +124,48 @@ func (cB *concreteBalance) debitUnits(dUnts *utils.Decimal, incrm *utils.Decimal
}
// unitFactor
var hasUF bool
if uF, err = cB.unitFactor(tnt, ev); err != nil {
return
}
var hasUF bool
if uF != nil && uF.Factor.Cmp(decimal.New(1, 0)) != 0 {
dUnts = &utils.Decimal{utils.MultiplyBig(dUnts.Big, uF.Factor.Big)}
incrm = &utils.Decimal{utils.MultiplyBig(incrm.Big, uF.Factor.Big)}
hasUF = true
dUnts = &utils.Decimal{utils.MultiplyBig(dUnts.Big, uF.Factor.Big)}
}
blcVal := cB.blnCfg.Units
// balanceLimit
var hasLmt bool
blncLmt := cB.balanceLimit()
if blncLmt != nil && blncLmt.Big.Cmp(decimal.New(0, 0)) != 0 {
blcVal = &utils.Decimal{utils.SubstractBig(blcVal.Big, blncLmt.Big)}
cB.blnCfg.Units.Big = utils.SubstractBig(cB.blnCfg.Units.Big, blncLmt.Big)
hasLmt = true
}
if blcVal.Compare(dUnts) == -1 && blncLmt != nil { // balance smaller than debit
// will use special rounding to 0 since otherwise we go negative (ie: 0.05 as increment)
maxIncrm := &utils.Decimal{
decimal.WithContext(
decimal.Context{RoundingMode: decimal.ToZero}).Quo(blcVal.Big,
incrm.Big).RoundToInt()}
dUnts = utils.MultiplyDecimal(incrm, maxIncrm)
}
rmain := &utils.Decimal{utils.SubstractBig(blcVal.Big, dUnts.Big)}
if hasLmt {
rmain = &utils.Decimal{utils.SumBig(rmain.Big, blncLmt.Big)}
}
if hasUF {
dbted = &utils.Decimal{utils.DivideBig(dUnts.Big, uF.Factor.Big)}
if cB.blnCfg.Units.Compare(dUnts) <= 0 && blncLmt != nil { // balance smaller than debit and limited
dbted = &utils.Decimal{cB.blnCfg.Units.Big}
cB.blnCfg.Units.Big = blncLmt.Big
} else {
cB.blnCfg.Units.Big = utils.SubstractBig(cB.blnCfg.Units.Big, dUnts.Big)
if hasLmt { // put back the limit
cB.blnCfg.Units.Big = utils.SumBig(cB.blnCfg.Units.Big, blncLmt.Big)
}
dbted = dUnts
}
rmainFlt64, ok := rmain.Float64()
if !ok {
return nil, nil, fmt.Errorf("failed representing decimal <%s> as float64", rmain)
if hasUF {
dbted.Big = utils.DivideBig(dbted.Big, uF.Factor.Big)
}
return
}
// cloneUnitsFromConcretes returns cloned units from the concrete balances passed as parameters
func cloneUnitsFromConcretes(cBs []*concreteBalance) (clnUnits []*utils.Decimal) {
if cBs == nil {
return
}
clnUnits = make([]*utils.Decimal, len(cBs))
for i := range cBs {
clnUnits[i] = cBs[i].blnCfg.Units.Clone()
}
cB.blnCfg.Units = utils.NewDecimalFromFloat64(rmainFlt64)
return
}

View File

@@ -46,7 +46,7 @@ func TestCBDebitUnits(t *testing.T) {
fltrS: new(engine.FilterS),
}
toDebit := utils.NewDecimal(6, 0)
if dbted, uFctr, err := cb.debitUnits(toDebit, utils.NewDecimal(1, 0),
if dbted, uFctr, err := cb.debitUnits(toDebit,
"cgrates.org", utils.MapStorage{}); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(cb.blnCfg.UnitFactors[0], uFctr) {
@@ -54,8 +54,9 @@ func TestCBDebitUnits(t *testing.T) {
} else if dbted.Compare(toDebit) != 0 {
t.Errorf("debited: %s", dbted)
} else if cb.blnCfg.Units.Cmp(decimal.New(-100, 0)) != 0 {
t.Errorf("balance remaining: %f", cb.blnCfg.Units)
t.Errorf("balance remaining: %s", cb.blnCfg.Units)
}
//with increment and not enough balance
cb = &concreteBalance{
blnCfg: &utils.Balance{
@@ -64,20 +65,20 @@ func TestCBDebitUnits(t *testing.T) {
Opts: map[string]interface{}{
utils.MetaBalanceLimit: utils.NewDecimal(-1, 0),
},
Units: utils.NewDecimal(125, 2),
Units: utils.NewDecimal(125, 2), // 1.25
},
fltrS: new(engine.FilterS),
}
if dbted, _, err := cb.debitUnits(
utils.NewDecimal(25, 1), //2.5
utils.NewDecimal(1, 1), //0.1
toDebit = utils.NewDecimal(25, 1) //2.5
if dbted, _, err := cb.debitUnits(toDebit,
"cgrates.org", utils.MapStorage{}); err != nil {
t.Error(err)
} else if dbted.Cmp(decimal.New(22, 1)) != 0 { // only 1.2 is possible due to increment
t.Errorf("debited: %s, cmp: %v", dbted, dbted.Cmp(new(decimal.Big).SetFloat64(1.2)))
} else if cb.blnCfg.Units.Cmp(decimal.New(-95, 2)) != 0 {
t.Errorf("balance remaining: %f", cb.blnCfg.Units)
} else if dbted.Cmp(decimal.New(225, 2)) != 0 { // 2.25 debited
t.Errorf("debited: %s", dbted)
} else if cb.blnCfg.Units.Cmp(decimal.New(-1, 0)) != 0 {
t.Errorf("balance remaining: %s", cb.blnCfg.Units)
}
//with increment and unlimited balance
cb = &concreteBalance{
blnCfg: &utils.Balance{
@@ -86,20 +87,20 @@ func TestCBDebitUnits(t *testing.T) {
Opts: map[string]interface{}{
utils.MetaBalanceUnlimited: true,
},
Units: &utils.Decimal{decimal.New(125, 2)},
Units: utils.NewDecimal(125, 2), // 1.25
},
fltrS: new(engine.FilterS),
}
if dbted, _, err := cb.debitUnits(
utils.NewDecimal(25, 1), //2.5
utils.NewDecimal(1, 1), //0.1
toDebit = utils.NewDecimal(25, 1) // 2.5
if dbted, _, err := cb.debitUnits(toDebit,
"cgrates.org", utils.MapStorage{}); err != nil {
t.Error(err)
} else if dbted.Cmp(decimal.New(25, 1)) != 0 { // only 1.2 is possible due to increment
t.Errorf("debited: %s, cmp: %v", dbted, dbted.Cmp(new(decimal.Big).SetFloat64(1.2)))
} else if dbted.Cmp(decimal.New(25, 1)) != 0 { // debit more than available since we have unlimited
t.Errorf("debited: %s", dbted)
} else if cb.blnCfg.Units.Cmp(decimal.New(-125, 2)) != 0 {
t.Errorf("balance remaining: %f", cb.blnCfg.Units)
t.Errorf("balance remaining: %s", cb.blnCfg.Units)
}
//with increment and positive limit
cb = &concreteBalance{
blnCfg: &utils.Balance{
@@ -108,18 +109,18 @@ func TestCBDebitUnits(t *testing.T) {
Opts: map[string]interface{}{
utils.MetaBalanceLimit: utils.NewDecimal(5, 1), // 0.5 as limit
},
Units: &utils.Decimal{decimal.New(125, 2)},
Units: utils.NewDecimal(125, 2), // 1.25
},
fltrS: new(engine.FilterS),
}
if dbted, _, err := cb.debitUnits(
utils.NewDecimal(25, 1), //2.5
utils.NewDecimal(1, 1), //0.1
toDebit = utils.NewDecimal(25, 1) //2.5
if dbted, _, err := cb.debitUnits(toDebit,
"cgrates.org", utils.MapStorage{}); err != nil {
t.Error(err)
} else if dbted.Cmp(decimal.New(7, 1)) != 0 { // only 1.2 is possible due to increment
t.Errorf("debited: %s, cmp: %v", dbted, dbted.Cmp(new(decimal.Big).SetFloat64(1.2)))
} else if cb.blnCfg.Units.Cmp(decimal.New(55, 2)) != 0 {
t.Errorf("balance remaining: %f", cb.blnCfg.Units)
} else if dbted.Cmp(decimal.New(75, 2)) != 0 { // limit is 0.5
t.Errorf("debited: %s", dbted)
} else if cb.blnCfg.Units.Cmp(decimal.New(5, 1)) != 0 {
t.Errorf("balance remaining: %s", cb.blnCfg.Units)
}
}