diff --git a/accounts/abstractbalance.go b/accounts/abstractbalance.go
index 26dd6fe2b..1504467df 100644
--- a/accounts/abstractbalance.go
+++ b/accounts/abstractbalance.go
@@ -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
diff --git a/accounts/abstractbalance_test.go b/accounts/abstractbalance_test.go
new file mode 100644
index 000000000..2149ab0d2
--- /dev/null
+++ b/accounts/abstractbalance_test.go
@@ -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
+*/
+
+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)
+ }
+}
diff --git a/accounts/concretebalance.go b/accounts/concretebalance.go
index 84d3f37b9..67743969b 100644
--- a/accounts/concretebalance.go
+++ b/accounts/concretebalance.go
@@ -19,7 +19,6 @@ along with this program. If not, see
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
}
diff --git a/accounts/concretebalance_test.go b/accounts/concretebalance_test.go
index 1f051f71c..fec478d08 100644
--- a/accounts/concretebalance_test.go
+++ b/accounts/concretebalance_test.go
@@ -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)
}
+
}