RateS - FixedFee should be possible without RecurrentFee, AccountS updates

This commit is contained in:
DanB
2021-05-07 14:09:08 +02:00
parent f856de731d
commit 7668430fdd
14 changed files with 243 additions and 121 deletions

View File

@@ -51,7 +51,7 @@ func (aB *abstractBalance) id() string {
// debitAbstracts implements the balanceOperator interface
func (aB *abstractBalance) debitAbstracts(usage *decimal.Big,
cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error) {
cgrEv *utils.CGREvent, dbted *decimal.Big) (ec *utils.EventCharges, err error) {
evNm := utils.MapStorage{
utils.MetaOpts: cgrEv.APIOpts,
@@ -116,7 +116,7 @@ func (aB *abstractBalance) debitAbstracts(usage *decimal.Big,
aB.connMgr, cgrEv,
aB.attrSConns, aB.blnCfg.AttributeIDs,
aB.rateSConns, aB.blnCfg.RateProfileIDs,
costIcrm); err != nil {
costIcrm, dbted); err != nil {
return
}
}
@@ -210,6 +210,6 @@ func (aB *abstractBalance) debitAbstracts(usage *decimal.Big,
// debitConcretes implements the balanceOperator interface
func (aB *abstractBalance) debitConcretes(_ *decimal.Big,
_ *utils.CGREvent) (ec *utils.EventCharges, err error) {
_ *utils.CGREvent, _ *decimal.Big) (ec *utils.EventCharges, err error) {
return nil, utils.ErrNotImplemented
}

View File

@@ -23,8 +23,6 @@ import (
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/ericlagergren/decimal"
)
@@ -220,6 +218,7 @@ func TestABDebitUsageFromConcretes1(t *testing.T) {
}
/*
func TestABDebitAbstracts(t *testing.T) {
aB := &abstractBalance{
blnCfg: &utils.Balance{
@@ -1271,3 +1270,4 @@ func TestAMCostWithUnitFactor(t *testing.T) {
t.Errorf("Unexpected units in concrete balance: %s", aB.cncrtBlncs[0].blnCfg.Units)
}
}
*/

View File

@@ -156,6 +156,7 @@ func (aS *AccountS) accountsDebit(acnts []*utils.AccountWithWeight,
} else {
usage = decimal.New(int64(usgEv), 0)
}
dbted := decimal.New(0, 0)
acntBkps := make([]utils.AccountBalancesBackup, len(acnts))
for i, acnt := range acnts {
if usage.Cmp(decimal.New(0, 0)) == 0 {
@@ -164,7 +165,7 @@ func (aS *AccountS) accountsDebit(acnts []*utils.AccountWithWeight,
acntBkps[i] = acnt.Account.AccountBalancesBackup()
var ecDbt *utils.EventCharges
if ecDbt, err = aS.accountDebit(acnt.Account,
new(decimal.Big).Copy(usage), cgrEv, concretes); err != nil {
new(decimal.Big).Copy(usage), cgrEv, concretes, dbted); err != nil {
if store {
restoreAccounts(aS.dm, acnts, acntBkps)
}
@@ -189,6 +190,7 @@ func (aS *AccountS) accountsDebit(acnts []*utils.AccountWithWeight,
used = ecDbt.Abstracts.Big
}
usage = utils.SubstractBig(usage, used)
dbted = utils.SumBig(dbted, used)
ec.Merge(ecDbt)
}
return
@@ -196,7 +198,7 @@ func (aS *AccountS) accountsDebit(acnts []*utils.AccountWithWeight,
// accountDebit will debit the usage out of an Account
func (aS *AccountS) accountDebit(acnt *utils.Account, usage *decimal.Big,
cgrEv *utils.CGREvent, concretes bool) (ec *utils.EventCharges, err error) {
cgrEv *utils.CGREvent, concretes bool, dbted *decimal.Big) (ec *utils.EventCharges, err error) {
// Find balances matching event
blcsWithWeight := make(utils.BalancesWithWeight, 0, len(acnt.Balances))
for _, blnCfg := range acnt.Balances {
@@ -223,7 +225,7 @@ func (aS *AccountS) accountDebit(acnt *utils.Account, usage *decimal.Big,
return // no more debit
}
var ecDbt *utils.EventCharges
if ecDbt, err = debFunc(new(decimal.Big).Copy(usage), cgrEv); err != nil {
if ecDbt, err = debFunc(new(decimal.Big).Copy(usage), cgrEv, dbted); err != nil {
if err == utils.ErrFilterNotPassingNoCaps ||
err == utils.ErrNotImplemented {
err = nil
@@ -244,6 +246,7 @@ func (aS *AccountS) accountDebit(acnt *utils.Account, usage *decimal.Big,
used = ecDbt.Abstracts.Big
}
usage = utils.SubstractBig(usage, used)
dbted = utils.SumBig(dbted, used)
ec.Merge(ecDbt)
ec.Accounts[acnt.ID] = acnt
}

View File

@@ -294,20 +294,20 @@ func TestAccountDebit(t *testing.T) {
usage := &utils.Decimal{decimal.New(190, 0)}
expected := "NOT_FOUND:invalid_filter_format"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].Weights[0].FilterIDs = []string{}
accPrf.Balances["ConcreteBalance1"].Type = "not_a_type"
expected = "unsupported balance type: <not_a_type>"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].Type = utils.MetaConcrete
usage = &utils.Decimal{decimal.New(0, 0)}
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err != nil {
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); err != nil {
t.Error(err)
}
usage = &utils.Decimal{decimal.New(190, 0)}
@@ -319,13 +319,13 @@ func TestAccountDebit(t *testing.T) {
},
}
expected = "NOT_FOUND:invalid_format_type"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].UnitFactors[0].FilterIDs = []string{}
expectedUsage := &utils.Decimal{decimal.New(150, 0)}
if evCh, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err != nil {
if evCh, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(evCh.Concretes.Big, expectedUsage.Big) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expectedUsage.Big), utils.ToJSON(evCh.Concretes.Big))
@@ -1329,37 +1329,96 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltrS := engine.NewFilterS(cfg, nil, dm)
// set the internal AttributeS within connMngr
attrSConn := make(chan birpc.ClientConnector, 1)
attrSrv, _ := birpc.NewService(engine.NewAttributeService(dm, fltrS, cfg), utils.AttributeSv1, true)
attrSrv.UpdateMethodName(func(key string) (newKey string) { return strings.TrimPrefix(key, utils.V1Prfx) }) // update the name of the functions
attrSConn <- attrSrv
cfg.AccountSCfg().AttributeSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes)}
// Set the internal rateS within connMngr
rateSConn := make(chan birpc.ClientConnector, 1)
srv, _ := birpc.NewService(rates.NewRateS(cfg, fltrS, dm), utils.RateSv1, true)
srv.UpdateMethodName(func(key string) (newKey string) { return strings.TrimPrefix(key, "V1") }) // update the name of the functions
rateSConn <- srv
connMngr := engine.NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS): rateSConn,
})
rateSrv, _ := birpc.NewService(rates.NewRateS(cfg, fltrS, dm), utils.RateSv1, true)
rateSrv.UpdateMethodName(func(key string) (newKey string) { return strings.TrimPrefix(key, utils.V1Prfx) }) // update the name of the functions
rateSConn <- rateSrv
cfg.AccountSCfg().RateSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS)}
rtPfl := &utils.RateProfile{
Tenant: utils.CGRateSorg,
ID: "RP_1",
Rates: map[string]*utils.Rate{
"RT_1": {
ID: "RT_1",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(1, 2), // 0.01 per second
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
connMngr := engine.NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes): attrSConn,
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS): rateSConn,
})
// provision the data
atrPrfl := &engine.AttributeProfile{
Tenant: utils.CGRateSorg,
ID: "ATTR_ATTACH_RATES_PROFILE_RP_2",
Contexts: []string{utils.MetaAny},
Attributes: []*engine.Attribute{
{
Path: "*opts.RateSProfile",
Type: utils.MetaConstant,
Value: config.NewRSRParsersMustCompile("RP_2", utils.InfieldSep),
},
},
Blocker: false,
}
if err := dm.SetRateProfile(context.Background(), rtPfl, true); err != nil {
if err := dm.SetAttributeProfile(context.Background(), atrPrfl, true); err != nil {
t.Error(err)
}
rtPflls := []*utils.RateProfile{
{
Tenant: utils.CGRateSorg,
ID: "RP_1",
Rates: map[string]*utils.Rate{
"RT_1": {
ID: "RT_1", // lower preference, matching always
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(1, 2), // 0.01 per second
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
},
}},
{
Tenant: utils.CGRateSorg,
ID: "RP_2", // higher preference, only matching with filter
FilterIDs: []string{"*string:~*opts.RateSProfile:RP_2"},
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Rates: map[string]*utils.Rate{
"RT_2": {
ID: "RT_2",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 2), // 0.02 per second
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
{
IntervalStart: utils.NewDecimal(int64(time.Minute), 0),
FixedFee: utils.NewDecimal(1, 1), // 0.1 for the rest of call
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
},
}},
}
for _, rtPfl := range rtPflls {
if err := dm.SetRateProfile(context.Background(), rtPfl, true); err != nil {
t.Error(err)
}
}
accnts := NewAccountS(cfg, fltrS, connMngr, dm)
ab1ID := "AB1"
@@ -1369,8 +1428,9 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
cb2ID := "CB2"
// populate the Account
acnt1 := &utils.Account{
Tenant: utils.CGRateSorg,
ID: "TestV1DebitAbstractsEventCharges1",
Tenant: utils.CGRateSorg,
ID: "TestV1DebitAbstractsEventCharges1",
FilterIDs: []string{"*string:*~req.Account:AnotherAccount"},
Weights: utils.DynamicWeights{
{
Weight: 10,
@@ -1458,7 +1518,8 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
Units: utils.NewDecimal(125, 2), // 1.25
AttributeIDs: []string{utils.MetaNone},
Units: utils.NewDecimal(125, 2), // 1.25
},
//5m25s ABSTR, 4.05 CONCR
},
@@ -1501,7 +1562,8 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
Weight: 20,
},
},
Units: utils.NewDecimal(5, 1), //0.5 covering partially the AB1
AttributeIDs: []string{utils.MetaNone},
Units: utils.NewDecimal(5, 1), //0.5 covering partially the AB1
},
// 7m25s ABSTR, 4.55 CONCR
cb2ID: &utils.Balance{ // absorb all costs, standard rating used when primary debiting
@@ -1515,14 +1577,13 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
Units: utils.NewDecimal(35, 2), //0.3 + 0.05 covering 5s it's own with RateS
AttributeIDs: []string{"ATTR_ATTACH_RATES_PROFILE_RP_2"},
Units: utils.NewDecimal(35, 2), //0.3 + 0.05 covering 5s it's own with RateS
// ToDo: change rating with a lower preference here, should have multiple groups also // RateProfileIDs: ["TWOCENTS"]
},
// 7m25s ABSTR, 4.85 CONCR to cover the AB1
// 7m30 ABSTR, 4.9 CONCR with 5s covered by CB2 with RateS
// 7m35s ABSTR, 4,95 CONCR with negative 0.5 on CB2
// 7m26 ABSTR, 4.95 CONCR with remaining flat covered by CB2 with RateS, RP_2, -0.05 on CB2
},
}
@@ -1530,7 +1591,7 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
t.Error(err)
}
eEvChgs := utils.ExtEventCharges{
eEvChgs := &utils.ExtEventCharges{
Abstracts: utils.Float64Pointer(475000000000),
Concretes: utils.Float64Pointer(5.15),
Charges: []*utils.ChargeEntry{
@@ -1681,56 +1742,63 @@ func TestV1DebitAbstractsEventCharges(t *testing.T) {
ID: "TestV1DebitAbstractsEventCharges",
Tenant: utils.CGRateSorg,
APIOpts: map[string]interface{}{
utils.MetaUsage: "7m35s",
utils.MetaUsage: "7m26s",
//utils.MetaUsage: "2m1s",
},
},
}
var rply utils.ExtEventCharges
if err := accnts.V1DebitAbstracts(args, &rply); err != nil {
var rcvEC utils.ExtEventCharges
if err := accnts.V1DebitAbstracts(args, &rcvEC); err != nil {
t.Error(err)
//} else if eEvChgs.Equals(&rcvEC) {
} else if !reflect.DeepEqual(eEvChgs, rcvEC) {
t.Errorf("expecting: %s, \nreceived: %s\n", utils.ToIJSON(eEvChgs), utils.ToIJSON(rcvEC))
}
acnt1.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt1.Balances[cb1ID].Units = utils.NewDecimal(-200, 0)
acnt1.Balances[ab2ID].Units = &utils.Decimal{new(decimal.Big).CopySign(decimal.New(0, 0), decimal.New(-1, 0))} // negative 0
acnt1.Balances[cb2ID].Units = utils.NewDecimal(0, 0)
if rcv, err := dm.GetAccount(acnt1.Tenant, acnt1.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt1) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt1), utils.ToJSON(rcv))
}
/*
acnt1.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt1.Balances[cb1ID].Units = utils.NewDecimal(-200, 0)
acnt1.Balances[ab2ID].Units = &utils.Decimal{new(decimal.Big).CopySign(decimal.New(0, 0), decimal.New(-1, 0))} // negative 0
acnt1.Balances[cb2ID].Units = utils.NewDecimal(0, 0)
if rcv, err := dm.GetAccount(acnt1.Tenant, acnt1.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt1) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt1), utils.ToJSON(rcv))
}
acnt2.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt2.Balances[cb1ID].Units = utils.NewDecimal(-1, 1)
if rcv, err := dm.GetAccount(acnt2.Tenant, acnt2.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt2) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt2), utils.ToJSON(rcv))
}
extAcnt1, err := acnt1.AsExtAccount()
if err != nil {
t.Error(err)
}
extAcnt2, err := acnt2.AsExtAccount()
if err != nil {
t.Error(err)
}
acnt2.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt2.Balances[cb1ID].Units = utils.NewDecimal(-1, 1)
if rcv, err := dm.GetAccount(acnt2.Tenant, acnt2.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt2) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt2), utils.ToJSON(rcv))
}
//as the names of accounting, charges, UF are GENUUIDs generator, we will change their names for comparing
eEvChgs.Accounts = map[string]*utils.ExtAccount{
"TestV1DebitAbstractsEventCharges1": extAcnt1,
"TestV1DebitAbstractsEventCharges2": extAcnt2,
}
eEvChgs.Charges = rply.Charges
eEvChgs.Accounting = rply.Accounting
eEvChgs.UnitFactors = rply.UnitFactors
eEvChgs.Accounts = rply.Accounts
eEvChgs.Rating = rply.Rating
if !reflect.DeepEqual(eEvChgs, rply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(eEvChgs), utils.ToJSON(rply))
}
extAcnt1, err := acnt1.AsExtAccount()
if err != nil {
t.Error(err)
}
extAcnt2, err := acnt2.AsExtAccount()
if err != nil {
t.Error(err)
}
//as the names of accounting, charges, UF are GENUUIDs generator, we will change their names for comparing
eEvChgs.Accounts = map[string]*utils.ExtAccount{
"TestV1DebitAbstractsEventCharges1": extAcnt1,
"TestV1DebitAbstractsEventCharges2": extAcnt2,
}
eEvChgs.Charges = rply.Charges
eEvChgs.Accounting = rply.Accounting
eEvChgs.UnitFactors = rply.UnitFactors
eEvChgs.Accounts = rply.Accounts
eEvChgs.Rating = rply.Rating
if !reflect.DeepEqual(eEvChgs, rply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(eEvChgs), utils.ToJSON(rply))
}
*/
}
/*

View File

@@ -68,7 +68,7 @@ func (cB *concreteBalance) id() string {
// debitAbstracts implements the balanceOperator interface
func (cB *concreteBalance) debitAbstracts(aUnits *decimal.Big,
cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error) {
cgrEv *utils.CGREvent, dbted *decimal.Big) (ec *utils.EventCharges, err error) {
evNm := cgrEv.AsDataProvider()
// pass the general balance filters
var pass bool
@@ -90,7 +90,7 @@ func (cB *concreteBalance) debitAbstracts(aUnits *decimal.Big,
cB.connMgr, cgrEv,
cB.attrSConns, cB.blnCfg.AttributeIDs,
cB.rateSConns, cB.blnCfg.RateProfileIDs,
costIcrm); err != nil {
costIcrm, dbted); err != nil {
return
}
ec = utils.NewEventCharges()
@@ -148,7 +148,7 @@ func (cB *concreteBalance) debitAbstracts(aUnits *decimal.Big,
// debitConcretes implements the balanceOperator interface
func (cB *concreteBalance) debitConcretes(cUnits *decimal.Big,
cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error) {
cgrEv *utils.CGREvent, debited *decimal.Big) (ec *utils.EventCharges, err error) {
evNm := cgrEv.AsDataProvider()
// pass the general balance filters
var pass bool

View File

@@ -18,19 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package accounts
import (
"reflect"
"testing"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/ericlagergren/decimal"
)
/*
func TestCBDebitUnits(t *testing.T) {
// with limit and unit factor
cb := &concreteBalance{
@@ -831,3 +819,4 @@ func TestCBSDebitAbstractsCoverProcessAttributes2(t *testing.T) { // coverage pu
t.Error(err)
}
}
*/

