AccountS - debitUsageFromConcrete

This commit is contained in:
DanB
2021-01-29 20:22:03 +01:00
parent 7b208bd10c
commit ab378e7707
5 changed files with 82 additions and 73 deletions

View File

@@ -92,64 +92,6 @@ func (aB *abstractBalance) balanceLimit() (bL *utils.Decimal) {
return
}
// rateSCostForEvent will process the event with RateS in order to get the cost
func (aB *abstractBalance) rateSCostForEvent(cgrEv *utils.CGREvent) (rplyCost *engine.RateProfileCost, err error) {
if len(aB.rateSConns) == 0 {
return nil, utils.NewErrNotConnected(utils.RateS)
}
err = aB.connMgr.Call(aB.rateSConns, nil, utils.RateSv1CostForEvent,
&utils.ArgsCostForEvent{CGREvent: cgrEv}, &rplyCost)
return
}
// debitUsageFromConcrete attempts to debit the usage out of concrete balances
// returns utils.ErrInsufficientCredit if complete usage cannot be debitted
func (aB *abstractBalance) debitUsageFromConcrete(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
if rplyCost, err = aB.rateSCostForEvent(cgrEv); err != nil {
return
}
costIcrm.FixedFee = utils.NewDecimalFromFloat64(rplyCost.Cost)
}
var tCost *decimal.Big
if costIcrm.FixedFee != nil {
tCost = costIcrm.FixedFee.Big
}
// RecurrentFee is configured, used it with increments
if costIcrm.RecurrentFee.Big.Cmp(decimal.New(-1, 0)) != 0 {
rcrntCost := utils.MultiplyBig(
utils.DivideBig(usage.Big, costIcrm.Increment.Big),
costIcrm.RecurrentFee.Big)
if tCost == nil {
tCost = rcrntCost
} else {
tCost = utils.SumBig(tCost, rcrntCost)
}
}
clnedUnts := cloneUnitsFromConcretes(aB.cncrtBlncs)
for _, cB := range aB.cncrtBlncs {
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 {
restoreUnitsFromClones(aB.cncrtBlncs, clnedUnts)
return
}
tCost = utils.SubstractBig(tCost, dbted.Big)
if tCost.Cmp(decimal.New(0, 0)) <= 0 {
return // have debited all, total is smaller or equal to 0
}
}
// we could not debit all, put back what we have debited
restoreUnitsFromClones(aB.cncrtBlncs, clnedUnts)
return utils.ErrInsufficientCredit
}
// debitUsage implements the balanceOperator interface
func (aB *abstractBalance) debitUsage(usage *utils.Decimal,
cgrEv *utils.CGREvent) (dbted *utils.Decimal, ec *utils.EventCharges, err error) {
@@ -225,7 +167,8 @@ func (aB *abstractBalance) debitUsage(usage *utils.Decimal,
return nil, nil, utils.ErrMaxIncrementsExceeded
}
qriedUsage := usage.Big // so we can detect loops
if err = aB.debitUsageFromConcrete(usage, costIcrm, cgrEv); err != nil {
if err = debitUsageFromConcrete(aB.cncrtBlncs, usage, costIcrm, cgrEv,
aB.connMgr, aB.rateSConns, aB.blnCfg.RateProfileIDs); err != nil {
if err != utils.ErrInsufficientCredit {
return
}

View File

@@ -26,7 +26,7 @@ import (
"github.com/cgrates/cgrates/utils"
)
func TestABDebitUsageFromConcrete(t *testing.T) {
func TestDebitUsageFromConcrete(t *testing.T) {
aB := &abstractBalance{
cncrtBlncs: []*concreteBalance{
{
@@ -56,12 +56,12 @@ func TestABDebitUsageFromConcrete(t *testing.T) {
},
}}
// consume only from first balance
if err := aB.debitUsageFromConcrete(
if err := debitUsageFromConcrete(aB.cncrtBlncs,
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 {
new(utils.CGREvent), nil, nil, nil); err != nil {
t.Error(err)
} else if aB.cncrtBlncs[0].blnCfg.Units.Compare(utils.NewDecimal(0, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", aB.cncrtBlncs[0].blnCfg.Units)
@@ -73,12 +73,12 @@ func TestABDebitUsageFromConcrete(t *testing.T) {
aB.cncrtBlncs[0].blnCfg.Units = utils.NewDecimal(500, 0)
aB.cncrtBlncs[1].blnCfg.Units = utils.NewDecimal(125, 2)
if err := aB.debitUsageFromConcrete(
if err := debitUsageFromConcrete(aB.cncrtBlncs,
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 {
new(utils.CGREvent), nil, nil, nil); err != nil {
t.Error(err)
} else if aB.cncrtBlncs[0].blnCfg.Units.Compare(utils.NewDecimal(-200, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", aB.cncrtBlncs[0].blnCfg.Units)
@@ -90,12 +90,12 @@ func TestABDebitUsageFromConcrete(t *testing.T) {
aB.cncrtBlncs[0].blnCfg.Units = utils.NewDecimal(500, 0)
aB.cncrtBlncs[1].blnCfg.Units = utils.NewDecimal(125, 2)
if err := aB.debitUsageFromConcrete(
if err := debitUsageFromConcrete(aB.cncrtBlncs,
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 {
new(utils.CGREvent), nil, nil, nil); err == nil || err != utils.ErrInsufficientCredit {
t.Error(err)
} else if aB.cncrtBlncs[0].blnCfg.Units.Compare(utils.NewDecimal(500, 0)) != 0 {
t.Errorf("Unexpected units in first balance: %s", aB.cncrtBlncs[0].blnCfg.Units)

View File

@@ -52,14 +52,18 @@ func (cB *concreteBalance) costIncrement(tnt string, ev utils.DataProvider) (cos
} else if !pass {
continue
}
costIcrm = cIcrm.Clone() // need clone since we might modify
return
costIcrm = cIcrm
break
}
if costIcrm == nil {
costIcrm = new(utils.CostIncrement)
}
if costIcrm.Increment == nil {
costIcrm.Increment = utils.NewDecimal(1, 0)
}
if costIcrm.RecurrentFee == nil {
costIcrm.RecurrentFee = utils.NewDecimal(-1, 0)
}
// nothing matched, return default
costIcrm = &utils.CostIncrement{
Increment: &utils.Decimal{decimal.New(1, 0)},
RecurrentFee: &utils.Decimal{decimal.New(-1, 0)}}
return
}

View File

@@ -126,3 +126,63 @@ func processAttributeS(connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
attrArgs, &rplyEv)
return
}
// rateSCostForEvent will process the event with RateS in order to get the cost
func rateSCostForEvent(connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
rateSConns, rpIDs []string) (rplyCost *engine.RateProfileCost, err error) {
if len(rateSConns) == 0 {
return nil, utils.NewErrNotConnected(utils.RateS)
}
err = connMgr.Call(rateSConns, nil, utils.RateSv1CostForEvent,
&utils.ArgsCostForEvent{CGREvent: cgrEv, RateProfileIDs: rpIDs}, &rplyCost)
return
}
// debitUsageFromConcrete attempts to debit the usage out of concrete balances
// returns utils.ErrInsufficientCredit if complete usage cannot be debitted
func debitUsageFromConcrete(cncrtBlncs []*concreteBalance, usage *utils.Decimal,
costIcrm *utils.CostIncrement, cgrEv *utils.CGREvent,
connMgr *engine.ConnManager, rateSConns, rpIDs []string) (err error) {
if costIcrm.RecurrentFee.Cmp(decimal.New(-1, 0)) == 0 &&
costIcrm.FixedFee == nil {
var rplyCost *engine.RateProfileCost
if rplyCost, err = rateSCostForEvent(connMgr, cgrEv, rateSConns, rpIDs); err != nil {
return
}
costIcrm.FixedFee = utils.NewDecimalFromFloat64(rplyCost.Cost)
}
var tCost *decimal.Big
if costIcrm.FixedFee != nil {
tCost = costIcrm.FixedFee.Big
}
// RecurrentFee is configured, used it with increments
if costIcrm.RecurrentFee.Big.Cmp(decimal.New(-1, 0)) != 0 {
rcrntCost := utils.MultiplyBig(
utils.DivideBig(usage.Big, costIcrm.Increment.Big),
costIcrm.RecurrentFee.Big)
if tCost == nil {
tCost = rcrntCost
} else {
tCost = utils.SumBig(tCost, rcrntCost)
}
}
clnedUnts := cloneUnitsFromConcretes(cncrtBlncs)
for _, cB := range cncrtBlncs {
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 {
restoreUnitsFromClones(cncrtBlncs, clnedUnts)
return
}
tCost = utils.SubstractBig(tCost, dbted.Big)
if tCost.Cmp(decimal.New(0, 0)) <= 0 {
return // have debited all, total is smaller or equal to 0
}
}
// we could not debit all, put back what we have debited
restoreUnitsFromClones(cncrtBlncs, clnedUnts)
return utils.ErrInsufficientCredit
}

View File

@@ -1,3 +1,5 @@
// +build integration
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH