From 5a632207acbdf27aa40dd6ebda0bdf0e71a57e7b Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 3 Jan 2021 19:47:26 +0100 Subject: [PATCH] AccountS - abstractBalance with costIncrement, unitFactor, balanceLimit and partial debitUsage implementation --- accounts/absolutebalance.go | 51 ------- accounts/abstractbalance.go | 135 ++++++++++++++++++ accounts/concretebalance.go | 68 +++++---- ...counts_test.go => concretebalance_test.go} | 16 ++- accounts/libaccounts.go | 41 ------ config/accountscfg.go | 1 + utils/accountprofile.go | 54 +++++-- utils/chrgdaccount.go | 21 --- utils/eventcharges.go | 10 +- 9 files changed, 239 insertions(+), 158 deletions(-) delete mode 100644 accounts/absolutebalance.go create mode 100644 accounts/abstractbalance.go rename accounts/{libaccounts_test.go => concretebalance_test.go} (86%) delete mode 100644 utils/chrgdaccount.go diff --git a/accounts/absolutebalance.go b/accounts/absolutebalance.go deleted file mode 100644 index 963b9fd56..000000000 --- a/accounts/absolutebalance.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Real-time Online/Offline Charging System (OerS) 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 ( - "time" - - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/utils" - "github.com/ericlagergren/decimal" -) - -// newAbstractBalance constructs an abstractBalanceOperator -func newAbstractBalanceOperator(blnCfg *utils.Balance, cncrtBlncs []*concreteBalance, - fltrS *engine.FilterS, ralsConns []string) balanceOperator { - return &abstractBalance{blnCfg, cncrtBlncs, fltrS, ralsConns} -} - -// abstractBalance is the operator for *abstract balance type -type abstractBalance struct { - blnCfg *utils.Balance - cncrtBlncs []*concreteBalance // paying balances - fltrS *engine.FilterS - ralsConns []string -} - -// debitUsage implements the balanceOperator interface -func (ab *abstractBalance) debitUsage(usage *decimal.Big, startTime time.Time, - cgrEv *utils.CGREventWithOpts) (ec *utils.EventCharges, err error) { - //var uF *utils.UsageFactor - if _, _, err = usageWithFactor(ab.blnCfg, ab.fltrS, cgrEv, usage); err != nil { - return - } - return -} diff --git a/accounts/abstractbalance.go b/accounts/abstractbalance.go new file mode 100644 index 000000000..5a3ea9111 --- /dev/null +++ b/accounts/abstractbalance.go @@ -0,0 +1,135 @@ +/* +Real-time Online/Offline Charging System (OerS) 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 ( + "fmt" + "time" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/ericlagergren/decimal" +) + +// newAbstractBalance constructs an abstractBalanceOperator +func newAbstractBalanceOperator(blnCfg *utils.Balance, cncrtBlncs []*concreteBalance, + fltrS *engine.FilterS, ralsConns []string) balanceOperator { + return &abstractBalance{blnCfg, cncrtBlncs, fltrS, ralsConns} +} + +// abstractBalance is the operator for *abstract balance type +type abstractBalance struct { + blnCfg *utils.Balance + cncrtBlncs []*concreteBalance // paying balances + fltrS *engine.FilterS + ralsConns []string +} + +// costIncrement finds out the cost increment for the event +func (aB *abstractBalance) costIncrement(tnt string, ev utils.DataProvider) (costIcrm *utils.CostIncrement, err error) { + for _, cIcrm := range aB.blnCfg.CostIncrements { + var pass bool + if pass, err = aB.fltrS.Pass(tnt, cIcrm.FilterIDs, ev); err != nil { + return + } else if !pass { + continue + } + costIcrm = cIcrm.Clone() // need clone since we might modify + return + } + // nothing matched, return default + costIcrm = &utils.CostIncrement{ + Increment: decimal.New(1, 0), + RecurrentFee: decimal.New(-1, 0)} + + return +} + +// unitFactor returns the unitFactor for the event +func (aB *abstractBalance) unitFactor(tnt string, ev utils.DataProvider) (uF *utils.UnitFactor, err error) { + for _, uF = range aB.blnCfg.UnitFactors { + var pass bool + if pass, err = aB.fltrS.Pass(tnt, uF.FilterIDs, ev); err != nil { + return + } else if !pass { + continue + } + return + } + // nothing matched, return default + uF = &utils.UnitFactor{ + Factor: decimal.New(1, 0), + } + return +} + +// balanceLimit returns the balance's limit +func (aB *abstractBalance) balanceLimit() (bL *decimal.Big) { + if lmtIface, has := aB.blnCfg.Opts[utils.MetaBalanceLimit]; has { + bL = lmtIface.(*decimal.Big) + return + } + // nothing matched, return default + bL = decimal.New(0, 0) + return +} + +// debitUsage implements the balanceOperator interface +func (aB *abstractBalance) debitUsage(usage *decimal.Big, startTime time.Time, + cgrEv *utils.CGREventWithOpts) (ec *utils.EventCharges, err error) { + + evNm := utils.MapStorage{ + utils.MetaOpts: cgrEv.Opts, + utils.MetaReq: cgrEv.Event, + } + + // pass the general balance filters + var pass bool + if pass, err = aB.fltrS.Pass(cgrEv.CGREvent.Tenant, aB.blnCfg.FilterIDs, evNm); err != nil { + return + } else if !pass { + return nil, utils.ErrFilterNotPassingNoCaps + } + + // costIncrement + var costIcrm *utils.CostIncrement + if costIcrm, err = aB.costIncrement(cgrEv.CGREvent.Tenant, evNm); err != nil { + return + } + + blcVal := new(decimal.Big).SetFloat64(aB.blnCfg.Value) // FixMe without float64 + + // balanceLimit + var hasLmt bool + blncLmt := aB.balanceLimit() + if blncLmt.Cmp(decimal.New(0, 0)) != 0 { + blcVal = utils.SubstractBig(blcVal, blncLmt) + hasLmt = true + } + + // unitFactor + var uF *utils.UnitFactor + if uF, err = aB.unitFactor(cgrEv.CGREvent.Tenant, evNm); err != nil { + return + } + + fmt.Printf("costIcrm: %+v, blncLmt: %+v, hasLmt: %+v, uF: %+v", costIcrm, blncLmt, hasLmt, uF) + + return +} diff --git a/accounts/concretebalance.go b/accounts/concretebalance.go index 322aba6c2..fbc67784a 100644 --- a/accounts/concretebalance.go +++ b/accounts/concretebalance.go @@ -42,38 +42,39 @@ type concreteBalance struct { } // debit implements the balanceOperator interface -func (cb *concreteBalance) debitUsage(usage *decimal.Big, startTime time.Time, +func (cB *concreteBalance) debitUsage(usage *decimal.Big, startTime time.Time, cgrEv *utils.CGREventWithOpts) (ec *utils.EventCharges, err error) { - //var uF *utils.UsageFactor - if _, _, err = usageWithFactor(cb.blnCfg, cb.fltrS, cgrEv, usage); err != nil { - return - } - return -} -// debitUnits is a direct debit of balance units -func (cb *concreteBalance) debitUnits(dUnts *decimal.Big, incrm *decimal.Big, - cgrEv *utils.CGREventWithOpts) (dbted *decimal.Big, mtchedUF *utils.UnitFactor, err error) { - // *balanceLimit - blncLmt := decimal.New(0, 0) - if lmt, has := cb.blnCfg.Opts[utils.MetaBalanceLimit].(*decimal.Big); has { - blncLmt = lmt - } - blcVal := new(decimal.Big).SetFloat64(cb.blnCfg.Value) - var hasLmt bool - if blncLmt.Cmp(decimal.New(0, 0)) != 0 { - blcVal = utils.SubstractBig(blcVal, blncLmt) - hasLmt = true - } - // dynamic unit factor - fctr := decimal.New(1, 0) evNm := utils.MapStorage{ utils.MetaOpts: cgrEv.Opts, utils.MetaReq: cgrEv.Event, } - for _, uF := range cb.blnCfg.UnitFactors { + + // pass the general balance filters + var pass bool + if pass, err = cB.fltrS.Pass(cgrEv.CGREvent.Tenant, cB.blnCfg.FilterIDs, evNm); err != nil { + return + } else if !pass { + return nil, utils.ErrFilterNotPassingNoCaps + } + + return +} + +// debitUnits is a direct debit of balance units +func (cB *concreteBalance) debitUnits(dUnts *decimal.Big, incrm *decimal.Big, + cgrEv *utils.CGREventWithOpts) (dbted *decimal.Big, mtchedUF *utils.UnitFactor, err error) { + + evNm := utils.MapStorage{ + utils.MetaOpts: cgrEv.Opts, + utils.MetaReq: cgrEv.Event, + } + + // dynamic unit factor + fctr := decimal.New(1, 0) + for _, uF := range cB.blnCfg.UnitFactors { var pass bool - if pass, err = cb.fltrS.Pass(cgrEv.CGREvent.Tenant, uF.FilterIDs, evNm); err != nil { + if pass, err = cB.fltrS.Pass(cgrEv.CGREvent.Tenant, uF.FilterIDs, evNm); err != nil { return nil, nil, err } else if !pass { continue @@ -83,11 +84,24 @@ func (cb *concreteBalance) debitUnits(dUnts *decimal.Big, incrm *decimal.Big, break } var hasUF bool - if fctr.Cmp(decimal.New(1, 0)) != 0 { + if mtchedUF != nil && fctr.Cmp(decimal.New(1, 0)) != 0 { dUnts = utils.MultiplyBig(dUnts, fctr) incrm = utils.MultiplyBig(incrm, fctr) hasUF = true } + + blcVal := new(decimal.Big).SetFloat64(cB.blnCfg.Value) // FixMe without float64 + + // *balanceLimit + var hasLmt bool + blncLmt := decimal.New(0, 0) + if lmt, has := cB.blnCfg.Opts[utils.MetaBalanceLimit].(*decimal.Big); has { + blncLmt = lmt + } + if blncLmt.Cmp(decimal.New(0, 0)) != 0 { + blcVal = utils.SubstractBig(blcVal, blncLmt) + hasLmt = true + } if blcVal.Cmp(dUnts) == -1 { // balance smaller than debit maxIncrm := utils.DivideBig(blcVal, incrm).RoundToInt() dUnts = utils.MultiplyBig(incrm, maxIncrm) @@ -105,6 +119,6 @@ func (cb *concreteBalance) debitUnits(dUnts *decimal.Big, incrm *decimal.Big, if !ok { return nil, nil, fmt.Errorf("failed representing decimal <%s> as float64", rmain) } - cb.blnCfg.Value = rmainFlt64 + cB.blnCfg.Value = rmainFlt64 return } diff --git a/accounts/libaccounts_test.go b/accounts/concretebalance_test.go similarity index 86% rename from accounts/libaccounts_test.go rename to accounts/concretebalance_test.go index 1073d3677..ba924e524 100644 --- a/accounts/libaccounts_test.go +++ b/accounts/concretebalance_test.go @@ -59,22 +59,24 @@ func TestCBDebitUnits(t *testing.T) { //with increment and not enough balance cb = &concreteBalance{ blnCfg: &utils.Balance{ - ID: "TestCBDebitUnits", - Type: utils.MetaConcrete, + ID: "TestCBDebitUnits", + Type: utils.MetaConcrete, + Opts: map[string]interface{}{ + utils.MetaBalanceLimit: decimal.New(-1, 0), + }, Value: 1.25, }, fltrS: new(engine.FilterS), } + remBlnc, _ := new(decimal.Big).SetString("2.2") if dbted, _, err := cb.debitUnits( - new(decimal.Big).SetFloat64(1.5), + new(decimal.Big).SetFloat64(2.5), new(decimal.Big).SetFloat64(0.1), &utils.CGREventWithOpts{CGREvent: &utils.CGREvent{Tenant: "cgrates.org"}}); err != nil { t.Error(err) - // #FixMe: new(decimal.Big).SetFloat64(1.2) does not work :( - } else if dbted.Cmp(decimal.New(12, 1)) != 0 { // only 1.2 is possible due to increment - //} else if dbted.Cmp(new(decimal.Big).SetFloat64(1.2)) != 0 { + } else if dbted.Cmp(remBlnc) != 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.Value != 0.05 { + } else if cb.blnCfg.Value != -0.95 { t.Errorf("balance remaining: %f", cb.blnCfg.Value) } } diff --git a/accounts/libaccounts.go b/accounts/libaccounts.go index 2ccaa6137..7091c0bae 100644 --- a/accounts/libaccounts.go +++ b/accounts/libaccounts.go @@ -86,44 +86,3 @@ type balanceOperator interface { debitUsage(usage *decimal.Big, startTime time.Time, cgrEv *utils.CGREventWithOpts) (ec *utils.EventCharges, err error) } - -// usagewithFactor returns the usage considering also factor for the debit -// includes event filtering to avoid code duplication -func usageWithFactor(blnCfg *utils.Balance, fltrS *engine.FilterS, - cgrEv *utils.CGREventWithOpts, usage *decimal.Big) (resUsage *decimal.Big, mtchedUF *utils.UnitFactor, err error) { - resUsage = usage - fctr := decimal.New(1, 0) - if len(blnCfg.FilterIDs) == 0 && - len(blnCfg.UnitFactors) == 0 { - return - } - evNm := utils.MapStorage{ - utils.MetaOpts: cgrEv.Opts, - utils.MetaReq: cgrEv.Event, - } - // match the general balance filters - var pass bool - if pass, err = fltrS.Pass(cgrEv.CGREvent.Tenant, blnCfg.FilterIDs, evNm); err != nil { - return nil, nil, err - } else if !pass { - return nil, nil, utils.ErrFilterNotPassingNoCaps - } - // find out the factor - for _, uF := range blnCfg.UnitFactors { - if pass, err = fltrS.Pass(cgrEv.CGREvent.Tenant, uF.FilterIDs, evNm); err != nil { - return nil, nil, err - } else if !pass { - continue - } - fctr = uF.Factor - mtchedUF = uF - break - } - if mtchedUF == nil { - return - } - if fctr.Cmp(decimal.New(1, 0)) != 0 { - resUsage = utils.MultiplyBig(usage, fctr) - } - return -} diff --git a/config/accountscfg.go b/config/accountscfg.go index 5474635a9..22e71e620 100644 --- a/config/accountscfg.go +++ b/config/accountscfg.go @@ -23,6 +23,7 @@ import "github.com/cgrates/cgrates/utils" // AccountSCfg is the configuration of ActionS type AccountSCfg struct { Enabled bool + AttributeSConns []string RateSConns []string ThresholdSConns []string IndexedSelects bool diff --git a/utils/accountprofile.go b/utils/accountprofile.go index abe81354b..e8f960ac0 100644 --- a/utils/accountprofile.go +++ b/utils/accountprofile.go @@ -39,14 +39,52 @@ type AccountProfile struct { // Balance represents one Balance inside an Account type Balance struct { - ID string // Balance identificator, unique within an Account - FilterIDs []string - Weight float64 - Blocker bool - Type string - Opts map[string]interface{} - UnitFactors []*UnitFactor - Value float64 + ID string // Balance identificator, unique within an Account + FilterIDs []string + Weight float64 + Blocker bool + Type string + Opts map[string]interface{} + CostIncrements []*CostIncrement + CostAttributes []*CostAttributes + UnitFactors []*UnitFactor + Value float64 +} + +// CostAttributes will attach attribute profiles to cost events +type CostAttributes struct { + FilterIDs []string + AttributeProfileIDs []string +} + +// CostIncrement enforces cost calculation to specific balance increments +type CostIncrement struct { + FilterIDs []string + Increment *decimal.Big + FixedFee *decimal.Big + RecurrentFee *decimal.Big +} + +// Clone returns a copy of the CostIncrement +func (cI *CostIncrement) Clone() (cIcln *CostIncrement) { + cIcln = new(CostIncrement) + if cI.FilterIDs != nil { + cIcln.FilterIDs = make([]string, len(cI.FilterIDs)) + for i, fID := range cI.FilterIDs { + cIcln.FilterIDs[i] = fID + } + } + if cI.Increment != nil { + cIcln.Increment = new(decimal.Big).Copy(cI.Increment) + } + if cI.FixedFee != nil { + cIcln.FixedFee = new(decimal.Big).Copy(cI.FixedFee) + } + if cI.RecurrentFee != nil { + cIcln.RecurrentFee = new(decimal.Big).Copy(cI.RecurrentFee) + } + return + } // UnitFactor is a multiplicator for the usage received diff --git a/utils/chrgdaccount.go b/utils/chrgdaccount.go deleted file mode 100644 index 4dd717d46..000000000 --- a/utils/chrgdaccount.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Real-time Online/Offline Charging System (OerS) 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 utils - -type ChargedAccount struct{} // placeholder for now diff --git a/utils/eventcharges.go b/utils/eventcharges.go index 3e8f84bc0..06cd18742 100644 --- a/utils/eventcharges.go +++ b/utils/eventcharges.go @@ -18,13 +18,17 @@ along with this program. If not, see package utils -import "time" +import ( + "time" + + "github.com/ericlagergren/decimal" +) // EventCharges records the charges applied to an Event type EventCharges struct { StartTime *time.Time - Usage *time.Duration - Cost *float64 + Usage *decimal.Big + Cost *decimal.Big Charges []*ChargedInterval Account *AccountProfile Accounting *ChargedAccounting