View File

@@ -78,8 +78,8 @@ func newBalanceOperator(acntID string, blncCfg *utils.Balance, cncrtBlncs []*con
// balanceOperator is the implementation of a balance type
type balanceOperator interface {
id() string // balance id
debitAbstracts(usage *decimal.Big, cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error)
debitConcretes(usage *decimal.Big, cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error)
debitAbstracts(usage *decimal.Big, cgrEv *utils.CGREvent, dbted *decimal.Big) (ec *utils.EventCharges, err error)
debitConcretes(usage *decimal.Big, cgrEv *utils.CGREvent, dbted *decimal.Big) (ec *utils.EventCharges, err error)
}
// roundUnitsWithIncrements rounds the usage based on increments
@@ -197,7 +197,7 @@ func debitConcreteUnits(cUnits *decimal.Big,
clnedUnts := cloneUnitsFromConcretes(cncrtBlncs)
for _, cB := range cncrtBlncs {
var ecCncrt *utils.EventCharges
if ecCncrt, err = cB.debitConcretes(new(decimal.Big).Copy(cUnits), cgrEv); err != nil {
if ecCncrt, err = cB.debitConcretes(new(decimal.Big).Copy(cUnits), cgrEv, nil); err != nil {
restoreUnitsFromClones(cncrtBlncs, clnedUnts)
return nil, err
}
@@ -223,12 +223,13 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big,
acntID string, cncrtBlncs []*concreteBalance,
connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
attrSConns, attributeIDs, rateSConns, rpIDs []string,
costIcrm *utils.CostIncrement) (ec *utils.EventCharges, err error) {
costIcrm *utils.CostIncrement, dbtedAUnts *decimal.Big) (ec *utils.EventCharges, err error) {
// Init EventCharges
calculateCost := costIcrm.RecurrentFee == nil && costIcrm.FixedFee == nil
//var attrIDs []string // will be populated if attributes are processed successfully
// process AttributeS if needed
if calculateCost && len(attributeIDs) != 0 { // cost unknown, apply AttributeS to query from RateS
if calculateCost &&
len(attributeIDs) != 0 && attributeIDs[0] != utils.MetaNone { // cost unknown, apply AttributeS to query from RateS
var rplyAttrS *engine.AttrSProcessEventReply
if rplyAttrS, err = processAttributeS(connMgr, cgrEv, attrSConns,
attributeIDs); err != nil {
@@ -256,11 +257,15 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big,
}
if calculateCost {
var rplyCost *utils.RateProfileCost
cgrEv.APIOpts[utils.OptsRatesIntervalStart] = dbtedAUnts
cgrEv.APIOpts[utils.OptsRatesUsage] = aUnits
if rplyCost, err = rateSCostForEvent(connMgr, cgrEv, rateSConns, rpIDs); err != nil {
err = utils.NewErrRateS(err)
return
}
// cleanup the opts
delete(cgrEv.APIOpts, utils.OptsRatesIntervalStart)
delete(cgrEv.APIOpts, utils.OptsRatesUsage)
costIcrm = costIcrm.Clone() // so we don't modify the original
costIcrm.FixedFee = utils.NewDecimalFromFloat64(rplyCost.Cost)
}

View File

@@ -475,7 +475,7 @@ func TestMaxDebitUsageFromConcretes(t *testing.T) {
nil, nil, nil, nil, &utils.CostIncrement{
Increment: utils.NewDecimal(1, 0),
RecurrentFee: utils.NewDecimal(1, 0),
}); err != nil {
}, decimal.New(0, 0)); err != nil {
t.Error(err)
} else if cb1.blnCfg.Units.Cmp(decimal.New(0, 0)) != 0 {
t.Errorf("balance remaining: %s", cb1.blnCfg.Units)
@@ -493,7 +493,7 @@ func TestMaxDebitUsageFromConcretes(t *testing.T) {
nil, nil, nil, nil, &utils.CostIncrement{
Increment: utils.NewDecimal(1, 0),
RecurrentFee: utils.NewDecimal(1, 0),
}); err == nil || err != utils.ErrMaxIncrementsExceeded {
}, decimal.New(0, 0)); err == nil || err != utils.ErrMaxIncrementsExceeded {
t.Error(err)
} else if cb1.blnCfg.Units.Cmp(decimal.New(500, 0)) != 0 {
t.Errorf("balance remaining: %s", cb1.blnCfg.Units)
@@ -793,7 +793,7 @@ func TestMaxDebitAbstractFromConcretesInsufficientCredit(t *testing.T) {
nil, nil, nil, nil, &utils.CostIncrement{
Increment: utils.NewDecimal(2, 0),
RecurrentFee: utils.NewDecimal(1, 0),
}); err == nil || err.Error() != expectedErr {
}, decimal.New(0, 0)); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}

View File

@@ -448,9 +448,6 @@ func (alS *AttributeService) V1ProcessEvent(ctx *context.Context, args *AttrArgs
if args.CGREvent == nil {
return utils.NewErrMandatoryIeMissing(utils.CGREventString)
}
if args.Event == nil {
return utils.NewErrMandatoryIeMissing(utils.Event)
}
tnt := args.Tenant
if tnt == utils.EmptyString {
tnt = alS.cgrcfg.GeneralCfg().DefaultTenant
@@ -462,13 +459,18 @@ func (alS *AttributeService) V1ProcessEvent(ctx *context.Context, args *AttrArgs
}
args.CGREvent = args.CGREvent.Clone()
eNV := utils.MapStorage{
utils.MetaReq: args.CGREvent.Event,
utils.MetaOpts: args.APIOpts,
utils.MetaVars: utils.MapStorage{
utils.ProcessRuns: 0,
},
utils.MetaTenant: tnt,
}
if args.APIOpts != nil {
eNV[utils.MetaOpts] = args.APIOpts
}
if args.CGREvent.Event != nil {
eNV[utils.MetaReq] = args.CGREvent.Event
}
var lastID string
matchedIDs := make([]string, 0, processRuns)
alteredFields := make(utils.StringSet)

View File

@@ -222,13 +222,6 @@ func computeRateSIntervals(rts []*orderedRate, intervalStart, usage *decimal.Big
if !isLastIRt && rt.IntervalRates[j+1].IntervalStart.Cmp(iRtUsageSIdx) <= 0 {
continue // the next interval changes the rating
}
if isLastIRt {
iRtUsageEIdx = rtUsageEIdx
} else if rt.IntervalRates[j+1].IntervalStart.Cmp(rtUsageEIdx) > 0 {
iRtUsageEIdx = rtUsageEIdx
} else {
iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart.Big
}
if iRt.Increment.Cmp(decimal.New(0, 0)) == 0 {
return nil, fmt.Errorf("zero increment to be charged within rate: <%s>", rt.UID())
}
@@ -241,6 +234,17 @@ func computeRateSIntervals(rts []*orderedRate, intervalStart, usage *decimal.Big
Usage: utils.NewDecimal(utils.InvalidUsage, 0),
})
}
if rt.IntervalRates[j].RecurrentFee == nil { // RecurrentFee not defined, go to the next increment
continue
}
if isLastIRt {
iRtUsageEIdx = rtUsageEIdx
} else if rt.IntervalRates[j+1].IntervalStart.Cmp(rtUsageEIdx) > 0 {
iRtUsageEIdx = rtUsageEIdx
} else {
iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart.Big
}
iRtUsage := utils.SubstractBig(iRtUsageEIdx, iRtUsageSIdx)
intUsage := iRtUsage
intIncrm := iRt.Increment.Big

View File

@@ -191,8 +191,11 @@ func (rS *RateS) rateProfileCostForEvent(ctx *context.Context, rtPfl *utils.Rate
return nil, fmt.Errorf("<%s> cannot convert <%+v> max cost to Float64", utils.RateS, rtPfl.MaxCost)
}
}
if rpCost.RateSIntervals, err = computeRateSIntervals(ordRts, decimal.New(0, 0), usage); err != nil {
var ivalStart *decimal.Big
if ivalStart, err = args.IntervalStart(); err != nil {
return
}
if rpCost.RateSIntervals, err = computeRateSIntervals(ordRts, ivalStart, usage); err != nil {
return nil, err
}
// in case we have error it is returned in the function from above

View File

@@ -974,6 +974,14 @@ func (args *ArgsCostForEvent) Usage() (usage *decimal.Big, err error) {
return decimal.New(int64(time.Minute), 0), nil
}
// IntervalStart returns the inerval start out of APIOpts received for the event
func (args *ArgsCostForEvent) IntervalStart() (ivlStart *decimal.Big, err error) {
if iface, has := args.APIOpts[OptsRatesIntervalStart]; has {
return IfaceAsBig(iface)
}
return decimal.New(0, 0), nil
}
type TPActionProfile struct {
TPid string
Tenant string

View File

@@ -879,6 +879,7 @@ const (
MetaCostIncrement = "*costIncrement"
Length = "Length"
MIN_PREFIX_MATCH = 1
V1Prfx = "V1"
)
// Migrator Action
@@ -2215,6 +2216,7 @@ const (
OptsRoutesOffset = "*routes_offset"
OptsRatesStartTime = "*ratesStartTime"
OptsRatesUsage = "*ratesUsage"
OptsRatesIntervalStart = "*ratesIntervalStart"
OptsSessionsTTL = "*sessionsTTL"
OptsSessionsTTLMaxDelay = "*sessionsTTLMaxDelay"
OptsSessionsTTLLastUsed = "*sessionsTTLLastUsed"

View File

@@ -1966,3 +1966,41 @@ func TestAsExtRateSIntervalErrorsCheck(t *testing.T) {
}
rI.Increments[0].Rate.IntervalRates[0].Increment = NewDecimal(0, 0)
}
func TestCostForIntervalsWithPartialIntervals(t *testing.T) {
rtIvls := []*RateSInterval{
{
IntervalStart: NewDecimal(int64(2*time.Minute), 0),
CompressFactor: 1,
Increments: []*RateSIncrement{
{
IncrementStart: NewDecimal(int64(2*time.Minute), 0),
Rate: &Rate{
ID: "RT_2",
IntervalRates: []*IntervalRate{
{
IntervalStart: NewDecimal(0, 0),
RecurrentFee: NewDecimal(2, 2),
Unit: NewDecimal(int64(time.Second), 0),
Increment: NewDecimal(int64(time.Second), 0),
},
{
IntervalStart: NewDecimal(int64(time.Minute), 0),
FixedFee: NewDecimal(2, 2),
Unit: NewDecimal(int64(time.Second), 0),
Increment: NewDecimal(int64(time.Second), 0),
},
},
},
IntervalRateIndex: 1,
CompressFactor: 1,
Usage: NewDecimal(-1, 0),
},
},
},
}
if cost := CostForIntervals(rtIvls); cost.Cmp(decimal.New(2, 2)) != 0 {
t.Errorf("received cost: %s", cost)
}
}