From ff5f5f2d21f0474e05ea157a7dcea6ca5a9fc59d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 5 Feb 2020 11:02:55 +0200 Subject: [PATCH] Added FieldAsInterface for EventCost --- engine/account.go | 204 ++++++++++++++++++++++++--------------- engine/balances.go | 26 ++++- engine/eventcost.go | 150 ++++++++++++++++++++--------- engine/libeventcost.go | 211 ++++++++++++++++++++++++++++++++++++++++- engine/rateinterval.go | 19 ++++ utils/consts.go | 23 +++++ utils/coreutils.go | 20 ++++ 7 files changed, 532 insertions(+), 121 deletions(-) diff --git a/engine/account.go b/engine/account.go index 9579b6f4e..0c3d695e8 100644 --- a/engine/account.go +++ b/engine/account.go @@ -31,10 +31,8 @@ import ( "github.com/cgrates/cgrates/utils" ) -/* -Structure containing information about user's credit (minutes, cents, sms...).' -This can represent a user or a shared group. -*/ +// Account structure containing information about user's credit (minutes, cents, sms...).' +// This can represent a user or a shared group. type Account struct { ID string BalanceMap map[string]Balances @@ -47,17 +45,17 @@ type Account struct { } // User's available minutes for the specified destination -func (ub *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duration, credit float64, balances Balances) { - creditBalances := ub.getBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, "", cd.TimeStart) +func (acc *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duration, credit float64, balances Balances) { + creditBalances := acc.getBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, "", cd.TimeStart) - unitBalances := ub.getBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, "", cd.TimeStart) + unitBalances := acc.getBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, "", cd.TimeStart) // gather all balances from shared groups var extendedCreditBalances Balances for _, cb := range creditBalances { if len(cb.SharedGroups) > 0 { for sg := range cb.SharedGroups { if sharedGroup, _ := dm.GetSharedGroup(sg, false, utils.NonTransactional); sharedGroup != nil { - sgb := sharedGroup.GetBalances(cd.Destination, cd.Category, utils.MONETARY, ub, cd.TimeStart) + sgb := sharedGroup.GetBalances(cd.Destination, cd.Category, utils.MONETARY, acc, cd.TimeStart) sgb = sharedGroup.SortBalancesByStrategy(cb, sgb) extendedCreditBalances = append(extendedCreditBalances, sgb...) } @@ -71,7 +69,7 @@ func (ub *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duratio if len(mb.SharedGroups) > 0 { for sg := range mb.SharedGroups { if sharedGroup, _ := dm.GetSharedGroup(sg, false, utils.NonTransactional); sharedGroup != nil { - sgb := sharedGroup.GetBalances(cd.Destination, cd.Category, cd.ToR, ub, cd.TimeStart) + sgb := sharedGroup.GetBalances(cd.Destination, cd.Category, cd.ToR, acc, cd.TimeStart) sgb = sharedGroup.SortBalancesByStrategy(mb, sgb) extendedMinuteBalances = append(extendedMinuteBalances, sgb...) } @@ -186,7 +184,7 @@ func (acc *Account) setBalanceAction(a *Action) error { // Debits some amount of user's specified balance adding the balance if it does not exists. // Returns the remaining credit in user's balance. -func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool) error { +func (acc *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool) error { if a == nil { return errors.New("nil action") } @@ -195,16 +193,16 @@ func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool) er if bClone == nil { return errors.New("nil balance in action") } - if ub.BalanceMap == nil { - ub.BalanceMap = make(map[string]Balances) + if acc.BalanceMap == nil { + acc.BalanceMap = make(map[string]Balances) } found := false balanceType := a.Balance.GetType() - for _, b := range ub.BalanceMap[balanceType] { + for _, b := range acc.BalanceMap[balanceType] { if b.IsExpiredAt(time.Now()) { continue // just to be safe (cleaned expired balances above) } - b.account = ub + b.account = acc if b.MatchFilter(a.Balance, false, false) { if reset || (resetIfNegative && b.Value < 0) { b.SetValue(0) @@ -242,23 +240,23 @@ func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool) er utils.Logger.Warning(fmt.Sprintf("Could load value factor from actions: extra parametrs: %s", a.ExtraParameters)) } } - ub.BalanceMap[balanceType] = append(ub.BalanceMap[balanceType], bClone) + acc.BalanceMap[balanceType] = append(acc.BalanceMap[balanceType], bClone) _, err := guardian.Guardian.Guard(func() (interface{}, error) { sgs := make([]string, len(bClone.SharedGroups)) i := 0 - for sgId := range bClone.SharedGroups { + for sgID := range bClone.SharedGroups { // add shared group member - sg, err := dm.GetSharedGroup(sgId, false, utils.NonTransactional) + sg, err := dm.GetSharedGroup(sgID, false, utils.NonTransactional) if err != nil || sg == nil { //than is problem - utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId)) + utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgID)) } else { - if _, found := sg.MemberIds[ub.ID]; !found { + if _, found := sg.MemberIds[acc.ID]; !found { // add member and save if sg.MemberIds == nil { sg.MemberIds = make(utils.StringMap) } - sg.MemberIds[ub.ID] = true + sg.MemberIds[acc.ID] = true dm.SetSharedGroup(sg, utils.NonTransactional) } } @@ -271,17 +269,17 @@ func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool) er return err } } - ub.InitCounters() - ub.ExecuteActionTriggers(nil) + acc.InitCounters() + acc.ExecuteActionTriggers(nil) return nil } -func (ub *Account) getBalancesForPrefix(prefix, category, tor, +func (acc *Account) getBalancesForPrefix(prefix, category, tor, sharedGroup string, aTime time.Time) Balances { var balances Balances - balances = append(balances, ub.BalanceMap[tor]...) + balances = append(balances, acc.BalanceMap[tor]...) if tor != utils.MONETARY && tor != utils.GENERIC { - balances = append(balances, ub.BalanceMap[utils.GENERIC]...) + balances = append(balances, acc.BalanceMap[utils.GENERIC]...) } var usefulBalances Balances @@ -298,15 +296,15 @@ func (ub *Account) getBalancesForPrefix(prefix, category, tor, if !b.MatchCategory(category) { continue } - b.account = ub + b.account = acc if len(b.DestinationIDs) > 0 && b.DestinationIDs[utils.ANY] == false { for _, p := range utils.SplitPrefix(prefix, MIN_PREFIX_MATCH) { if destIDs, err := dm.GetReverseDestination(p, false, utils.NonTransactional); err == nil { foundResult := false allInclude := true // whether it is excluded or included - for _, dId := range destIDs { - inclDest, found := b.DestinationIDs[dId] + for _, dID := range destIDs { + inclDest, found := b.DestinationIDs[dID] if found { foundResult = true allInclude = allInclude && inclDest @@ -348,18 +346,18 @@ func (ub *Account) getBalancesForPrefix(prefix, category, tor, } // like getBalancesForPrefix but expanding shared balances -func (account *Account) getAlldBalancesForPrefix(destination, category, +func (acc *Account) getAlldBalancesForPrefix(destination, category, balanceType string, aTime time.Time) (bc Balances) { - balances := account.getBalancesForPrefix(destination, category, balanceType, "", aTime) + balances := acc.getBalancesForPrefix(destination, category, balanceType, "", aTime) for _, b := range balances { if len(b.SharedGroups) > 0 { - for sgId := range b.SharedGroups { - sharedGroup, err := dm.GetSharedGroup(sgId, false, utils.NonTransactional) + for sgID := range b.SharedGroups { + sharedGroup, err := dm.GetSharedGroup(sgID, false, utils.NonTransactional) if err != nil || sharedGroup == nil { - utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId)) + utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgID)) continue } - sharedBalances := sharedGroup.GetBalances(destination, category, balanceType, account, aTime) + sharedBalances := sharedGroup.GetBalances(destination, category, balanceType, acc, aTime) sharedBalances = sharedGroup.SortBalancesByStrategy(b, sharedBalances) bc = append(bc, sharedBalances...) } @@ -370,9 +368,9 @@ func (account *Account) getAlldBalancesForPrefix(destination, category, return } -func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bool, goNegative bool) (cc *CallCost, err error) { - usefulUnitBalances := ub.getAlldBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, cd.TimeStart) - usefulMoneyBalances := ub.getAlldBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, cd.TimeStart) +func (acc *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bool, goNegative bool) (cc *CallCost, err error) { + usefulUnitBalances := acc.getAlldBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, cd.TimeStart) + usefulMoneyBalances := acc.getAlldBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, cd.TimeStart) var leftCC *CallCost cc = cd.CreateCallCost() var hadBalanceSubj bool @@ -478,7 +476,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo if inc.BalanceInfo == nil { inc.BalanceInfo = &DebitInfo{} } - inc.BalanceInfo.AccountID = ub.ID + inc.BalanceInfo.AccountID = acc.ID } } cc.Timespans = append(cc.Timespans, leftCC.Timespans...) @@ -493,12 +491,12 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo if initialLength == 0 { // this is the first add, debit the connect fee - ok, debitedConnectFeeBalance = ub.DebitConnectionFee(cc, usefulMoneyBalances, count, true) + ok, debitedConnectFeeBalance = acc.DebitConnectionFee(cc, usefulMoneyBalances, count, true) } //log.Printf("Left CC: %+v ", leftCC) // get the default money balanance // and go negative on it with the amount still unpaid - if len(leftCC.Timespans) > 0 && leftCC.Cost > 0 && !ub.AllowNegative && !dryRun { + if len(leftCC.Timespans) > 0 && leftCC.Cost > 0 && !acc.AllowNegative && !dryRun { utils.Logger.Warning(fmt.Sprintf(" Going negative on account %s with AllowNegative: false", cd.GetAccountKey())) } leftCC.Timespans.Decompress() @@ -518,7 +516,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo ID: debitedConnectFeeBalance.ID, Value: debitedConnectFeeBalance.Value, }, - AccountID: ub.ID, + AccountID: acc.ID, }, } @@ -534,11 +532,11 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo } cost := increment.Cost - defaultBalance := ub.GetDefaultMoneyBalance() + defaultBalance := acc.GetDefaultMoneyBalance() defaultBalance.SubstractValue(cost) //send default balance to thresholdS to be processed if len(config.CgrConfig().RalsCfg().ThresholdSConns) != 0 { - acntTnt := utils.NewTenantID(ub.ID) + acntTnt := utils.NewTenantID(acc.ID) thEv := &ArgsProcessEvent{ CGREvent: &utils.CGREvent{ Tenant: acntTnt.Tenant, @@ -564,10 +562,10 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo ID: defaultBalance.ID, Value: defaultBalance.Value, } - increment.BalanceInfo.AccountID = ub.ID + increment.BalanceInfo.AccountID = acc.ID increment.paid = true if count { - ub.countUnits( + acc.countUnits( cost, utils.MONETARY, leftCC, @@ -583,15 +581,16 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo COMMIT: if !dryRun { // save darty shared balances - usefulMoneyBalances.SaveDirtyBalances(ub) - usefulUnitBalances.SaveDirtyBalances(ub) + usefulMoneyBalances.SaveDirtyBalances(acc) + usefulUnitBalances.SaveDirtyBalances(acc) } //log.Printf("Final CC: %+v", cc) return } -func (ub *Account) GetDefaultMoneyBalance() *Balance { - for _, balance := range ub.BalanceMap[utils.MONETARY] { +// GetDefaultMoneyBalance returns the defaultmoney balance +func (acc *Account) GetDefaultMoneyBalance() *Balance { + for _, balance := range acc.BalanceMap[utils.MONETARY] { if balance.IsDefault() { return balance } @@ -601,14 +600,14 @@ func (ub *Account) GetDefaultMoneyBalance() *Balance { Uuid: utils.GenUUID(), ID: utils.MetaDefault, } // minimum weight - if ub.BalanceMap == nil { - ub.BalanceMap = make(map[string]Balances) + if acc.BalanceMap == nil { + acc.BalanceMap = make(map[string]Balances) } - ub.BalanceMap[utils.MONETARY] = append(ub.BalanceMap[utils.MONETARY], defaultBalance) + acc.BalanceMap[utils.MONETARY] = append(acc.BalanceMap[utils.MONETARY], defaultBalance) return defaultBalance } -// Scans the action triggers and execute the actions for which trigger is met +// ExecuteActionTriggers scans the action triggers and execute the actions for which trigger is met func (acc *Account) ExecuteActionTriggers(a *Action) { if acc.executingTriggers { return @@ -685,7 +684,7 @@ func (acc *Account) ExecuteActionTriggers(a *Action) { acc.CleanExpiredStuff() } -// Mark all action trigers as ready for execution +// ResetActionTriggers marks all action trigers as ready for execution // If the action is not nil it acts like a filter func (acc *Account) ResetActionTriggers(a *Action) { for _, at := range acc.ActionTriggers { @@ -697,7 +696,7 @@ func (acc *Account) ResetActionTriggers(a *Action) { acc.ExecuteActionTriggers(a) } -// Sets/Unsets recurrent flag for action triggers +// SetRecurrent sets/unsets recurrent flag for action triggers func (acc *Account) SetRecurrent(a *Action, recurrent bool) { for _, at := range acc.ActionTriggers { if !at.Match(a) { @@ -713,7 +712,7 @@ func (acc *Account) countUnits(amount float64, kind string, cc *CallCost, b *Bal acc.ExecuteActionTriggers(nil) } -// Create counters for all triggered actions +// InitCounters creates counters for all triggered actions func (acc *Account) InitCounters() { oldUcs := acc.UnitCounters acc.UnitCounters = make(UnitCounters) @@ -766,6 +765,7 @@ func (acc *Account) InitCounters() { } } +// CleanExpiredStuff removed expired balances and actiontriggers func (acc *Account) CleanExpiredStuff() { if config.CgrConfig().RalsCfg().RemoveExpired { for key, bm := range acc.BalanceMap { @@ -797,7 +797,7 @@ func (acc *Account) allBalancesExpired() bool { return true } -// returns the shared groups that this user balance belnongs to +// GetSharedGroups returns the shared groups that this user balance belnongs to func (acc *Account) GetSharedGroups() (groups []string) { for _, balanceChain := range acc.BalanceMap { for _, b := range balanceChain { @@ -809,10 +809,11 @@ func (acc *Account) GetSharedGroups() (groups []string) { return } -func (account *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) (utils.StringMap, error) { // ToDo: make sure we return accountIDs +// GetUniqueSharedGroupMembers returns the acounts from the group +func (acc *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) (utils.StringMap, error) { // ToDo: make sure we return accountIDs var balances []*Balance - balances = append(balances, account.getBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, "", cd.TimeStart)...) - balances = append(balances, account.getBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, "", cd.TimeStart)...) + balances = append(balances, acc.getBalancesForPrefix(cd.Destination, cd.Category, utils.MONETARY, "", cd.TimeStart)...) + balances = append(balances, acc.getBalancesForPrefix(cd.Destination, cd.Category, cd.ToR, "", cd.TimeStart)...) // gather all shared group ids var sharedGroupIds []string for _, b := range balances { @@ -834,6 +835,7 @@ func (account *Account) GetUniqueSharedGroupMembers(cd *CallDescriptor) (utils.S return memberIds, nil } +// Clone creates a copy of the account func (acc *Account) Clone() *Account { newAcc := &Account{ ID: acc.ID, @@ -856,6 +858,7 @@ func (acc *Account) Clone() *Account { return newAcc } +// DebitConnectionFee debits the connection fee func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balances, count bool, block bool) (bool, Balance) { var debitedBalance Balance @@ -919,6 +922,7 @@ func (acc *Account) matchActionFilter(condition string) (bool, error) { return false, nil } +// GetID returns the account ID func (acc *Account) GetID() string { split := strings.Split(acc.ID, utils.CONCATENATED_KEY_SEP) if len(split) != 2 { @@ -927,7 +931,7 @@ func (acc *Account) GetID() string { return split[1] } -// used in some api for transition +// AsOldStructure used in some api for transition func (acc *Account) AsOldStructure() interface{} { type Balance struct { Uuid string //system wide unique @@ -1069,6 +1073,7 @@ func (acc *Account) AsOldStructure() interface{} { return result } +// AsAccountSummary converts the account into AccountSummary func (acc *Account) AsAccountSummary() *AccountSummary { idSplt := strings.Split(acc.ID, utils.CONCATENATED_KEY_SEP) ad := &AccountSummary{AllowNegative: acc.AllowNegative, Disabled: acc.Disabled} @@ -1091,8 +1096,9 @@ func (acc *Account) AsAccountSummary() *AccountSummary { return ad } -func (acnt *Account) Publish() { - acntTnt := utils.NewTenantID(acnt.ID) +// Publish sends the account to stats and threshold +func (acc *Account) Publish() { + acntTnt := utils.NewTenantID(acc.ID) cgrEv := &utils.CGREvent{ Tenant: acntTnt.Tenant, ID: utils.GenUUID(), @@ -1100,8 +1106,8 @@ func (acnt *Account) Publish() { utils.EventType: utils.AccountUpdate, utils.EventSource: utils.AccountService, utils.Account: acntTnt.ID, - utils.AllowNegative: acnt.AllowNegative, - utils.Disabled: acnt.Disabled}} + utils.AllowNegative: acc.AllowNegative, + utils.Disabled: acc.Disabled}} if len(config.CgrConfig().RalsCfg().StatSConns) != 0 { go func() { var reply []string @@ -1128,17 +1134,18 @@ func (acnt *Account) Publish() { } } -func (acnt *Account) AsNavigableMap(_ []*config.FCTemplate) (*config.NavigableMap, error) { +// AsNavigableMap converts the Account to NavigableMap +func (acc *Account) AsNavigableMap(_ []*config.FCTemplate) (*config.NavigableMap, error) { mpIface := map[string]interface{}{ - "ID": acnt.ID, + "ID": acc.ID, //"UnitCounters": acnt.UnitCounters, - "ActionTriggers": acnt.ActionTriggers, - "AllowNegative": acnt.AllowNegative, - "Disabled": acnt.Disabled, + "ActionTriggers": acc.ActionTriggers, + "AllowNegative": acc.AllowNegative, + "Disabled": acc.Disabled, } - balanceMap := make(map[string]interface{}, len(acnt.BalanceMap)) - for key, balances := range acnt.BalanceMap { + balanceMap := make(map[string]interface{}, len(acc.BalanceMap)) + for key, balances := range acc.BalanceMap { balSls := make([]*config.NavigableMap, len(balances)) for i, balance := range balances { balSls[i], _ = balance.AsNavigableMap(nil) @@ -1151,6 +1158,7 @@ func (acnt *Account) AsNavigableMap(_ []*config.FCTemplate) (*config.NavigableMa } +// NewAccountSummaryFromJSON creates a new AcccountSummary from a json string func NewAccountSummaryFromJSON(jsn string) (acntSummary *AccountSummary, err error) { if !utils.SliceHasMember([]string{"", "null"}, jsn) { // Unmarshal only when content json.Unmarshal([]byte(jsn), &acntSummary) @@ -1167,6 +1175,7 @@ type AccountSummary struct { Disabled bool } +// Clone creates a copy of the structure func (as *AccountSummary) Clone() (cln *AccountSummary) { cln = new(AccountSummary) cln.Tenant = as.Tenant @@ -1184,11 +1193,58 @@ func (as *AccountSummary) Clone() (cln *AccountSummary) { } // GetBalanceWithID returns a Balance given balance type and balance ID -func (acnt *Account) GetBalanceWithID(blcType, blcID string) (blc *Balance) { - for _, blc = range acnt.BalanceMap[blcType] { +func (acc *Account) GetBalanceWithID(blcType, blcID string) (blc *Balance) { + for _, blc = range acc.BalanceMap[blcType] { if blc.ID == blcID { return } } return nil } + +// FieldAsInterface func to help EventCost FieldAsInterface +func (as *AccountSummary) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + opath, indx := utils.GetPathIndex(fldPath[0]) + if opath == utils.BalanceSummaries && indx != nil { + if len(as.BalanceSummaries) < *indx { + return nil, utils.ErrNotFound + } + bl := as.BalanceSummaries[*indx] + if len(fldPath) == 1 { + return bl, nil + } + return bl.FieldAsInterface(fldPath[1:]) + } + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.Tenant: + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return as.Tenant, nil + case utils.ID: + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return as.ID, nil + case utils.BalanceSummaries: + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return as.BalanceSummaries, nil + case utils.AllowNegative: + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return as.AllowNegative, nil + case utils.Disabled: + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return as.Disabled, nil + } +} diff --git a/engine/balances.go b/engine/balances.go index 54d3101ac..e111622d2 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -892,7 +892,7 @@ func (f ValueFactor) GetValue(tor string) float64 { return 1.0 } -// BalanceDigest represents compressed information about a balance +// BalanceSummary represents compressed information about a balance type BalanceSummary struct { UUID string // Balance UUID ID string // Balance ID if not defined @@ -901,9 +901,10 @@ type BalanceSummary struct { Disabled bool } +// BalanceSummaries is a list of BalanceSummaries type BalanceSummaries []*BalanceSummary -// GetBalanceSummary returns a BalanceSummary based on an UUID +// BalanceSummaryWithUUD returns a BalanceSummary based on an UUID func (bs BalanceSummaries) BalanceSummaryWithUUD(bsUUID string) (b *BalanceSummary) { for _, blc := range bs { if blc.UUID == bsUUID { @@ -913,3 +914,24 @@ func (bs BalanceSummaries) BalanceSummaryWithUUD(bsUUID string) (b *BalanceSumma } return } + +// FieldAsInterface func to help EventCost FieldAsInterface +func (bl *BalanceSummary) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.UUID: + return bl.UUID, nil + case utils.ID: + return bl.ID, nil + case utils.Type: + return bl.Type, nil + case utils.Value: + return bl.Value, nil + case utils.Disabled: + return bl.Disabled, nil + } +} diff --git a/engine/eventcost.go b/engine/eventcost.go index 222249360..f63029980 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -21,10 +21,10 @@ package engine import ( "errors" "fmt" - "strconv" - "strings" + "net" "time" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -840,48 +840,28 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error return } -// getIndex returns the path and index if index present -// path[index]=>path,index -// path=>path,nil -func getIndex(spath string) (opath string, idx *int) { - idxStart := strings.Index(spath, utils.IdxStart) - if idxStart == -1 || !strings.HasSuffix(spath, utils.IdxEnd) { - return spath, nil - } - slctr := spath[idxStart+1 : len(spath)-1] - opath = spath[:idxStart] - if strings.HasPrefix(slctr, utils.DynamicDataPrefix) { - return - } - idxVal, err := strconv.Atoi(slctr) - if err != nil { - return spath, nil - } - return opath, &idxVal -} - // FieldAsInterface func to implement DataProvider func (ec *EventCost) FieldAsInterface(fldPath []string) (val interface{}, err error) { if len(fldPath) == 0 { return nil, utils.ErrNotFound } switch fldPath[0] { - default: //"Charges [1]" - opath, indx := getIndex(fldPath[0]) + default: // "Charges[1]" + opath, indx := utils.GetPathIndex(fldPath[0]) if opath != utils.Charges { return nil, fmt.Errorf("unsupported field prefix: <%s>", opath) } if indx != nil { - chr := ec.Charges[*indx] - if len(fldPath) == 1 { - return chr, nil - } - if fldPath[1] == utils.Rating { - return ec.getRatingForPath(fldPath[2:], ec.Rating[chr.RatingID]) + if len(ec.Charges) < *indx { + return nil, utils.ErrNotFound } + return ec.getChargesForPath(fldPath[1:], ec.Charges[*indx]) } - case utils.Charges: // not needed? - // return ec.Charges.FieldAsInterface(fldPath[1:]) + case utils.Charges: + if len(fldPath) != 1 { // slice has no members + return nil, utils.ErrNotFound + } + return ec.Charges, nil case utils.CGRID: if len(fldPath) != 1 { return nil, utils.ErrNotFound @@ -908,21 +888,57 @@ func (ec *EventCost) FieldAsInterface(fldPath []string) (val interface{}, err er } return ec.Cost, nil case utils.AccountSummary: - // return ec.AccountSummary.FieldAsInterface(fldPath[1:]) - case utils.Timings: // not needed? - // return ec.Timings.FieldAsInterface(fldPath[1:]) - case utils.Rates: // not needed? - // return ec.Rates.FieldAsInterface(fldPath[1:]) - case utils.RatingFilters: // not needed? - // return ec.RatingFilters.FieldAsInterface(fldPath[1:]) - case utils.Accounting: // not needed? - // return ec.Accounting.FieldAsInterface(fldPath[1:]) - case utils.Rating: // not needed? - // return ec.Rating.FieldAsInterface(fldPath[1:]) + return ec.AccountSummary.FieldAsInterface(fldPath[1:]) + case utils.Timings: + return ec.Timings.FieldAsInterface(fldPath[1:]) + case utils.Rates: + return ec.Rates.FieldAsInterface(fldPath[1:]) + case utils.RatingFilters: + return ec.RatingFilters.FieldAsInterface(fldPath[1:]) + case utils.Accounting: + return ec.Accounting.FieldAsInterface(fldPath[1:]) + case utils.Rating: + return ec.Rating.FieldAsInterface(fldPath[1:]) } return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) } +func (ec *EventCost) getChargesForPath(fldPath []string, chr *ChargingInterval) (val interface{}, err error) { + if chr == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 0 { + return chr, nil + } + if fldPath[0] == utils.CompressFactor { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return chr.CompressFactor, nil + } + if fldPath[0] == utils.Rating { + return ec.getRatingForPath(fldPath[1:], ec.Rating[chr.RatingID]) + } + opath, indx := utils.GetPathIndex(fldPath[0]) + if opath != utils.Increments { + return nil, fmt.Errorf("unsupported field prefix: <%s>", opath) + } + if indx == nil { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return chr.Increments, nil + } + incr := chr.Increments[*indx] + if len(fldPath) == 1 { + return incr, nil + } + if fldPath[1] == utils.Accounting { + return ec.getAcountingForPath(fldPath[3:], ec.Accounting[incr.AccountingID]) + } + return incr.FieldAsInterface(fldPath) +} + func (ec *EventCost) getRatingForPath(fldPath []string, rating *RatingUnit) (val interface{}, err error) { if rating == nil { return nil, utils.ErrNotFound @@ -933,7 +949,7 @@ func (ec *EventCost) getRatingForPath(fldPath []string, rating *RatingUnit) (val switch fldPath[0] { default: - opath, indx := getIndex(fldPath[0]) + opath, indx := utils.GetPathIndex(fldPath[0]) if opath != utils.Rates { return nil, fmt.Errorf("unsupported field prefix: <%s>", opath) } @@ -978,3 +994,49 @@ func (ec *EventCost) getRatingForPath(fldPath []string, rating *RatingUnit) (val } return rating.FieldAsInterface(fldPath) } + +func (ec *EventCost) getAcountingForPath(fldPath []string, bc *BalanceCharge) (val interface{}, err error) { + if bc == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 0 { + return bc, nil + } + + if fldPath[0] == utils.Balance { + bl := ec.AccountSummary.BalanceSummaries.BalanceSummaryWithUUD(bc.BalanceUUID) + if bl == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 1 { + return bl, nil + } + return bl.FieldAsInterface(fldPath[1:]) + + } + return bc.FieldAsInterface(fldPath) +} + +// String to implement Dataprovider +func (ec *EventCost) String() string { + return utils.ToJSON(ec) +} + +// FieldAsString to implement Dataprovider +func (ec *EventCost) FieldAsString(fldPath []string) (string, error) { + ival, err := ec.FieldAsInterface(fldPath) + if err != nil { + return utils.EmptyString, err + } + return utils.IfaceAsString(ival), nil +} + +// AsNavigableMap to implement Dataprovider +func (ec *EventCost) AsNavigableMap([]*config.FCTemplate) (*config.NavigableMap, error) { + return nil, utils.ErrNotImplemented +} + +// RemoteHost to implement Dataprovider +func (ec *EventCost) RemoteHost() net.Addr { + return utils.LocalAddr() +} diff --git a/engine/libeventcost.go b/engine/libeventcost.go index d57a1ca0a..0b73074f9 100644 --- a/engine/libeventcost.go +++ b/engine/libeventcost.go @@ -106,6 +106,7 @@ func (cIl *ChargingInterval) Cost() float64 { return *cIl.cost } +// TotalCost returns the cost of charges func (cIl *ChargingInterval) TotalCost() float64 { return utils.Round((cIl.Cost() * float64(cIl.CompressFactor)), globalRoundingDecimals, utils.ROUNDING_MIDDLE) @@ -131,6 +132,7 @@ type ChargingIncrement struct { CompressFactor int } +// Equals returns if the structure has the same value func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { return cIt.Usage == oCIt.Usage && cIt.Cost == oCIt.Cost && @@ -145,6 +147,7 @@ func (cIt *ChargingIncrement) PartiallyEquals(oCIt *ChargingIncrement) bool { cIt.AccountingID == oCIt.AccountingID } +// Clone creates a copy of ChargingIncrement func (cIt *ChargingIncrement) Clone() (cln *ChargingIncrement) { cln = new(ChargingIncrement) *cln = *cIt @@ -156,10 +159,30 @@ func (cIt *ChargingIncrement) TotalUsage() time.Duration { return time.Duration(cIt.Usage.Nanoseconds() * int64(cIt.CompressFactor)) } +// TotalCost returns the cost of the increment func (cIt *ChargingIncrement) TotalCost() float64 { return cIt.Cost * float64(cIt.CompressFactor) } +// FieldAsInterface func to help EventCost FieldAsInterface +func (cIt *ChargingIncrement) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.Usage: + return cIt.Usage, nil + case utils.Cost: + return cIt.Cost, nil + case utils.AccountingID: + return cIt.AccountingID, nil + case utils.CompressFactor: + return cIt.CompressFactor, nil + } +} + // BalanceCharge represents one unit charged to a balance type BalanceCharge struct { AccountID string // keep reference for shared balances @@ -169,6 +192,28 @@ type BalanceCharge struct { ExtraChargeID string // used in cases when paying *voice with *monetary } +// FieldAsInterface func to help EventCost FieldAsInterface +func (bc *BalanceCharge) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.AccountID: + return bc.AccountID, nil + case utils.BalanceUUID: + return bc.BalanceUUID, nil + case utils.RatingID: + return bc.RatingID, nil + case utils.Units: + return bc.Units, nil + case utils.ExtraChargeID: + return bc.ExtraChargeID, nil + } +} + +// Equals returns if the structure have the same fields func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { bcExtraChargeID := bc.ExtraChargeID if bcExtraChargeID == "" { @@ -185,14 +230,17 @@ func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { bcExtraChargeID == oBCExtraChargerID } +// Clone creates a copy of BalanceCharge func (bc *BalanceCharge) Clone() *BalanceCharge { clnBC := new(BalanceCharge) *clnBC = *bc return clnBC } +// RatingMatchedFilters a rating filter type RatingMatchedFilters map[string]interface{} +// Equals returns if the RatingMatchedFilters are equal func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) bool { for k := range rf { if rf[k] != oRF[k] { @@ -202,6 +250,7 @@ func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) bool { return true } +// Clone creates a copy of RatingMatchedFilters func (rf RatingMatchedFilters) Clone() (cln map[string]interface{}) { if rf == nil { return nil @@ -213,6 +262,18 @@ func (rf RatingMatchedFilters) Clone() (cln map[string]interface{}) { return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (rf *RatingMatchedFilters) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + ct, has := (*rf)[fldPath[0]] + if !has || ct == nil { + return nil, utils.ErrNotFound + } + return ct, nil +} + // ChargedTiming represents one timing attached to a charge type ChargedTiming struct { Years utils.Years @@ -222,6 +283,7 @@ type ChargedTiming struct { StartTime string } +// Equals returns if the timings are equal func (ct *ChargedTiming) Equals(oCT *ChargedTiming) bool { return ct.Years.Equals(oCT.Years) && ct.Months.Equals(oCT.Months) && @@ -230,12 +292,34 @@ func (ct *ChargedTiming) Equals(oCT *ChargedTiming) bool { ct.StartTime == oCT.StartTime } +// Clone creates a copy of ChargedTiming func (ct *ChargedTiming) Clone() (cln *ChargedTiming) { cln = new(ChargedTiming) *cln = *ct return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (ct *ChargedTiming) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.YearsFieldName: + return ct.Years, nil + case utils.MonthsFieldName: + return ct.Months, nil + case utils.MonthDaysFieldName: + return ct.MonthDays, nil + case utils.WeekDaysFieldName: + return ct.WeekDays, nil + case utils.StartTime: + return ct.StartTime, nil + } +} + // RatingUnit represents one unit out of RatingPlan matching for an event type RatingUnit struct { ConnectFee float64 @@ -248,6 +332,7 @@ type RatingUnit struct { RatingFiltersID string } +// Equals returns if RatingUnit is equal to the other func (ru *RatingUnit) Equals(oRU *RatingUnit) bool { return ru.ConnectFee == oRU.ConnectFee && ru.RoundingMethod == oRU.RoundingMethod && @@ -259,15 +344,44 @@ func (ru *RatingUnit) Equals(oRU *RatingUnit) bool { ru.RatingFiltersID == oRU.RatingFiltersID } +// Clone creates a copy of RatingUnit func (ru *RatingUnit) Clone() (cln *RatingUnit) { cln = new(RatingUnit) *cln = *ru return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (ru *RatingUnit) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.ConnectFee: + return ru.ConnectFee, nil + case utils.RoundingMethod: + return ru.RoundingMethod, nil + case utils.RoundingDecimals: + return ru.RoundingDecimals, nil + case utils.MaxCost: + return ru.MaxCost, nil + case utils.MaxCostStrategy: + return ru.MaxCostStrategy, nil + case utils.TimingID: + return ru.TimingID, nil + case utils.RatesID: + return ru.RatesID, nil + case utils.RatingFiltersID: + return ru.RatingFiltersID, nil + } +} + +// RatingFilters the map of rating filters type RatingFilters map[string]RatingMatchedFilters // so we can define search methods -// GetWithSet attempts to retrieve the UUID of a matching data or create a new one +// GetIDWithSet attempts to retrieve the UUID of a matching data or create a new one func (rfs RatingFilters) GetIDWithSet(rmf RatingMatchedFilters) string { if rmf == nil || len(rmf) == 0 { return "" @@ -283,6 +397,7 @@ func (rfs RatingFilters) GetIDWithSet(rmf RatingMatchedFilters) string { return uuid } +// Clone creates a copy of RatingFilters func (rfs RatingFilters) Clone() (cln RatingFilters) { cln = make(RatingFilters, len(rfs)) for k, v := range rfs { @@ -291,6 +406,22 @@ func (rfs RatingFilters) Clone() (cln RatingFilters) { return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (rfs *RatingFilters) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + ct, has := (*rfs)[fldPath[0]] + if !has || ct == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 1 { + return ct, nil + } + return ct.FieldAsInterface(fldPath[1:]) +} + +// Rating the map of rating units type Rating map[string]*RatingUnit // GetIDWithSet attempts to retrieve the UUID of a matching data or create a new one @@ -309,6 +440,7 @@ func (crus Rating) GetIDWithSet(cru *RatingUnit) string { return uuid } +// Clone creates a copy of Rating func (crus Rating) Clone() (cln Rating) { cln = make(Rating, len(crus)) for k, v := range crus { @@ -317,8 +449,50 @@ func (crus Rating) Clone() (cln Rating) { return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (crus *Rating) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + rt, has := (*crus)[fldPath[0]] + if !has || rt == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 1 { + return rt, nil + } + return rt.FieldAsInterface(fldPath[1:]) +} + +// ChargedRates the map with rateGroups type ChargedRates map[string]RateGroups +// FieldAsInterface func to help EventCost FieldAsInterface +func (crs *ChargedRates) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + opath, indx := utils.GetPathIndex(fldPath[0]) + cr, has := (*crs)[opath] + if !has || cr == nil { + return nil, utils.ErrNotFound + } + if indx != nil { + if len(cr) < *indx { + return nil, utils.ErrNotFound + } + rg := cr[*indx] + if len(fldPath) == 1 { + return rg, nil + } + return rg.FieldAsInterface(fldPath[1:]) + } + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + return cr, nil +} + // GetIDWithSet attempts to retrieve the UUID of a matching data or create a new one func (crs ChargedRates) GetIDWithSet(rg RateGroups) string { if rg == nil || len(rg) == 0 { @@ -335,6 +509,7 @@ func (crs ChargedRates) GetIDWithSet(rg RateGroups) string { return uuid } +// Clone creates a copy of ChargedRates func (crs ChargedRates) Clone() (cln ChargedRates) { cln = make(ChargedRates, len(crs)) for k, v := range crs { @@ -343,8 +518,24 @@ func (crs ChargedRates) Clone() (cln ChargedRates) { return } +// ChargedTimings the map of ChargedTiming type ChargedTimings map[string]*ChargedTiming +// FieldAsInterface func to help EventCost FieldAsInterface +func (cts *ChargedTimings) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + ct, has := (*cts)[fldPath[0]] + if !has || ct == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 1 { + return ct, nil + } + return ct.FieldAsInterface(fldPath[1:]) +} + // GetIDWithSet attempts to retrieve the UUID of a matching data or create a new one func (cts ChargedTimings) GetIDWithSet(ct *ChargedTiming) string { if ct == nil { @@ -361,6 +552,7 @@ func (cts ChargedTimings) GetIDWithSet(ct *ChargedTiming) string { return uuid } +// Clone creates a copy of ChargedTimings func (cts ChargedTimings) Clone() (cln ChargedTimings) { cln = make(ChargedTimings, len(cts)) for k, v := range cts { @@ -369,6 +561,7 @@ func (cts ChargedTimings) Clone() (cln ChargedTimings) { return } +// Accounting the map of debited balances type Accounting map[string]*BalanceCharge // GetIDWithSet attempts to retrieve the UUID of a matching data or create a new one @@ -387,6 +580,7 @@ func (cbs Accounting) GetIDWithSet(cb *BalanceCharge) string { return uuid } +// Clone creates a copy of Accounting func (cbs Accounting) Clone() (cln Accounting) { cln = make(Accounting, len(cbs)) for k, v := range cbs { @@ -395,6 +589,21 @@ func (cbs Accounting) Clone() (cln Accounting) { return } +// FieldAsInterface func to help EventCost FieldAsInterface +func (cbs *Accounting) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) == 0 { + return nil, utils.ErrNotFound + } + ac, has := (*cbs)[fldPath[0]] + if !has || ac == nil { + return nil, utils.ErrNotFound + } + if len(fldPath) == 1 { + return ac, nil + } + return ac.FieldAsInterface(fldPath[1:]) +} + // IfaceAsEventCost converts an interface to EventCost func IfaceAsEventCost(itm interface{}) (ec *EventCost, err error) { switch otm := itm.(type) { diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 822e1a934..befd983f0 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -233,6 +233,25 @@ type Rate struct { RateUnit time.Duration } +// FieldAsInterface func to help EventCost FieldAsInterface +func (r *Rate) FieldAsInterface(fldPath []string) (val interface{}, err error) { + if len(fldPath) != 1 { + return nil, utils.ErrNotFound + } + switch fldPath[0] { + default: + return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) + case utils.GroupIntervalStart: + return r.GroupIntervalStart, nil + case utils.Value: + return r.Value, nil + case utils.RateIncrement: + return r.RateIncrement, nil + case utils.RateUnit: + return r.RateUnit, nil + } +} + func (r *Rate) Stringify() string { return utils.Sha1(fmt.Sprintf("%v", r))[:8] } diff --git a/utils/consts.go b/utils/consts.go index 823cd4284..45bf0bddf 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -497,6 +497,29 @@ const ( Accounting = "Accounting" Rating = "Rating" Charges = "Charges" + CompressFactor = "CompressFactor" + Increments = "Increments" + Balance = "Balance" + BalanceSummaries = "BalanceSummaries" + Type = "Type" + YearsFieldName = "Years" + MonthsFieldName = "Months" + MonthDaysFieldName = "MonthDays" + WeekDaysFieldName = "WeekDays" + GroupIntervalStart = "GroupIntervalStart" + RateIncrement = "RateIncrement" + RateUnit = "RateUnit" + BalanceUUID = "BalanceUUID" + RatingID = "RatingID" + ExtraChargeID = "ExtraChargeID" + ConnectFee = "ConnectFee" + RoundingMethod = "RoundingMethod" + RoundingDecimals = "RoundingDecimals" + MaxCostStrategy = "MaxCostStrategy" + TimingID = "TimingID" + RatesID = "RatesID" + RatingFiltersID = "RatingFiltersID" + AccountingID = "AccountingID" MetaSessionS = "*sessions" MetaDefault = "*default" Error = "Error" diff --git a/utils/coreutils.go b/utils/coreutils.go index 52ee222aa..28998e28b 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -892,3 +892,23 @@ func CastRPCErr(err error) error { func RandomInteger(min, max int) int { return math_rand.Intn(max-min) + min } + +// GetPathIndex returns the path and index if index present +// path[index]=>path,index +// path=>path,nil +func GetPathIndex(spath string) (opath string, idx *int) { + idxStart := strings.Index(spath, IdxStart) + if idxStart == -1 || !strings.HasSuffix(spath, IdxEnd) { + return spath, nil + } + slctr := spath[idxStart+1 : len(spath)-1] + opath = spath[:idxStart] + // if strings.HasPrefix(slctr, DynamicDataPrefix) { + // return + // } + idxVal, err := strconv.Atoi(slctr) + if err != nil { + return spath, nil + } + return opath, &idxVal +}