mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-17 06:09:53 +05:00
AccountS - abstractBalance with costIncrement, unitFactor, balanceLimit and partial debitUsage implementation
This commit is contained in:
@@ -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
135
accounts/abstractbalance.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user