mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-14 20:59:53 +05:00
AccountS - debitUsageFromConcrete implementation with tests
This commit is contained in:
@@ -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
|
||||
|
||||
106
accounts/abstractbalance_test.go
Normal file
106
accounts/abstractbalance_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user