diff --git a/accounts/abstractbalance.go b/accounts/abstractbalance.go index 267099a6c..7ed609bbd 100644 --- a/accounts/abstractbalance.go +++ b/accounts/abstractbalance.go @@ -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 } diff --git a/accounts/abstractbalance_test.go b/accounts/abstractbalance_test.go index 8c1290110..9b1fc9d25 100644 --- a/accounts/abstractbalance_test.go +++ b/accounts/abstractbalance_test.go @@ -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) } } +*/ diff --git a/accounts/accounts.go b/accounts/accounts.go index 5c122029b..dbbc5b50c 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -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 } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 2fa71dc5f..c4e47f56f 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -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: " - 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)) + } + */ } /* diff --git a/accounts/concretebalance.go b/accounts/concretebalance.go index 1685cb05c..13a003714 100644 --- a/accounts/concretebalance.go +++ b/accounts/concretebalance.go @@ -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 diff --git a/accounts/concretebalance_test.go b/accounts/concretebalance_test.go index 62ed35836..d5335514e 100644 --- a/accounts/concretebalance_test.go +++ b/accounts/concretebalance_test.go @@ -18,19 +18,7 @@ along with this program. If not, see 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) } } +*/ diff --git a/accounts/libaccounts.go b/accounts/libaccounts.go index 763e73551..700b9af5a 100644 --- a/accounts/libaccounts.go +++ b/accounts/libaccounts.go @@ -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) } diff --git a/accounts/libaccounts_test.go b/accounts/libaccounts_test.go index 027e4b256..c5c262e4c 100644 --- a/accounts/libaccounts_test.go +++ b/accounts/libaccounts_test.go @@ -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) } diff --git a/engine/attributes.go b/engine/attributes.go index 0b1287312..59f1cba13 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -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) diff --git a/rates/librates.go b/rates/librates.go index 84e1a2e71..158851462 100644 --- a/rates/librates.go +++ b/rates/librates.go @@ -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 diff --git a/rates/rates.go b/rates/rates.go index 3bf53c00c..bd7c527ae 100644 --- a/rates/rates.go +++ b/rates/rates.go @@ -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 diff --git a/utils/apitpdata.go b/utils/apitpdata.go index d2d46342a..32e774bda 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -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 diff --git a/utils/consts.go b/utils/consts.go index 40204b325..4b26bf890 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -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" diff --git a/utils/librates_test.go b/utils/librates_test.go index 88478b8d8..601f92ddf 100644 --- a/utils/librates_test.go +++ b/utils/librates_test.go @@ -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) + } +}