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)
+}