mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Constructor looks inside the params' balance map for the Factors key. It expects either a string representing a JSON serialized map or the map itself. BalanceFilter Clone function has been updated to set a deep copy of the original Factors map instead of a shallow one. BalanceFilter getter function for Factors now returns nil instead of an empty map. It's slightly more memory efficient and assignment to this map will not be attempted, so it's panic proof. BalanceFilter.ModifyBalance now updates Factors only if the key is found in the request params' Balance map. Setting Factors to null is also possible as long as the Factors key exists and is set to null. Note: only *set_balance can overwrite the Factors map, all the others can only set it if the balance does exist prior to sending the request. Update balance integration tests.
653 lines
15 KiB
Go
653 lines
15 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) 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 engine
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
type BalanceFilter struct {
|
|
Uuid *string
|
|
ID *string
|
|
Type *string
|
|
Value *utils.ValueFormula
|
|
ExpirationDate *time.Time
|
|
Weight *float64
|
|
DestinationIDs *utils.StringMap
|
|
RatingSubject *string
|
|
Categories *utils.StringMap
|
|
SharedGroups *utils.StringMap
|
|
TimingIDs *utils.StringMap
|
|
Timings []*RITiming
|
|
Disabled *bool
|
|
Factors *ValueFactors
|
|
Blocker *bool
|
|
}
|
|
|
|
// NewBalanceFilter creates a new BalanceFilter based on given filter
|
|
func NewBalanceFilter(filter map[string]any, defaultTimezone string) (*BalanceFilter, error) {
|
|
bf := new(BalanceFilter)
|
|
if id, has := filter[utils.ID]; has {
|
|
bf.ID = utils.StringPointer(utils.IfaceAsString(id))
|
|
}
|
|
if uuid, has := filter[utils.UUID]; has {
|
|
bf.Uuid = utils.StringPointer(utils.IfaceAsString(uuid))
|
|
}
|
|
// if ty, has := filter[utils.Type]; has {
|
|
// bf.Type = utils.StringPointer(utils.IfaceAsString(ty))
|
|
// }
|
|
if val, has := filter[utils.Value]; has {
|
|
value, err := utils.IfaceAsTFloat64(val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.Value = &utils.ValueFormula{Static: math.Abs(value)}
|
|
}
|
|
if exp, has := filter[utils.ExpiryTime]; has {
|
|
expTime, err := utils.IfaceAsTime(exp, defaultTimezone)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.ExpirationDate = utils.TimePointer(expTime)
|
|
}
|
|
if weight, has := filter[utils.Weight]; has {
|
|
value, err := utils.IfaceAsFloat64(weight)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.Weight = utils.Float64Pointer(value)
|
|
}
|
|
if dst, has := filter[utils.DestinationIDs]; has {
|
|
bf.DestinationIDs = utils.StringMapPointer(utils.ParseStringMap(utils.IfaceAsString(dst)))
|
|
}
|
|
if rs, has := filter[utils.RatingSubject]; has {
|
|
bf.RatingSubject = utils.StringPointer(utils.IfaceAsString(rs))
|
|
}
|
|
if cat, has := filter[utils.Categories]; has {
|
|
bf.Categories = utils.StringMapPointer(utils.ParseStringMap(utils.IfaceAsString(cat)))
|
|
}
|
|
if grps, has := filter[utils.SharedGroups]; has {
|
|
bf.SharedGroups = utils.StringMapPointer(utils.ParseStringMap(utils.IfaceAsString(grps)))
|
|
}
|
|
if tim, has := filter[utils.TimingIDs]; has {
|
|
bf.TimingIDs = utils.StringMapPointer(utils.ParseStringMap(utils.IfaceAsString(tim)))
|
|
}
|
|
if dis, has := filter[utils.Disabled]; has {
|
|
value, err := utils.IfaceAsBool(dis)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.Disabled = utils.BoolPointer(value)
|
|
}
|
|
if blk, has := filter[utils.Blocker]; has {
|
|
value, err := utils.IfaceAsBool(blk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.Blocker = utils.BoolPointer(value)
|
|
}
|
|
if facVal, has := filter[utils.Factors]; has {
|
|
var err error
|
|
var facBytes []byte
|
|
|
|
switch v := facVal.(type) {
|
|
case string:
|
|
facBytes = []byte(v)
|
|
default:
|
|
facBytes, err = json.Marshal(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var vf ValueFactors
|
|
if err := json.Unmarshal(facBytes, &vf); err != nil {
|
|
return nil, err
|
|
}
|
|
bf.Factors = &vf
|
|
}
|
|
return bf, nil
|
|
}
|
|
|
|
func (bp *BalanceFilter) CreateBalance() *Balance {
|
|
b := &Balance{
|
|
Uuid: bp.GetUuid(),
|
|
ID: bp.GetID(),
|
|
Value: bp.GetValue(),
|
|
ExpirationDate: bp.GetExpirationDate(),
|
|
Weight: bp.GetWeight(),
|
|
DestinationIDs: bp.GetDestinationIDs(),
|
|
RatingSubject: bp.GetRatingSubject(),
|
|
Categories: bp.GetCategories(),
|
|
SharedGroups: bp.GetSharedGroups(),
|
|
Timings: bp.Timings,
|
|
TimingIDs: bp.GetTimingIDs(),
|
|
Disabled: bp.GetDisabled(),
|
|
Factors: bp.GetFactors(),
|
|
Blocker: bp.GetBlocker(),
|
|
}
|
|
return b.Clone()
|
|
}
|
|
|
|
func (bf *BalanceFilter) Clone() *BalanceFilter {
|
|
if bf == nil {
|
|
return nil
|
|
}
|
|
result := &BalanceFilter{}
|
|
if bf.Uuid != nil {
|
|
result.Uuid = new(string)
|
|
*result.Uuid = *bf.Uuid
|
|
}
|
|
if bf.ID != nil {
|
|
result.ID = new(string)
|
|
*result.ID = *bf.ID
|
|
}
|
|
if bf.Type != nil {
|
|
result.Type = new(string)
|
|
*result.Type = *bf.Type
|
|
}
|
|
if bf.Value != nil {
|
|
result.Value = new(utils.ValueFormula)
|
|
*result.Value = *bf.Value
|
|
}
|
|
if bf.ExpirationDate != nil {
|
|
result.ExpirationDate = new(time.Time)
|
|
*result.ExpirationDate = *bf.ExpirationDate
|
|
}
|
|
if bf.Weight != nil {
|
|
result.Weight = new(float64)
|
|
*result.Weight = *bf.Weight
|
|
}
|
|
if bf.DestinationIDs != nil {
|
|
result.DestinationIDs = utils.StringMapPointer(bf.DestinationIDs.Clone())
|
|
}
|
|
if bf.RatingSubject != nil {
|
|
result.RatingSubject = new(string)
|
|
*result.RatingSubject = *bf.RatingSubject
|
|
}
|
|
if bf.Categories != nil {
|
|
result.Categories = utils.StringMapPointer(bf.Categories.Clone())
|
|
}
|
|
if bf.SharedGroups != nil {
|
|
result.SharedGroups = utils.StringMapPointer(bf.SharedGroups.Clone())
|
|
}
|
|
if bf.TimingIDs != nil {
|
|
result.TimingIDs = utils.StringMapPointer(bf.TimingIDs.Clone())
|
|
}
|
|
if bf.Timings != nil {
|
|
result.Timings = make([]*RITiming, len(bf.Timings))
|
|
for i, rit := range bf.Timings {
|
|
result.Timings[i] = rit.Clone()
|
|
}
|
|
}
|
|
if bf.Disabled != nil {
|
|
result.Disabled = new(bool)
|
|
*result.Disabled = *bf.Disabled
|
|
}
|
|
if bf.Factors != nil {
|
|
cln := make(ValueFactors, len(*bf.Factors))
|
|
for key, value := range *bf.Factors {
|
|
cln[key] = value
|
|
}
|
|
result.Factors = &cln
|
|
}
|
|
if bf.Blocker != nil {
|
|
result.Blocker = new(bool)
|
|
*result.Blocker = *bf.Blocker
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (bf *BalanceFilter) LoadFromBalance(b *Balance) *BalanceFilter {
|
|
if b.Uuid != "" {
|
|
bf.Uuid = &b.Uuid
|
|
}
|
|
if b.ID != "" {
|
|
bf.ID = &b.ID
|
|
}
|
|
if b.Value != 0 {
|
|
bf.Value.Static = b.Value
|
|
}
|
|
if !b.ExpirationDate.IsZero() {
|
|
bf.ExpirationDate = &b.ExpirationDate
|
|
}
|
|
if b.Weight != 0 {
|
|
bf.Weight = &b.Weight
|
|
}
|
|
if !b.DestinationIDs.IsEmpty() {
|
|
bf.DestinationIDs = &b.DestinationIDs
|
|
}
|
|
if b.RatingSubject != "" {
|
|
bf.RatingSubject = &b.RatingSubject
|
|
}
|
|
if !b.Categories.IsEmpty() {
|
|
bf.Categories = &b.Categories
|
|
}
|
|
if !b.SharedGroups.IsEmpty() {
|
|
bf.SharedGroups = &b.SharedGroups
|
|
}
|
|
if !b.TimingIDs.IsEmpty() {
|
|
bf.TimingIDs = &b.TimingIDs
|
|
}
|
|
if len(b.Timings) != 0 {
|
|
bf.Timings = make([]*RITiming, len(b.Timings))
|
|
copy(bf.Timings, b.Timings)
|
|
|
|
}
|
|
if len(b.Factors) != 0 {
|
|
bf.Factors = &b.Factors
|
|
}
|
|
if b.Disabled {
|
|
bf.Disabled = &b.Disabled
|
|
}
|
|
if b.Blocker {
|
|
bf.Blocker = &b.Blocker
|
|
}
|
|
bf.Timings = b.Timings
|
|
return bf
|
|
}
|
|
|
|
func (bp *BalanceFilter) Equal(o *BalanceFilter) bool {
|
|
if bp.ID != nil && o.ID != nil {
|
|
return *bp.ID == *o.ID
|
|
}
|
|
return reflect.DeepEqual(bp, o)
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetType() string {
|
|
if bp == nil || bp.Type == nil {
|
|
return ""
|
|
}
|
|
return *bp.Type
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetValue() float64 {
|
|
if bp == nil || bp.Value == nil {
|
|
return 0.0
|
|
}
|
|
if bp.Value.Method == "" {
|
|
return bp.Value.Static
|
|
}
|
|
// calculate using formula
|
|
formula, exists := utils.ValueFormulas[bp.Value.Method]
|
|
if !exists {
|
|
return 0.0
|
|
}
|
|
return formula(bp.Value.Params)
|
|
}
|
|
|
|
func (bp *BalanceFilter) SetValue(v float64) {
|
|
if bp.Value == nil {
|
|
bp.Value = new(utils.ValueFormula)
|
|
}
|
|
bp.Value.Static = v
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetUuid() string {
|
|
if bp == nil || bp.Uuid == nil {
|
|
return ""
|
|
}
|
|
return *bp.Uuid
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetID() string {
|
|
if bp == nil || bp.ID == nil {
|
|
return ""
|
|
}
|
|
return *bp.ID
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetDestinationIDs() utils.StringMap {
|
|
if bp == nil || bp.DestinationIDs == nil {
|
|
return utils.StringMap{}
|
|
}
|
|
return *bp.DestinationIDs
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetCategories() utils.StringMap {
|
|
if bp == nil || bp.Categories == nil {
|
|
return utils.StringMap{}
|
|
}
|
|
return *bp.Categories
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetTimingIDs() utils.StringMap {
|
|
if bp == nil || bp.TimingIDs == nil {
|
|
return utils.StringMap{}
|
|
}
|
|
return *bp.TimingIDs
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetSharedGroups() utils.StringMap {
|
|
if bp == nil || bp.SharedGroups == nil {
|
|
return utils.StringMap{}
|
|
}
|
|
return *bp.SharedGroups
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetWeight() float64 {
|
|
if bp == nil || bp.Weight == nil {
|
|
return 0.0
|
|
}
|
|
return *bp.Weight
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetRatingSubject() string {
|
|
if bp == nil || bp.RatingSubject == nil {
|
|
return ""
|
|
}
|
|
return *bp.RatingSubject
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetDisabled() bool {
|
|
if bp == nil || bp.Disabled == nil {
|
|
return false
|
|
}
|
|
return *bp.Disabled
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetBlocker() bool {
|
|
if bp == nil || bp.Blocker == nil {
|
|
return false
|
|
}
|
|
return *bp.Blocker
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetExpirationDate() time.Time {
|
|
if bp == nil || bp.ExpirationDate == nil {
|
|
return time.Time{}
|
|
}
|
|
return *bp.ExpirationDate
|
|
}
|
|
|
|
func (bp *BalanceFilter) GetFactors() ValueFactors {
|
|
if bp == nil || bp.Factors == nil {
|
|
return nil
|
|
}
|
|
return *bp.Factors
|
|
}
|
|
|
|
func (bp *BalanceFilter) EmptyExpirationDate() bool {
|
|
if bp.ExpirationDate == nil {
|
|
return true
|
|
}
|
|
return (*bp.ExpirationDate).IsZero()
|
|
}
|
|
|
|
func (bf *BalanceFilter) ModifyBalance(b *Balance) {
|
|
if b == nil {
|
|
return
|
|
}
|
|
if bf.ID != nil {
|
|
b.ID = *bf.ID
|
|
}
|
|
if bf.Value != nil {
|
|
b.Value = bf.GetValue()
|
|
}
|
|
if bf.ExpirationDate != nil {
|
|
b.ExpirationDate = *bf.ExpirationDate
|
|
}
|
|
if bf.RatingSubject != nil {
|
|
b.RatingSubject = *bf.RatingSubject
|
|
}
|
|
if bf.Categories != nil {
|
|
b.Categories = *bf.Categories
|
|
}
|
|
if bf.DestinationIDs != nil {
|
|
b.DestinationIDs = *bf.DestinationIDs
|
|
}
|
|
if bf.SharedGroups != nil {
|
|
b.SharedGroups = *bf.SharedGroups
|
|
}
|
|
if bf.TimingIDs != nil {
|
|
b.TimingIDs = *bf.TimingIDs
|
|
}
|
|
if bf.Timings != nil && len(bf.Timings) != 0 {
|
|
b.Timings = make([]*RITiming, len(bf.Timings))
|
|
copy(b.Timings, bf.Timings)
|
|
}
|
|
if bf.Weight != nil {
|
|
b.Weight = *bf.Weight
|
|
}
|
|
if bf.Factors != nil {
|
|
b.Factors = *bf.Factors
|
|
}
|
|
if bf.Blocker != nil {
|
|
b.Blocker = *bf.Blocker
|
|
}
|
|
if bf.Disabled != nil {
|
|
b.Disabled = *bf.Disabled
|
|
}
|
|
b.SetDirty() // Mark the balance as dirty since we have modified and it should be checked by action triggers
|
|
}
|
|
|
|
func (bp *BalanceFilter) String() string {
|
|
return utils.ToJSON(bp)
|
|
}
|
|
|
|
func (bp *BalanceFilter) FieldAsInterface(fldPath []string) (val any, err error) {
|
|
if bp == nil || len(fldPath) == 0 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
switch fldPath[0] {
|
|
default:
|
|
opath, indx := utils.GetPathIndexString(fldPath[0])
|
|
if indx != nil {
|
|
switch opath {
|
|
case utils.DestinationIDs:
|
|
if bp.DestinationIDs == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
val, has := (*bp.DestinationIDs)[*indx]
|
|
if !has || len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return val, nil
|
|
case utils.Categories:
|
|
if bp.Categories == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
val, has := (*bp.Categories)[*indx]
|
|
if !has || len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return val, nil
|
|
case utils.SharedGroups:
|
|
if bp.SharedGroups == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
val, has := (*bp.SharedGroups)[*indx]
|
|
if !has || len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return val, nil
|
|
case utils.TimingIDs:
|
|
if bp.TimingIDs == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
val, has := (*bp.TimingIDs)[*indx]
|
|
if !has || len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return val, nil
|
|
case utils.Timings:
|
|
var idx int
|
|
if idx, err = strconv.Atoi(*indx); err != nil {
|
|
return
|
|
}
|
|
if len(bp.Timings) <= idx {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
tm := bp.Timings[idx]
|
|
if len(fldPath) == 1 {
|
|
return tm, nil
|
|
}
|
|
return tm.FieldAsInterface(fldPath[1:])
|
|
case utils.Factors:
|
|
if bp.Factors == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
val, has := (*bp.Factors)[*indx]
|
|
if !has || len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return val, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0])
|
|
case utils.Uuid:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.Uuid == nil {
|
|
return
|
|
}
|
|
return *bp.Uuid, nil
|
|
case utils.ID:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.ID == nil {
|
|
return
|
|
}
|
|
return *bp.ID, nil
|
|
case utils.Type:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.Type == nil {
|
|
return
|
|
}
|
|
return *bp.Type, nil
|
|
case utils.ExpirationDate:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.ExpirationDate == nil {
|
|
return
|
|
}
|
|
return *bp.ExpirationDate, nil
|
|
case utils.Weight:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.Weight == nil {
|
|
return
|
|
}
|
|
return *bp.Weight, nil
|
|
case utils.RatingSubject:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.RatingSubject == nil {
|
|
return
|
|
}
|
|
return *bp.RatingSubject, nil
|
|
case utils.Disabled:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.Disabled == nil {
|
|
return
|
|
}
|
|
return *bp.Disabled, nil
|
|
case utils.Blocker:
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if bp.Blocker == nil {
|
|
return
|
|
}
|
|
return *bp.Blocker, nil
|
|
case utils.DestinationIDs:
|
|
if len(fldPath) == 1 {
|
|
return bp.DestinationIDs, nil
|
|
}
|
|
if bp.DestinationIDs == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return bp.DestinationIDs.FieldAsInterface(fldPath[1:])
|
|
case utils.Categories:
|
|
if len(fldPath) == 1 {
|
|
return bp.Categories, nil
|
|
}
|
|
if bp.Categories == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return bp.Categories.FieldAsInterface(fldPath[1:])
|
|
case utils.SharedGroups:
|
|
if len(fldPath) == 1 {
|
|
return bp.SharedGroups, nil
|
|
}
|
|
if bp.SharedGroups == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return bp.SharedGroups.FieldAsInterface(fldPath[1:])
|
|
case utils.Timings:
|
|
if len(fldPath) == 1 {
|
|
return bp.Timings, nil
|
|
}
|
|
for _, tm := range bp.Timings {
|
|
if tm.ID == fldPath[1] {
|
|
if len(fldPath) == 2 {
|
|
return tm, nil
|
|
}
|
|
return tm.FieldAsInterface(fldPath[2:])
|
|
}
|
|
}
|
|
return nil, utils.ErrNotFound
|
|
case utils.TimingIDs:
|
|
if len(fldPath) == 1 {
|
|
return bp.TimingIDs, nil
|
|
}
|
|
if bp.TimingIDs == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return bp.TimingIDs.FieldAsInterface(fldPath[1:])
|
|
case utils.Factors:
|
|
if len(fldPath) == 1 {
|
|
return bp.Factors, nil
|
|
}
|
|
if bp.Factors == nil {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
return bp.Factors.FieldAsInterface(fldPath[1:])
|
|
case utils.Value:
|
|
if len(fldPath) == 1 {
|
|
return bp.Value, nil
|
|
}
|
|
return bp.Value.FieldAsInterface(fldPath[1:])
|
|
}
|
|
}
|
|
|
|
func (bp *BalanceFilter) FieldAsString(fldPath []string) (val string, err error) {
|
|
var iface any
|
|
iface, err = bp.FieldAsInterface(fldPath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return utils.IfaceAsString(iface), nil
|
|
}
|