diff --git a/engine/libsuppliers.go b/engine/libsuppliers.go index 88059dd86..bd834bfb4 100644 --- a/engine/libsuppliers.go +++ b/engine/libsuppliers.go @@ -19,7 +19,6 @@ along with this program. If not, see package engine import ( - "errors" "fmt" "sort" "time" @@ -70,6 +69,15 @@ type SupplierEvent struct { Event map[string]interface{} } +func (se *SupplierEvent) CheckMandatoryFields(fldNames []string) error { + for _, fldName := range fldNames { + if _, has := se.Event[fldName]; !has { + return utils.NewErrMandatoryIeMissing(fldName) + } + } + return nil +} + // AnswerTime returns the AnswerTime of SupplierEvent func (le *SupplierEvent) FieldAsString(fldName string) (val string, err error) { iface, has := le.Event[fldName] @@ -78,41 +86,47 @@ func (le *SupplierEvent) FieldAsString(fldName string) (val string, err error) { } val, canCast := utils.CastFieldIfToString(iface) if !canCast { - return "", errors.New("cannot cast to string") + return "", fmt.Errorf("cannot cast %s to string", fldName) } return val, nil } -// AnswerTime returns the AnswerTime of SupplierEvent -func (le *SupplierEvent) AnswerTime(timezone string) (at time.Time, err error) { - atIf, has := le.Event[utils.ANSWER_TIME] +// FieldAsTime returns the a field as Time instance +func (le *SupplierEvent) FieldAsTime(fldName string, timezone string) (t time.Time, err error) { + iface, has := le.Event[fldName] if !has { - return at, utils.ErrNotFound + err = utils.ErrNotFound + return } - if at, canCast := atIf.(time.Time); canCast { - return at, nil + var canCast bool + if t, canCast = iface.(time.Time); canCast { + return } - atStr, canCast := atIf.(string) + s, canCast := iface.(string) if !canCast { - return at, errors.New("cannot cast to string") + err = fmt.Errorf("cannot cast %s to string", fldName) + return } - return utils.ParseTimeDetectLayout(atStr, timezone) + return utils.ParseTimeDetectLayout(s, timezone) } -// AnswerTime returns the AnswerTime of SupplierEvent -func (le *SupplierEvent) Usage() (usage time.Duration, err error) { - iface, has := le.Event[utils.USAGE] +// FieldAsTime returns the a field as Time instance +func (le *SupplierEvent) FieldAsDuration(fldName string) (d time.Duration, err error) { + iface, has := le.Event[fldName] if !has { - return 0, utils.ErrNotFound + err = utils.ErrNotFound + return } - if usage, canCast := iface.(time.Duration); canCast { - return usage, nil + var canCast bool + if d, canCast = iface.(time.Duration); canCast { + return } - usageStr, canCast := iface.(string) + s, canCast := iface.(string) if !canCast { - return 0, errors.New("cannot cast to string") + err = fmt.Errorf("cannot cast %s to string", fldName) + return } - return utils.ParseDurationWithNanosecs(usageStr) + return utils.ParseDurationWithNanosecs(s) } // SuppliersSorter is the interface which needs to be implemented by supplier sorters @@ -158,7 +172,7 @@ func (ws *WeightSorter) SortSuppliers(prflID string, for i, s := range suppls { sortedSuppls.SortedSuppliers[i] = &SortedSupplier{ SupplierID: s.ID, - SortingData: map[string]interface{}{"Weight": s.Weight}} + SortingData: map[string]interface{}{Weight: s.Weight}} } sortedSuppls.SortWeight() return diff --git a/engine/spls_leastcost.go b/engine/spls_leastcost.go index 3b960a122..24c1b6833 100644 --- a/engine/spls_leastcost.go +++ b/engine/spls_leastcost.go @@ -34,14 +34,20 @@ type LeastCostSorter struct { } func (lcs *LeastCostSorter) SortSuppliers(prflID string, - suppls []*Supplier, suplEv *SupplierEvent) (sortedSuppls *SortedSuppliers, err error) { + suppls []*Supplier, ev *SupplierEvent) (sortedSuppls *SortedSuppliers, err error) { sortedSuppls = &SortedSuppliers{ProfileID: prflID, Sorting: lcs.sorting, SortedSuppliers: make([]*SortedSupplier, len(suppls))} for i, s := range suppls { + cost, err := lcs.spS.costForEvent(ev, s.AccountIDs, s.RatingPlanIDs) + if err != nil { + return nil, err + } sortedSuppls.SortedSuppliers[i] = &SortedSupplier{ - SupplierID: s.ID, - SortingData: map[string]interface{}{"Weight": s.Weight}} + SupplierID: s.ID, + SortingData: map[string]interface{}{ + Weight: s.Weight, + Cost: cost}} } sortedSuppls.SortCost() return diff --git a/engine/suppliers.go b/engine/suppliers.go index bf9cb9d2c..28d2ae4df 100644 --- a/engine/suppliers.go +++ b/engine/suppliers.go @@ -23,6 +23,7 @@ import ( "sort" "time" + //"github.com/cgrates/cgrates/cache" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/guardian" "github.com/cgrates/cgrates/utils" @@ -130,7 +131,7 @@ func (spS *SupplierService) matchingSupplierProfilesForEvent(ev *SupplierEvent) } return nil, err } - aTime, err := ev.AnswerTime(spS.timezone) + aTime, err := ev.FieldAsTime(utils.ANSWER_TIME, spS.timezone) if err != nil { if err == utils.ErrNotFound { aTime = time.Now() @@ -142,7 +143,8 @@ func (spS *SupplierService) matchingSupplierProfilesForEvent(ev *SupplierEvent) !lcrPrfl.ActivationInterval.IsActiveAtTime(aTime) { // not active continue } - if pass, err := spS.filterS.PassFiltersForEvent(ev.Tenant, ev.Event, lcrPrfl.FilterIDs); err != nil { + if pass, err := spS.filterS.PassFiltersForEvent(ev.Tenant, + ev.Event, lcrPrfl.FilterIDs); err != nil { return nil, err } else if !pass { continue @@ -166,8 +168,50 @@ func (spS *SupplierService) matchingSupplierProfilesForEvent(ev *SupplierEvent) return } -// costForEvent will compute cost out of ratingPlanIDs for event -func (spS *SupplierService) costForEvent(ev *SupplierEvent, rpIDs []string) (ec *EventCost, err error) { +// costForEvent will compute cost out of accounts and rating plans for event +func (spS *SupplierService) costForEvent(ev *SupplierEvent, + acntIDs, rpIDs []string) (ec *EventCost, err error) { + /*if err = ev.CheckMandatoryFields([]string{utils.ACCOUNT, + utils.DESTINATION, utils.ANSWER_TIME, utils.USAGE}); err != nil { + return + } + var acnt, subj, dst string + if acnt, err = ev.FieldAsString(utils.ACCOUNT); err != nil { + return + } + if subj, err = ev.FieldAsString(utils.ACCOUNT); err != nil { + if err != utils.ErrNotFound { + return + } + subj = acnt + } + if dst, err = ev.FieldAsString(utils.DESTINATION); err != nil { + return + } + var aTime time.Time + if aTime, err = ev.FieldAsTime(utils.ANSWER_TIME, spS.timezone); err != nil { + return + } + var usage time.Duration + if usage, err = ev.FieldAsDuration(utils.USAGE); err != nil { + return + } + for i, rp := range rpIDs { + rPrfl := &RatingProfile{ + Id: utils.ConcatenatedKey(utils.OUT, + ev.Tenant, utils.MetaSuppliers, subj), + RatingPlanActivations: RatingPlanActivations{ + &RatingPlanActivation{ + ActivationTime: aTime, + RatingPlanId: rp, + }, + }, + } + // force cache set so it can be picked by calldescriptor for cost calculation + cache.Set(utils.RATING_PROFILE_PREFIX+rPrfl.Id, rPrfl, + true, utils.NonTransactional) + } + */ return } diff --git a/utils/cgrevent.go b/utils/cgrevent.go new file mode 100644 index 000000000..2a7207130 --- /dev/null +++ b/utils/cgrevent.go @@ -0,0 +1,91 @@ +/* +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 PURPOev. 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 utils + +import ( + "fmt" + "time" +) + +// CGREvent is a generic event processed by CGR services +type CGREvent struct { + Tenant string + ID string + Event map[string]interface{} +} + +func (ev *CGREvent) CheckMandatoryFields(fldNames []string) error { + for _, fldName := range fldNames { + if _, has := ev.Event[fldName]; !has { + return NewErrMandatoryIeMissing(fldName) + } + } + return nil +} + +// AnswerTime returns the AnswerTime of CGREvent +func (ev *CGREvent) FieldAsString(fldName string) (val string, err error) { + iface, has := ev.Event[fldName] + if !has { + return "", ErrNotFound + } + val, canCast := CastFieldIfToString(iface) + if !canCast { + return "", fmt.Errorf("cannot cast %s to string", fldName) + } + return val, nil +} + +// FieldAsTime returns the a field as Time instance +func (ev *CGREvent) FieldAsTime(fldName string, timezone string) (t time.Time, err error) { + iface, has := ev.Event[fldName] + if !has { + err = ErrNotFound + return + } + var canCast bool + if t, canCast = iface.(time.Time); canCast { + return + } + s, canCast := iface.(string) + if !canCast { + err = fmt.Errorf("cannot cast %s to string", fldName) + return + } + return ParseTimeDetectLayout(s, timezone) +} + +// FieldAsTime returns the a field as Time instance +func (ev *CGREvent) FieldAsDuration(fldName string) (d time.Duration, err error) { + iface, has := ev.Event[fldName] + if !has { + err = ErrNotFound + return + } + var canCast bool + if d, canCast = iface.(time.Duration); canCast { + return + } + s, canCast := iface.(string) + if !canCast { + err = fmt.Errorf("cannot cast %s to string", fldName) + return + } + return ParseDurationWithNanosecs(s) +}