/* 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 */ 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]interface{}) } 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]interface{}) } 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 }