AccountS - abstractBalance with costIncrement, unitFactor, balanceLimit and partial debitUsage implementation

This commit is contained in:
DanB
2021-01-03 19:47:26 +01:00
parent 9df5ec3092
commit 5a632207ac
9 changed files with 239 additions and 158 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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
}

135
accounts/abstractbalance.go Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 <http://www.gnu.org/licenses/>
*/
package utils
type ChargedAccount struct{} // placeholder for now

View File

@@ -18,13 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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