mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
313 lines
9.8 KiB
Go
313 lines
9.8 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 Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package accounts
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/cgrates/birpc/context"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
// actSetAccount updates the balances base on the diktat
|
|
func actSetAccount(ctx *context.Context, dm *engine.DataManager, tnt, acntID string, diktats []*utils.BalDiktat, reset bool) (err error) {
|
|
var qAcnt *utils.Account
|
|
if qAcnt, err = dm.GetAccount(ctx, tnt, acntID); err != nil {
|
|
if err != utils.ErrNotFound {
|
|
return
|
|
}
|
|
// in case the account doesn't exist create it with minimal information
|
|
qAcnt = &utils.Account{
|
|
Tenant: tnt,
|
|
ID: acntID,
|
|
}
|
|
}
|
|
for _, dk := range diktats {
|
|
// check if we have a valid path(e.g. *balance.Test.ID)
|
|
path := utils.SplitPath(dk.Path, utils.NestingSep[0], -1)
|
|
// check the path to be a valid one
|
|
|
|
switch path[0] {
|
|
case utils.MetaBalance:
|
|
if len(path) < 3 {
|
|
return utils.ErrWrongPath
|
|
}
|
|
bal, has := qAcnt.Balances[path[1]]
|
|
if !has {
|
|
// no balance for that ID create one
|
|
bal = utils.NewDefaultBalance(path[1])
|
|
if qAcnt.Balances == nil {
|
|
// in case the account has no balance create the balance map
|
|
qAcnt.Balances = make(map[string]*utils.Balance)
|
|
}
|
|
qAcnt.Balances[path[1]] = bal
|
|
}
|
|
if err = actSetBalance(bal, path[2:], dk.Value, reset); err != nil {
|
|
return
|
|
}
|
|
case utils.MetaAccount:
|
|
// special case in order to handle account field set in *set_balance/*add_balance action
|
|
if len(path) < 2 {
|
|
return utils.ErrWrongPath
|
|
}
|
|
if err = actSetAccountFields(qAcnt, path[1:], dk.Value); err != nil {
|
|
return
|
|
}
|
|
default:
|
|
return utils.ErrWrongPath
|
|
}
|
|
}
|
|
return dm.SetAccount(ctx, qAcnt, false)
|
|
}
|
|
|
|
// actSetAccountFields sets the fields inside the account
|
|
func actSetAccountFields(ac *utils.Account, path []string, value string) (err error) {
|
|
switch path[0] {
|
|
// the tenant and ID should come from user and should not change
|
|
case utils.FilterIDs:
|
|
ac.FilterIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
case utils.Weights:
|
|
ac.Weights, err = utils.NewDynamicWeightsFromString(value, utils.InfieldSep, utils.ANDSep)
|
|
case utils.Opts:
|
|
if ac.Opts == nil { // if the options are not initialized already init them here
|
|
ac.Opts = make(map[string]any)
|
|
}
|
|
err = utils.MapStorage(ac.Opts).Set(path[1:], value)
|
|
case utils.ThresholdIDs:
|
|
ac.ThresholdIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
default:
|
|
err = utils.ErrWrongPath
|
|
}
|
|
return
|
|
}
|
|
|
|
// actSetBalance will set the field at path from balance with value
|
|
// value is string as the value received from action is string
|
|
// the balance must not be nil
|
|
func actSetBalance(bal *utils.Balance, path []string, value string, reset bool) (err error) {
|
|
// check if we have path past *balance
|
|
if len(path) == 0 {
|
|
return utils.ErrWrongPath
|
|
}
|
|
// select what field is update based on the first value from path
|
|
// special case for CostIncrements and UnitFactors
|
|
// that are converted from string similar to how are loaded from CSVs
|
|
switch path[0] {
|
|
case utils.ID:
|
|
bal.ID = value
|
|
case utils.FilterIDs:
|
|
if value != utils.EmptyString {
|
|
bal.FilterIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
}
|
|
case utils.Weights:
|
|
if value != utils.EmptyString {
|
|
bal.Weights, err = utils.NewDynamicWeightsFromString(value, utils.InfieldSep, utils.ANDSep)
|
|
}
|
|
case utils.Type:
|
|
bal.Type = value
|
|
case utils.Units:
|
|
var z *utils.Decimal
|
|
if z, err = utils.NewDecimalFromString(value); err != nil {
|
|
return
|
|
}
|
|
// do not overwrite the Units if the action is *add_balance
|
|
// this flag makes the difference between the *add_balance and *set_balance actions
|
|
if !reset && bal.Units != nil {
|
|
bal.Units.Add(bal.Units.Big, z.Big)
|
|
} else {
|
|
bal.Units = z
|
|
}
|
|
case utils.UnitFactors:
|
|
// just recreate them from string
|
|
if value != utils.EmptyString {
|
|
bal.UnitFactors, err = actNewUnitFactorsFromString(value)
|
|
}
|
|
case utils.Opts:
|
|
if bal.Opts == nil { // if the options are not initilized already init them here
|
|
bal.Opts = make(map[string]any)
|
|
}
|
|
err = utils.MapStorage(bal.Opts).Set(path[1:], value)
|
|
case utils.CostIncrements:
|
|
// just recreate them from string
|
|
if value != utils.EmptyString {
|
|
bal.CostIncrements, err = actNewCostIncrementsFromString(value)
|
|
}
|
|
case utils.AttributeIDs:
|
|
if value != utils.EmptyString {
|
|
bal.AttributeIDs = strings.Split(value, utils.InfieldSep)
|
|
}
|
|
case utils.RateProfileIDs:
|
|
if value != utils.EmptyString {
|
|
bal.RateProfileIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
}
|
|
default:
|
|
// we modify the UnitFactors explicit
|
|
// e.g. *balance.TEST.UnitFactors[0].Factor
|
|
if strings.HasPrefix(path[0], utils.UnitFactors) {
|
|
bal.UnitFactors, err = actSetUnitFactor(bal.UnitFactors, path, value)
|
|
return
|
|
}
|
|
|
|
// we modify the CostIncrements explicit
|
|
// e.g. *balance.TEST.CostIncrements[0].Increment
|
|
if strings.HasPrefix(path[0], utils.CostIncrements) {
|
|
bal.CostIncrements, err = actSetCostIncrement(bal.CostIncrements, path, value)
|
|
return
|
|
}
|
|
// not a valid path
|
|
err = utils.ErrWrongPath
|
|
}
|
|
return
|
|
}
|
|
|
|
// actNewUnitFactorsFromString converts a string to a list of UnitFactors
|
|
// similar to the how the TP are loaded from CSV
|
|
func actNewUnitFactorsFromString(value string) (units []*utils.UnitFactor, err error) {
|
|
sls := strings.Split(value, utils.InfieldSep)
|
|
if len(sls)%2 != 0 {
|
|
return nil, fmt.Errorf("invalid key: <%s> for BalanceUnitFactors", value)
|
|
}
|
|
units = make([]*utils.UnitFactor, 0, len(sls)/2)
|
|
|
|
for j := 0; j < len(sls); j += 2 {
|
|
var z *utils.Decimal
|
|
if z, err = utils.NewDecimalFromString(sls[j+1]); err != nil {
|
|
return
|
|
}
|
|
var fltrs []string
|
|
if sls[j] != utils.EmptyString {
|
|
fltrs = strings.Split(sls[j], utils.ANDSep)
|
|
}
|
|
units = append(units, &utils.UnitFactor{
|
|
FilterIDs: fltrs,
|
|
Factor: z,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// actNewCostIncrementsFromString converts a string to a list of CostIncrements
|
|
// similar to the how the TP are loaded from CSV
|
|
func actNewCostIncrementsFromString(value string) (costs []*utils.CostIncrement, err error) {
|
|
sls := strings.Split(value, utils.InfieldSep)
|
|
if len(sls)%4 != 0 {
|
|
return nil, fmt.Errorf("invalid key: <%s> for BalanceCostIncrements", value)
|
|
}
|
|
costs = make([]*utils.CostIncrement, 0, len(sls)/4)
|
|
for j := 0; j < len(sls); j += 4 {
|
|
cost := &utils.CostIncrement{}
|
|
if sls[j] != utils.EmptyString {
|
|
cost.FilterIDs = strings.Split(sls[j], utils.ANDSep)
|
|
}
|
|
if incrementStr := sls[j+1]; incrementStr != utils.EmptyString {
|
|
if cost.Increment, err = utils.NewDecimalFromString(incrementStr); err != nil {
|
|
return
|
|
}
|
|
}
|
|
if fixedFeeStr := sls[j+2]; fixedFeeStr != utils.EmptyString {
|
|
if cost.FixedFee, err = utils.NewDecimalFromString(fixedFeeStr); err != nil {
|
|
return
|
|
}
|
|
}
|
|
if recurrentFeeStr := sls[j+3]; recurrentFeeStr != utils.EmptyString {
|
|
if cost.RecurrentFee, err = utils.NewDecimalFromString(recurrentFeeStr); err != nil {
|
|
return
|
|
}
|
|
}
|
|
costs = append(costs, cost)
|
|
}
|
|
return
|
|
}
|
|
|
|
// actSetUnitFactor will update the UnitFactors
|
|
func actSetUnitFactor(uFs []*utils.UnitFactor, path []string, value string) (untFctr []*utils.UnitFactor, err error) {
|
|
pathVal := path[0][11:]
|
|
lp := len(pathVal)
|
|
// check path requierments
|
|
// exact 2 elements
|
|
// and the first element have an index between brackets
|
|
if len(path) != 2 ||
|
|
pathVal[0] != '[' ||
|
|
pathVal[lp-1] != ']' {
|
|
return nil, utils.ErrWrongPath
|
|
}
|
|
pathVal = pathVal[1 : lp-1]
|
|
var idx int
|
|
// convert the index from string to int
|
|
if idx, err = strconv.Atoi(pathVal); err != nil {
|
|
return
|
|
}
|
|
if len(uFs) == idx { // special case add a new unitFactor
|
|
uFs = append(uFs, &utils.UnitFactor{})
|
|
} else if len(uFs) < idx { // make sure we are in slice range
|
|
return nil, utils.ErrWrongPath
|
|
}
|
|
|
|
switch path[1] {
|
|
case utils.FilterIDs:
|
|
uFs[idx].FilterIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
case utils.Factor:
|
|
uFs[idx].Factor, err = utils.NewDecimalFromString(value)
|
|
default:
|
|
err = utils.ErrWrongPath
|
|
}
|
|
return uFs, err
|
|
}
|
|
|
|
func actSetCostIncrement(cIs []*utils.CostIncrement, path []string, value string) (cstIncr []*utils.CostIncrement, err error) {
|
|
pathVal := path[0][14:]
|
|
lp := len(pathVal)
|
|
// check path requierments
|
|
// exact 2 elements
|
|
// and the first element have an index between brackets
|
|
if len(path) != 2 ||
|
|
pathVal[0] != '[' ||
|
|
pathVal[lp-1] != ']' {
|
|
return nil, utils.ErrWrongPath
|
|
}
|
|
pathVal = pathVal[1 : lp-1]
|
|
var idx int
|
|
// convert the index from string to int
|
|
if idx, err = strconv.Atoi(pathVal); err != nil {
|
|
return
|
|
}
|
|
if len(cIs) == idx { // special case add a new CostIncrement
|
|
cIs = append(cIs, &utils.CostIncrement{})
|
|
} else if len(cIs) < idx { // make sure we are in slice range
|
|
return nil, utils.ErrWrongPath
|
|
}
|
|
|
|
switch path[1] {
|
|
case utils.FilterIDs:
|
|
cIs[idx].FilterIDs = utils.NewStringSet(strings.Split(value, utils.InfieldSep)).AsSlice()
|
|
case utils.Increment:
|
|
cIs[idx].Increment, err = utils.NewDecimalFromString(value)
|
|
case utils.FixedFee:
|
|
cIs[idx].FixedFee, err = utils.NewDecimalFromString(value)
|
|
case utils.RecurrentFee:
|
|
cIs[idx].RecurrentFee, err = utils.NewDecimalFromString(value)
|
|
default:
|
|
err = utils.ErrWrongPath
|
|
}
|
|
return cIs, err
|
|
}
|