diff --git a/accounts/libaccounts.go b/accounts/libaccounts.go
index e066c0fde..0d983cb6d 100644
--- a/accounts/libaccounts.go
+++ b/accounts/libaccounts.go
@@ -119,11 +119,11 @@ func processAttributeS(connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
// rateSCostForEvent will process the event with RateS in order to get the cost
func rateSCostForEvent(connMgr *engine.ConnManager, cgrEv *utils.CGREvent,
- rateSConns, rpIDs []string) (rplyCost *engine.RateProfileCost, err error) {
+ rateSConns, rpIDs []string) (rplyCost *utils.RateProfileCost, err error) {
if len(rateSConns) == 0 {
return nil, utils.NewErrNotConnected(utils.RateS)
}
- var tmpReply engine.RateProfileCost
+ var tmpReply utils.RateProfileCost
if err = connMgr.Call(rateSConns, nil, utils.RateSv1CostForEvent,
&utils.ArgsCostForEvent{CGREvent: cgrEv, RateProfileIDs: rpIDs}, &tmpReply); err != nil {
return
@@ -250,7 +250,7 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big,
return nil, utils.ErrMaxIncrementsExceeded
}
if calculateCost {
- var rplyCost *engine.RateProfileCost
+ var rplyCost *utils.RateProfileCost
if rplyCost, err = rateSCostForEvent(connMgr, cgrEv, rateSConns, rpIDs); err != nil {
err = utils.NewErrRateS(err)
return
diff --git a/apier/v1/api_interfaces.go b/apier/v1/api_interfaces.go
index a2a136197..8ff5cffe3 100644
--- a/apier/v1/api_interfaces.go
+++ b/apier/v1/api_interfaces.go
@@ -184,7 +184,7 @@ type CoreSv1Interface interface {
type RateSv1Interface interface {
Ping(ign *utils.CGREvent, reply *string) error
- CostForEvent(args *utils.ArgsCostForEvent, rpCost *engine.RateProfileCost) error
+ CostForEvent(args *utils.ArgsCostForEvent, rpCost *utils.RateProfileCost) error
}
type RateProfileSv1Interface interface {
@@ -239,7 +239,7 @@ type ReplicatorSv1Interface interface {
SetAttributeProfile(ap *engine.AttributeProfileWithOpts, reply *string) error
SetChargerProfile(cp *engine.ChargerProfileWithOpts, reply *string) error
SetDispatcherProfile(dpp *engine.DispatcherProfileWithOpts, reply *string) error
- SetRateProfile(dpp *engine.RateProfileWithOpts, reply *string) error
+ SetRateProfile(dpp *utils.RateProfileWithOpts, reply *string) error
SetActionPlan(args *engine.SetActionPlanArgWithOpts, reply *string) error
SetAccountActionPlans(args *engine.SetAccountActionPlansArgWithOpts, reply *string) error
SetDispatcherHost(dpp *engine.DispatcherHostWithOpts, reply *string) error
diff --git a/apier/v1/dispatcher.go b/apier/v1/dispatcher.go
index 907a1d528..5c0bfbb26 100755
--- a/apier/v1/dispatcher.go
+++ b/apier/v1/dispatcher.go
@@ -1137,7 +1137,7 @@ func (dS *DispatcherReplicatorSv1) SetDispatcherProfile(args *engine.DispatcherP
}
// SetRateProfile
-func (dS *DispatcherReplicatorSv1) SetRateProfile(args *engine.RateProfileWithOpts, reply *string) error {
+func (dS *DispatcherReplicatorSv1) SetRateProfile(args *utils.RateProfileWithOpts, reply *string) error {
return dS.dS.ReplicatorSv1SetRateProfile(args, reply)
}
@@ -1335,7 +1335,7 @@ func (dR *DispatcherRateSv1) Ping(args *utils.CGREvent, reply *string) error {
return dR.dR.RateSv1Ping(args, reply)
}
-func (dR *DispatcherRateSv1) CostForEvent(args *utils.ArgsCostForEvent, rpCost *engine.RateProfileCost) error {
+func (dR *DispatcherRateSv1) CostForEvent(args *utils.ArgsCostForEvent, rpCost *utils.RateProfileCost) error {
return dR.dR.RateSv1CostForEvent(args, rpCost)
}
diff --git a/apier/v1/rateprofiles.go b/apier/v1/rateprofiles.go
index b0fe9999b..dd6184f9c 100644
--- a/apier/v1/rateprofiles.go
+++ b/apier/v1/rateprofiles.go
@@ -23,7 +23,6 @@ import (
"github.com/cgrates/cgrates/rates"
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -212,7 +211,7 @@ func (rSv1 *RateSv1) Call(serviceMethod string,
return utils.APIerRPCCall(rSv1, serviceMethod, args, reply)
}
-func (rSv1 *RateSv1) CostForEvent(args *utils.ArgsCostForEvent, rpCost *engine.RateProfileCost) (err error) {
+func (rSv1 *RateSv1) CostForEvent(args *utils.ArgsCostForEvent, rpCost *utils.RateProfileCost) (err error) {
return rSv1.rS.V1CostForEvent(args, rpCost)
}
diff --git a/apier/v1/replicator.go b/apier/v1/replicator.go
index 5aa42dc6c..6182a2679 100644
--- a/apier/v1/replicator.go
+++ b/apier/v1/replicator.go
@@ -662,7 +662,7 @@ func (rplSv1 *ReplicatorSv1) SetDispatcherHost(dpp *engine.DispatcherHostWithOpt
}
// SetRateProfile is the replication method coresponding to the dataDb driver method
-func (rplSv1 *ReplicatorSv1) SetRateProfile(dpp *engine.RateProfileWithOpts, reply *string) (err error) {
+func (rplSv1 *ReplicatorSv1) SetRateProfile(dpp *utils.RateProfileWithOpts, reply *string) (err error) {
if err = rplSv1.dm.DataDB().SetRateProfileDrv(dpp.RateProfile); err != nil {
return
}
diff --git a/console/rates_profile.go b/console/rates_profile.go
index 9af36a608..615eca642 100644
--- a/console/rates_profile.go
+++ b/console/rates_profile.go
@@ -19,7 +19,6 @@ along with this program. If not, see
package console
import (
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -61,6 +60,6 @@ func (self *CmdGetRateProfile) PostprocessRpcParams() error {
}
func (self *CmdGetRateProfile) RpcResult() interface{} {
- var atr engine.RateProfile
+ var atr utils.RateProfile
return &atr
}
diff --git a/console/rates_profile_set.go b/console/rates_profile_set.go
index 927989b73..826c9d790 100644
--- a/console/rates_profile_set.go
+++ b/console/rates_profile_set.go
@@ -50,9 +50,11 @@ func (self *CmdSetRateProfile) RpcMethod() string {
func (self *CmdSetRateProfile) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
- self.rpcParams = &engine.APIRateProfileWithOpts{
- APIRateProfile: new(engine.APIRateProfile),
- Opts: make(map[string]interface{}),
+ self.rpcParams = &v1.APIRateProfileWithCache{
+ APIRateProfileWithOpts: &utils.APIRateProfileWithOpts{
+ APIRateProfile: new(utils.APIRateProfile),
+ Opts: make(map[string]interface{}),
+ },
}
}
return self.rpcParams
diff --git a/dispatchers/rates.go b/dispatchers/rates.go
index bbe49aa6f..bf9b6438f 100644
--- a/dispatchers/rates.go
+++ b/dispatchers/rates.go
@@ -19,7 +19,6 @@ along with this program. If not, see
package dispatchers
import (
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -37,7 +36,7 @@ func (dS *DispatcherService) RateSv1Ping(args *utils.CGREvent, rpl *string) (err
return dS.Dispatch(args, utils.RateS, utils.RateSv1Ping, args, rpl)
}
-func (dS *DispatcherService) RateSv1CostForEvent(args *utils.ArgsCostForEvent, rpCost *engine.RateProfileCost) (err error) {
+func (dS *DispatcherService) RateSv1CostForEvent(args *utils.ArgsCostForEvent, rpCost *utils.RateProfileCost) (err error) {
if args == nil {
args = new(utils.ArgsCostForEvent)
}
diff --git a/dispatchers/replicator.go b/dispatchers/replicator.go
index 9fd07fe14..a5ed36862 100644
--- a/dispatchers/replicator.go
+++ b/dispatchers/replicator.go
@@ -852,9 +852,9 @@ func (dS *DispatcherService) ReplicatorSv1SetDispatcherProfile(args *engine.Disp
}, utils.MetaReplicator, utils.ReplicatorSv1SetDispatcherProfile, args, rpl)
}
-func (dS *DispatcherService) ReplicatorSv1SetRateProfile(args *engine.RateProfileWithOpts, rpl *string) (err error) {
+func (dS *DispatcherService) ReplicatorSv1SetRateProfile(args *utils.RateProfileWithOpts, rpl *string) (err error) {
if args == nil {
- args = &engine.RateProfileWithOpts{}
+ args = &utils.RateProfileWithOpts{}
}
args.Tenant = utils.FirstNonEmpty(args.Tenant, dS.cfg.GeneralCfg().DefaultTenant)
if len(dS.cfg.DispatcherSCfg().AttributeSConns) != 0 {
diff --git a/engine/caches.go b/engine/caches.go
index f38781ead..c3645d8fb 100644
--- a/engine/caches.go
+++ b/engine/caches.go
@@ -65,8 +65,8 @@ func init() {
gob.Register(new(DispatcherHostProfile))
gob.Register(new(DispatcherHostWithOpts))
// RateProfiles
- gob.Register(new(RateProfile))
- gob.Register(new(RateProfileWithOpts))
+ gob.Register(new(utils.RateProfile))
+ gob.Register(new(utils.RateProfileWithOpts))
// ActionProfiles
gob.Register(new(ActionProfile))
gob.Register(new(ActionProfileWithAPIOpts))
diff --git a/engine/datadbmock.go b/engine/datadbmock.go
index 078a9d18f..2ea1d31b8 100644
--- a/engine/datadbmock.go
+++ b/engine/datadbmock.go
@@ -382,11 +382,11 @@ func (dbM *DataDBMock) RemoveDispatcherHostDrv(string, string) error {
return utils.ErrNotImplemented
}
-func (dbM *DataDBMock) GetRateProfileDrv(string, string) (*RateProfile, error) {
+func (dbM *DataDBMock) GetRateProfileDrv(string, string) (*utils.RateProfile, error) {
return nil, utils.ErrNotImplemented
}
-func (dbM *DataDBMock) SetRateProfileDrv(*RateProfile) error {
+func (dbM *DataDBMock) SetRateProfileDrv(*utils.RateProfile) error {
return utils.ErrNotImplemented
}
diff --git a/engine/datamanager.go b/engine/datamanager.go
index f09996d9e..0e771968c 100644
--- a/engine/datamanager.go
+++ b/engine/datamanager.go
@@ -3037,14 +3037,14 @@ func (dm *DataManager) SetLoadIDs(loadIDs map[string]int64) (err error) {
}
func (dm *DataManager) GetRateProfile(tenant, id string, cacheRead, cacheWrite bool,
- transactionID string) (rpp *RateProfile, err error) {
+ transactionID string) (rpp *utils.RateProfile, err error) {
tntID := utils.ConcatenatedKey(tenant, id)
if cacheRead {
if x, ok := Cache.Get(utils.CacheRateProfiles, tntID); ok {
if x == nil {
return nil, utils.ErrNotFound
}
- return x.(*RateProfile), nil
+ return x.(*utils.RateProfile), nil
}
}
if dm == nil {
@@ -3090,7 +3090,7 @@ func (dm *DataManager) GetRateProfile(tenant, id string, cacheRead, cacheWrite b
return
}
-func (dm *DataManager) SetRateProfile(rpp *RateProfile, withIndex bool) (err error) {
+func (dm *DataManager) SetRateProfile(rpp *utils.RateProfile, withIndex bool) (err error) {
if dm == nil {
return utils.ErrNoDatabaseConn
}
@@ -3158,7 +3158,7 @@ func (dm *DataManager) SetRateProfile(rpp *RateProfile, withIndex bool) (err err
config.CgrConfig().DataDbCfg().RplFiltered,
utils.RateProfilePrefix, rpp.TenantID(), // this are used to get the host IDs from cache
utils.ReplicatorSv1SetRateProfile,
- &RateProfileWithOpts{
+ &utils.RateProfileWithOpts{
RateProfile: rpp,
Opts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID,
config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)})
@@ -3223,7 +3223,7 @@ func (dm *DataManager) RemoveRateProfileRates(tenant, id string, rateIDs []strin
}
}
}
- oldRpp.Rates = map[string]*Rate{}
+ oldRpp.Rates = map[string]*utils.Rate{}
} else {
for _, rateID := range rateIDs {
if _, has := oldRpp.Rates[rateID]; !has {
@@ -3248,7 +3248,7 @@ func (dm *DataManager) RemoveRateProfileRates(tenant, id string, rateIDs []strin
config.CgrConfig().DataDbCfg().RplFiltered,
utils.RateProfilePrefix, oldRpp.TenantID(), // this are used to get the host IDs from cache
utils.ReplicatorSv1SetRateProfile,
- &RateProfileWithOpts{
+ &utils.RateProfileWithOpts{
RateProfile: oldRpp,
Opts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID,
config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)})
@@ -3256,7 +3256,7 @@ func (dm *DataManager) RemoveRateProfileRates(tenant, id string, rateIDs []strin
return
}
-func (dm *DataManager) SetRateProfileRates(rpp *RateProfile, withIndex bool) (err error) {
+func (dm *DataManager) SetRateProfileRates(rpp *utils.RateProfile, withIndex bool) (err error) {
if dm == nil {
return utils.ErrNoDatabaseConn
}
@@ -3298,7 +3298,7 @@ func (dm *DataManager) SetRateProfileRates(rpp *RateProfile, withIndex bool) (er
config.CgrConfig().DataDbCfg().RplFiltered,
utils.RateProfilePrefix, oldRpp.TenantID(), // this are used to get the host IDs from cache
utils.ReplicatorSv1SetRateProfile,
- &RateProfileWithOpts{
+ &utils.RateProfileWithOpts{
RateProfile: oldRpp,
Opts: utils.GenerateDBItemOpts(itm.APIKey, itm.RouteID,
config.CgrConfig().DataDbCfg().RplCache, utils.EmptyString)})
diff --git a/engine/libindex.go b/engine/libindex.go
index 95cc1b16a..3c4d55d64 100644
--- a/engine/libindex.go
+++ b/engine/libindex.go
@@ -772,7 +772,7 @@ func UpdateFilterIndex(dm *DataManager, oldFlt, newFlt *Filter) (err error) {
removeIndexKeys, ids); err != nil {
return
}
- var rp *RateProfile
+ var rp *utils.RateProfile
if rp, err = dm.GetRateProfile(newFlt.Tenant, rpID, true, false, utils.NonTransactional); err != nil {
return
}
diff --git a/engine/model_helpers.go b/engine/model_helpers.go
index a520c88a3..fa57e7093 100644
--- a/engine/model_helpers.go
+++ b/engine/model_helpers.go
@@ -3060,13 +3060,13 @@ func APItoModelTPRateProfile(tPrf *utils.TPRateProfile) (mdls RateProfileMdls) {
return
}
-func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *RateProfile, err error) {
- rp = &RateProfile{
+func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *utils.RateProfile, err error) {
+ rp = &utils.RateProfile{
Tenant: tpRp.Tenant,
ID: tpRp.ID,
FilterIDs: make([]string, len(tpRp.FilterIDs)),
MaxCostStrategy: tpRp.MaxCostStrategy,
- Rates: make(map[string]*Rate),
+ Rates: make(map[string]*utils.Rate),
MinCost: utils.NewDecimalFromFloat64(tpRp.MinCost),
MaxCost: utils.NewDecimalFromFloat64(tpRp.MaxCost),
}
@@ -3086,12 +3086,12 @@ func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *RateProfi
}
}
for key, rate := range tpRp.Rates {
- rp.Rates[key] = &Rate{
+ rp.Rates[key] = &utils.Rate{
ID: rate.ID,
Blocker: rate.Blocker,
FilterIDs: rate.FilterIDs,
ActivationTimes: rate.ActivationTimes,
- IntervalRates: make([]*IntervalRate, len(rate.IntervalRates)),
+ IntervalRates: make([]*utils.IntervalRate, len(rate.IntervalRates)),
}
if rate.Weights != utils.EmptyString {
weight, err := utils.NewDynamicWeightsFromString(rate.Weights, ";", "&")
@@ -3101,8 +3101,8 @@ func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *RateProfi
rp.Rates[key].Weights = weight
}
for i, iRate := range rate.IntervalRates {
- rp.Rates[key].IntervalRates[i] = new(IntervalRate)
- if rp.Rates[key].IntervalRates[i].IntervalStart, err = utils.ParseDurationWithNanosecs(iRate.IntervalStart); err != nil {
+ rp.Rates[key].IntervalRates[i] = new(utils.IntervalRate)
+ if rp.Rates[key].IntervalRates[i].IntervalStart, err = utils.NewDecimalFromUsage(iRate.IntervalStart); err != nil {
return nil, err
}
rp.Rates[key].IntervalRates[i].FixedFee = utils.NewDecimalFromFloat64(iRate.FixedFee)
@@ -3118,7 +3118,7 @@ func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *RateProfi
return rp, nil
}
-func RateProfileToAPI(rp *RateProfile) (tpRp *utils.TPRateProfile) {
+func RateProfileToAPI(rp *utils.RateProfile) (tpRp *utils.TPRateProfile) {
tpRp = &utils.TPRateProfile{
Tenant: rp.Tenant,
ID: rp.ID,
diff --git a/engine/rateprofile.go b/engine/rateprofile.go
deleted file mode 100644
index 505600ca3..000000000
--- a/engine/rateprofile.go
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
-Real-time Online/Offline Charging System (OerS) 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 engine
-
-import (
- "fmt"
- "sort"
- "time"
-
- "github.com/ericlagergren/decimal"
-
- "github.com/cgrates/cgrates/utils"
- "github.com/cgrates/cron"
-)
-
-// RateProfile represents the configuration of a Rate profile
-type RateProfile struct {
- Tenant string
- ID string
- FilterIDs []string
- ActivationInterval *utils.ActivationInterval
- Weights utils.DynamicWeights
- MinCost *utils.Decimal
- MaxCost *utils.Decimal
- MaxCostStrategy string
- Rates map[string]*Rate
-}
-
-func (rp *RateProfile) TenantID() string {
- return utils.ConcatenatedKey(rp.Tenant, rp.ID)
-}
-
-func (rp *RateProfile) Compile() (err error) {
- for _, rtP := range rp.Rates {
- rtP.uID = utils.ConcatenatedKey(rp.Tenant, rp.ID, rtP.ID)
- if err = rtP.Compile(); err != nil {
- return
- }
- }
- return
-}
-
-// Rate defines rate related information used within a RateProfile
-type Rate struct {
- ID string // RateID
- FilterIDs []string // RateFilterIDs
- ActivationTimes string // ActivationTimes is a cron formatted time interval
- Weights utils.DynamicWeights // RateWeight will decide the winner per interval start
- Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
- IntervalRates []*IntervalRate
-
- sched cron.Schedule // compiled version of activation times as cron.Schedule interface
- uID string
-}
-
-// UID returns system wide unique identifier
-func (rt *Rate) UID() string {
- return rt.uID
-}
-
-type IntervalRate struct {
- IntervalStart time.Duration // Starting point when the Rate kicks in
- FixedFee *utils.Decimal
- RecurrentFee *utils.Decimal
- Unit *utils.Decimal // RateUnit
- Increment *utils.Decimal // RateIncrement
-}
-
-func (rt *Rate) Compile() (err error) {
- aTime := rt.ActivationTimes
- if aTime == utils.EmptyString {
- aTime = "* * * * *"
- }
- if rt.sched, err = cron.ParseStandard(aTime); err != nil {
- return
- }
- return
-}
-
-// RunTimes returns the set of activation and deactivation times for this rate on the interval between >=sTime and , sTime: <%+v>, eTime: <%+v>",
- rt, sTime, eTime))
- return nil, utils.ErrMaxIterationsReached
-}
-
-// RateProfileWithOpts is used in replicatorV1 for dispatcher
-type RateProfileWithOpts struct {
- *RateProfile
- Opts map[string]interface{}
-}
-
-// RateSInterval is used by RateS to integrate Rate info for one charging interval
-type RateSInterval struct {
- UsageStart time.Duration
- Increments []*RateSIncrement
- CompressFactor int64
-
- cost *decimal.Big // unexported total interval cost
-}
-
-type RateSIncrement struct {
- UsageStart time.Duration
- Rate *Rate
- IntervalRateIndex int
- CompressFactor int64
- Usage time.Duration
-
- cost *decimal.Big // unexported total increment cost
-}
-
-// RateProfileCost is the cost returned by RateS at cost queries
-type RateProfileCost struct {
- ID string // RateProfileID
- Cost float64
- MinCost float64
- MaxCost float64
- MaxCostStrategy string
- RateSIntervals []*RateSInterval
- Altered []string
-}
-
-// CorrectCost should be called in final phase of cost calculation
-// in order to apply further correction like Min/MaxCost or rounding
-func (rPc *RateProfileCost) CorrectCost(rndDec *int, rndMtd string) {
- if rPc.MinCost != 0 && rPc.Cost < rPc.MinCost {
- rPc.Cost = rPc.MinCost
- rPc.Altered = append(rPc.Altered, utils.MinCost)
- }
- if rPc.MaxCost != 0 && rPc.Cost > rPc.MaxCost {
- rPc.Cost = rPc.MaxCost
- rPc.Altered = append(rPc.Altered, utils.MaxCost)
- }
- if rndDec != nil {
- rPc.Cost = utils.Round(rPc.Cost, *rndDec, rndMtd)
- rPc.Altered = append(rPc.Altered, utils.RoundingDecimals)
- }
-}
-
-// Sort will sort the IntervalRates from each Rate based on IntervalStart
-func (rpp *RateProfile) Sort() {
- for _, rate := range rpp.Rates {
- sort.Slice(rate.IntervalRates, func(i, j int) bool {
- return rate.IntervalRates[i].IntervalStart < rate.IntervalRates[j].IntervalStart
- })
- }
-}
-
-// CompressEquals compares two RateSIntervals for Compress function
-func (rIv *RateSInterval) CompressEquals(rIv2 *RateSInterval) (eq bool) {
- if len(rIv.Increments) != len(rIv2.Increments) {
- return
- }
- for i, rIcr := range rIv.Increments {
- if !rIcr.CompressEquals(rIv2.Increments[i]) {
- return
- }
- }
- return true
-}
-
-func (rIv *RateSInterval) Cost() *decimal.Big {
- if rIv.cost == nil {
- rIv.cost = new(decimal.Big)
- for _, incrm := range rIv.Increments {
- rIv.cost = utils.SumBig(rIv.cost, incrm.Cost())
- }
- }
- return rIv.cost
-}
-
-// CompressEquals compares two RateSIncrement for Compress function
-func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
- if rIcr.Rate.UID() != rIcr2.Rate.UID() {
- return
- }
- if rIcr.IntervalRateIndex != rIcr2.IntervalRateIndex {
- return
- }
- if rIcr.Usage != rIcr2.Usage {
- return
- }
- return true
-}
-
-// Cost computes the Cost on RateSIncrement
-func (rIcr *RateSIncrement) Cost() *decimal.Big {
- if rIcr.cost == nil {
- icrRt := rIcr.Rate.IntervalRates[rIcr.IntervalRateIndex]
- if rIcr.Usage == utils.InvalidDuration { // FixedFee
- rIcr.cost = icrRt.FixedFee.Big
- } else {
- rIcr.cost = icrRt.RecurrentFee.Big
- if icrRt.Unit != icrRt.Increment {
- rIcr.cost = utils.DivideBig(
- utils.MultiplyBig(rIcr.cost, icrRt.Increment.Big),
- icrRt.Unit.Big)
- }
- if rIcr.CompressFactor != 1 {
- rIcr.cost = utils.MultiplyBig(
- rIcr.cost,
- new(decimal.Big).SetUint64(uint64(rIcr.CompressFactor)))
- }
- }
- }
- return rIcr.cost
-}
-
-// CostForIntervals sums the costs for all intervals
-func CostForIntervals(rtIvls []*RateSInterval) (cost *decimal.Big) {
- cost = new(decimal.Big)
- for _, rtIvl := range rtIvls {
- cost = utils.SumBig(cost, rtIvl.Cost())
- }
- return
-}
-
-// CompressIntervals will compress intervals which equal
-func CompressIntervals(rtIvls []*RateSInterval) {
-}
-
-func (ext *APIRateProfile) AsRateProfile() (rp *RateProfile, err error) {
- rp = &RateProfile{
- Tenant: ext.Tenant,
- ID: ext.ID,
- FilterIDs: ext.FilterIDs,
- ActivationInterval: ext.ActivationInterval,
- MaxCostStrategy: ext.MaxCostStrategy,
- }
- if ext.Weights != utils.EmptyString {
- if rp.Weights, err = utils.NewDynamicWeightsFromString(ext.Weights, ";", "&"); err != nil {
- return nil, err
- }
- }
- if ext.MinCost != nil {
- rp.MinCost = utils.NewDecimalFromFloat64(*ext.MinCost)
- }
- if ext.MaxCost != nil {
- rp.MaxCost = utils.NewDecimalFromFloat64(*ext.MaxCost)
- }
- if len(ext.Rates) != 0 {
- rp.Rates = make(map[string]*Rate)
- for key, extRate := range ext.Rates {
- if rp.Rates[key], err = extRate.AsRate(); err != nil {
- return
- }
- }
- }
- if err = rp.Compile(); err != nil {
- return
- }
- return
-}
-
-type APIRateProfile struct {
- Tenant string
- ID string
- FilterIDs []string
- ActivationInterval *utils.ActivationInterval
- Weights string
- MinCost *float64
- MaxCost *float64
- MaxCostStrategy string
- Rates map[string]*APIRate
-}
-
-type APIRateProfileWithOpts struct {
- *APIRateProfile
- Opts map[string]interface{}
-}
-
-func (ext *APIRate) AsRate() (rate *Rate, err error) {
- rate = &Rate{
- ID: ext.ID,
- FilterIDs: ext.FilterIDs,
- ActivationTimes: ext.ActivationTimes,
- Blocker: ext.Blocker,
- }
- if ext.Weights != utils.EmptyString {
- if rate.Weights, err = utils.NewDynamicWeightsFromString(ext.Weights, ";", "&"); err != nil {
- return nil, err
- }
- }
- if len(ext.IntervalRates) != 0 {
- rate.IntervalRates = make([]*IntervalRate, len(ext.IntervalRates))
- for i, iRate := range ext.IntervalRates {
- if rate.IntervalRates[i], err = iRate.AsIntervalRate(); err != nil {
- return
- }
- }
- }
- return
-}
-
-type APIRate struct {
- ID string // RateID
- FilterIDs []string // RateFilterIDs
- ActivationTimes string // ActivationTimes is a cron formatted time interval
- Weights string // RateWeight will decide the winner per interval start
- Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
- IntervalRates []*APIIntervalRate
-}
-
-func (ext *APIIntervalRate) AsIntervalRate() (iRate *IntervalRate, err error) {
- iRate = new(IntervalRate)
- if iRate.IntervalStart, err = utils.ParseDurationWithNanosecs(ext.IntervalStart); err != nil {
- return
- }
- if ext.FixedFee != nil {
- iRate.FixedFee = utils.NewDecimalFromFloat64(*ext.FixedFee)
- }
- if ext.RecurrentFee != nil {
- iRate.RecurrentFee = utils.NewDecimalFromFloat64(*ext.RecurrentFee)
- }
- if ext.Unit != nil {
- iRate.Unit = utils.NewDecimalFromFloat64(*ext.Unit)
- }
- if ext.Increment != nil {
- iRate.Increment = utils.NewDecimalFromFloat64(*ext.Increment)
- }
- return
-}
-
-type APIIntervalRate struct {
- IntervalStart string
- FixedFee *float64
- RecurrentFee *float64
- Unit *float64 // RateUnit
- Increment *float64 // RateIncrement
-}
diff --git a/engine/rateprofile_test.go b/engine/rateprofile_test.go
deleted file mode 100644
index 9b8695be5..000000000
--- a/engine/rateprofile_test.go
+++ /dev/null
@@ -1,1210 +0,0 @@
-/*
-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 engine
-
-import (
- "reflect"
- "testing"
- "time"
-
- "github.com/ericlagergren/decimal"
-
- "github.com/cgrates/cron"
-
- "github.com/cgrates/cgrates/utils"
-)
-
-func TestRateProfileSort(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rPrf := &RateProfile{
- Tenant: "cgrates.org",
- ID: "RP1",
- Rates: map[string]*Rate{
- "RT_WEEK": {
- ID: "RT_WEEK",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * 1-5",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_Custom": {
- ID: "RT_Custom",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * 1-5",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: time.Second,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Second,
- RecurrentFee: utils.NewDecimal(19, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 15 * time.Second,
- RecurrentFee: utils.NewDecimal(4, 1),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 10 * time.Second,
- RecurrentFee: utils.NewDecimal(27, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_WEEKEND": {
- ID: "RT_WEEKEND",
- Weights: utils.DynamicWeights{
- {
- Weight: 10,
- },
- },
- ActivationTimes: "* * * * 0,6",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 10 * time.Second,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(18, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 18 * time.Second,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_CHRISTMAS": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- },
- }
- exp := &RateProfile{
- Tenant: "cgrates.org",
- ID: "RP1",
- Rates: map[string]*Rate{
- "RT_WEEK": {
- ID: "RT_WEEK",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * 1-5",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_WEEKEND": {
- ID: "RT_WEEKEND",
- Weights: utils.DynamicWeights{
- {
- Weight: 10,
- },
- },
- ActivationTimes: "* * * * 0,6",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 10 * time.Second,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 18 * time.Second,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(18, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_Custom": {
- ID: "RT_Custom",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * 1-5",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: time.Second,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Second,
- RecurrentFee: utils.NewDecimal(19, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 10 * time.Second,
- RecurrentFee: utils.NewDecimal(27, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: 15 * time.Second,
- RecurrentFee: utils.NewDecimal(4, 1),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- "RT_CHRISTMAS": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(6, 2),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- },
- },
- }
- rPrf.Sort()
- if !reflect.DeepEqual(rPrf, exp) {
- t.Errorf("Expected: %v,\n received: %v", utils.ToJSON(exp), utils.ToJSON(rPrf))
- }
-}
-
-func TestRateProfileCompile(t *testing.T) {
- rt := &RateProfile{
- Rates: map[string]*Rate{
- "randomVal1": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- },
- },
- Tenant: "cgrates.org",
- ID: "RTP1",
- }
- expectedATime, err := cron.ParseStandard("* * 24 12 *")
- if err != nil {
- t.Fatal(err)
- }
- expRt := &RateProfile{
- Rates: map[string]*Rate{
- "randomVal1": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- sched: expectedATime,
- uID: utils.ConcatenatedKey(rt.Tenant, rt.ID, "RT_CHRISTMAS"),
- },
- },
- Tenant: "cgrates.org",
- ID: "RTP1",
- }
- if err := rt.Compile(); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(rt, expRt) {
- t.Errorf("Expected %+v, received %+v", utils.ToJSON(expRt), utils.ToJSON(rt))
- }
-}
-
-func TestRateUID(t *testing.T) {
- rt := &RateProfile{
- Rates: map[string]*Rate{
- "randomVal1": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- uID: "randomID",
- },
- },
- }
- expected := "randomID"
- if newID := rt.Rates["randomVal1"].UID(); !reflect.DeepEqual(newID, expected) {
- t.Errorf("Expected %+v, received %+v", expected, newID)
- }
-}
-
-func TestRateProfileCompileError(t *testing.T) {
- rt := &RateProfile{
- Rates: map[string]*Rate{
- "randomVal": {
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * * *",
- },
- },
- }
- expectedErr := "expected exactly 5 fields, found 4: [* * * *]"
- if err := rt.Compile(); err == nil || err.Error() != expectedErr {
- t.Errorf("Expected %+v, received %+v ", expectedErr, err)
- }
-}
-
-func TestRateCompileChristmasTime(t *testing.T) {
- rt := &Rate{
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- }
- expTime, err := cron.ParseStandard("* * 24 12 *")
- if err != nil {
- t.Error(err)
- }
- expectedRt := &Rate{
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- sched: expTime,
- }
- if err := rt.Compile(); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(expectedRt, rt) {
- t.Errorf("Expected %+v, received %+v", expectedRt, rt)
- }
-}
-
-func TestRateCompileEmptyActivationTime(t *testing.T) {
- rt := &Rate{
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: utils.EmptyString,
- }
- expTime, err := cron.ParseStandard("* * * * *")
- if err != nil {
- t.Error(err)
- }
- expectedRt := &Rate{
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: utils.EmptyString,
- sched: expTime,
- }
- if err := rt.Compile(); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(rt, expectedRt) {
- t.Errorf("Expected %+v, received %+v", expectedRt, rt)
- }
-}
-
-func TestRateProfileRunTimes(t *testing.T) {
- rt := &Rate{
- ID: "RATE0",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- },
- },
- }
- rt.Compile()
-
- sTime := time.Date(2020, time.June, 28, 18, 56, 05, 0, time.UTC)
- eTime := sTime.Add(2 * time.Minute)
- eRTimes := [][]time.Time{
- {time.Date(2020, time.June, 28, 18, 56, 0, 0, time.UTC),
- time.Time{}},
- }
-
- if rTimes, err := rt.RunTimes(sTime, eTime, 10); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(eRTimes, rTimes) {
- t.Errorf("expecting: %+v, received: %+v", eRTimes, rTimes)
- }
-
- rt = &Rate{
- ID: "RT_CHRISTMAS",
- Weights: utils.DynamicWeights{
- {
- Weight: 30,
- },
- },
- ActivationTimes: "* * 24 12 *",
- }
- rt.Compile()
-
- // sTime and eTime inside the activation interval
- sTime = time.Date(2020, 12, 24, 12, 0, 0, 0, time.UTC)
- eTime = sTime.Add(time.Hour)
- eRTimes = [][]time.Time{
- {time.Date(2020, 12, 24, 12, 0, 0, 0, time.UTC), time.Date(2020, 12, 25, 0, 0, 0, 0, time.UTC)},
- }
- if rTimes, err := rt.RunTimes(sTime, eTime, 10); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(eRTimes, rTimes) {
- t.Errorf("expecting: %+v, received: %+v", eRTimes, rTimes)
- }
- // sTime smaller than activation time, eTime equals aTime
- sTime = time.Date(2020, 12, 23, 23, 0, 0, 0, time.UTC)
- eTime = sTime.Add(time.Hour)
- eRTimes = nil // cannot cover full interval
- if rTimes, err := rt.RunTimes(sTime, eTime, 10); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(eRTimes, rTimes) {
- t.Errorf("expecting: %+v, received: %+v", eRTimes, rTimes)
- }
-
- // sTime smaller than activation time but first aTime inside, eTime inside activation interval
- sTime = time.Date(2020, 12, 23, 23, 59, 59, 0, time.UTC)
- eTime = sTime.Add(time.Hour)
- eRTimes = [][]time.Time{
- {time.Date(2020, 12, 24, 0, 0, 0, 0, time.UTC), time.Date(2020, 12, 25, 0, 0, 0, 0, time.UTC)},
- }
- if rTimes, err := rt.RunTimes(sTime, eTime, 1000000); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(eRTimes, rTimes) {
- t.Errorf("expecting: %+v, received: %+v", eRTimes, rTimes)
- }
-
- // sTime way before aTime, eTime inside aInterval
- sTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
- eTime = time.Date(2021, 12, 24, 0, 1, 0, 0, time.UTC)
- eRTimes = [][]time.Time{
- {time.Date(2021, 12, 24, 0, 0, 0, 0, time.UTC), time.Date(2021, 12, 25, 0, 0, 0, 0, time.UTC)},
- }
- if rTimes, err := rt.RunTimes(sTime, eTime, 1000000); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(eRTimes, rTimes) {
- t.Errorf("expecting: %+v, received: %+v", eRTimes, rTimes)
- }
-}
-
-func TestRateProfileRunTimesMaxIterations(t *testing.T) {
- rt := &Rate{
- ID: "RATE0",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- },
- },
- ActivationTimes: "* * 24 12 *",
- }
- err := rt.Compile()
- if err != nil {
- t.Error(err)
- }
- sTime := time.Date(2020, 12, 24, 23, 30, 0, 0, time.UTC)
- eTime := time.Date(2021, 12, 25, 23, 30, 0, 0, time.UTC)
- expectedErr := "maximum iterations reached"
- if _, err := rt.RunTimes(sTime, eTime, 2); err == nil || err.Error() != expectedErr {
- t.Errorf("Expected %+v, received %+v", expectedErr, err)
- }
-}
-
-func TestRateProfileRunTimesPassingActivationTIme(t *testing.T) {
- rt := &Rate{
- ID: "RATE0",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- },
- },
- ActivationTimes: "* * 24 * *",
- }
- err := rt.Compile()
- if err != nil {
- t.Error(err)
- }
- sTime := time.Date(2020, 12, 23, 0, 0, 0, 0, time.UTC)
- eTime := time.Date(2020, 12, 27, 0, 0, 0, 0, time.UTC)
- expectedTime := [][]time.Time{
- {time.Date(2020, 12, 24, 0, 0, 0, 0, time.UTC), time.Date(2020, 12, 25, 0, 0, 0, 0, time.UTC)},
- }
- if rTimes, err := rt.RunTimes(sTime, eTime, 2); err != nil {
- t.Error(err)
- } else if !reflect.DeepEqual(expectedTime, rTimes) {
- t.Errorf("Expected %+v, received %+v", expectedTime, rTimes)
- }
-}
-
-func TestCostForIntervals(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rt0 := &Rate{
- ID: "RATE0",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: time.Duration(0),
- Unit: minDecimal,
- Increment: minDecimal,
- RecurrentFee: utils.NewDecimal(24, 1),
- },
- {
- IntervalStart: time.Duration(60 * time.Second),
- Unit: minDecimal,
- Increment: secDecimal,
- RecurrentFee: utils.NewDecimal(24, 1),
- },
- },
- }
- rt0.Compile()
- rt1 := &Rate{
- ID: "RATE1",
- IntervalRates: []*IntervalRate{
- {
-
- IntervalStart: time.Duration(0),
- Unit: minDecimal,
- Increment: secDecimal,
- RecurrentFee: utils.NewDecimal(12, 1),
- },
- {
-
- IntervalStart: time.Duration(2 * time.Minute),
- Unit: minDecimal,
- Increment: secDecimal,
- RecurrentFee: utils.NewDecimal(6, 1),
- },
- },
- }
- rt1.Compile()
- rtIvls := []*RateSInterval{
- {
- UsageStart: time.Duration(0),
- Increments: []*RateSIncrement{
- {
- UsageStart: time.Duration(0),
- Usage: time.Duration(time.Minute),
- Rate: rt0,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- {
- UsageStart: time.Duration(time.Minute),
- Usage: time.Duration(30 * time.Second),
- Rate: rt0,
- IntervalRateIndex: 1,
- CompressFactor: 30,
- },
- },
- CompressFactor: 1,
- },
- {
- UsageStart: time.Duration(90 * time.Second),
- Increments: []*RateSIncrement{
- {
- UsageStart: time.Duration(90 * time.Second),
- Usage: time.Duration(30 * time.Second),
- Rate: rt1,
- IntervalRateIndex: 0,
- CompressFactor: 30,
- },
- {
- UsageStart: time.Duration(2 * time.Minute),
- Usage: time.Duration(10 * time.Second),
- Rate: rt1,
- IntervalRateIndex: 1,
- CompressFactor: 10,
- },
- },
- CompressFactor: 1,
- },
- }
- eDcml, _ := new(decimal.Big).SetFloat64(4.3).Float64()
- if cost, _ := CostForIntervals(rtIvls).Float64(); cost != eDcml {
- t.Errorf("eDcml: %f, received: %+v", eDcml, cost)
- }
-}
-
-func TestCostForIntervalsWIthFixedFee(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rt0 := &Rate{
- ID: "RATE0",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: time.Duration(0),
- FixedFee: utils.NewDecimal(4, 1),
- RecurrentFee: utils.NewDecimal(24, 1),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Duration(60 * time.Second),
- RecurrentFee: utils.NewDecimal(24, 1),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- rt0.Compile()
- rt1 := &Rate{
- ID: "RATE1",
- IntervalRates: []*IntervalRate{
- {
-
- IntervalStart: time.Duration(0),
- FixedFee: utils.NewDecimal(2, 1),
- RecurrentFee: utils.NewDecimal(12, 1),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- {
- IntervalStart: time.Duration(2 * time.Minute),
- RecurrentFee: utils.NewDecimal(6, 1),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- rt1.Compile()
- rtIvls := []*RateSInterval{
- {
- UsageStart: time.Duration(0),
- Increments: []*RateSIncrement{
- { // cost 0,4
- UsageStart: time.Duration(0),
- Rate: rt0,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- Usage: utils.InvalidDuration,
- },
- { // cost 2,4
- UsageStart: time.Duration(0),
- Rate: rt0,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- Usage: time.Duration(time.Minute),
- },
- { // cost 1,2
- UsageStart: time.Duration(time.Minute),
- Rate: rt0,
- IntervalRateIndex: 1,
- CompressFactor: 30,
- Usage: time.Duration(30 * time.Second),
- },
- },
- CompressFactor: 1,
- },
- {
- UsageStart: time.Duration(90 * time.Second),
- Increments: []*RateSIncrement{
- { // cost 0,2
- UsageStart: time.Duration(90 * time.Second),
- Rate: rt1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- Usage: utils.InvalidDuration,
- },
- { // cost 0,6
- UsageStart: time.Duration(90 * time.Second),
- Rate: rt1,
- IntervalRateIndex: 0,
- CompressFactor: 30,
- Usage: time.Duration(30 * time.Second),
- },
- { // cost 0,1
- UsageStart: time.Duration(2 * time.Minute),
- Rate: rt1,
- IntervalRateIndex: 1,
- CompressFactor: 10,
- Usage: time.Duration(10 * time.Second),
- },
- },
- CompressFactor: 1,
- },
- }
- eDcml, _ := new(decimal.Big).SetFloat64(4.9).Float64()
- if cost, _ := CostForIntervals(rtIvls).Float64(); cost != eDcml {
- t.Errorf("eDcml: %f, received: %+v", eDcml, cost)
- }
-}
-
-func TestRateProfileCostCorrectCost(t *testing.T) {
- rPrfCost := &RateProfileCost{
- ID: "Test1",
- Cost: 0.234,
- }
- rPrfCost.CorrectCost(utils.IntPointer(2), utils.MetaRoundingUp)
- if rPrfCost.Cost != 0.24 {
- t.Errorf("Expected: %+v, received: %+v", 0.24, rPrfCost.Cost)
- }
- if !reflect.DeepEqual(rPrfCost.Altered, []string{utils.RoundingDecimals}) {
- t.Errorf("Expected: %+v, received: %+v", []string{utils.RoundingDecimals}, rPrfCost.Altered)
- }
-
-}
-
-func TestRateProfileCostCorrectCostMinCost(t *testing.T) {
- testRPC := &RateProfileCost{
- Cost: 0.5,
- MinCost: 1.5,
- }
- testRPC.CorrectCost(utils.IntPointer(2), "")
- if testRPC.Cost != 1.5 {
- t.Errorf("\nExpecting: <1.5>,\n Received: <%+v>", testRPC.Cost)
- }
-}
-
-func TestRateProfileCostCorrectCostMaxCost(t *testing.T) {
- testRPC := &RateProfileCost{
- Cost: 2.5,
- MaxCost: 1.5,
- }
- testRPC.CorrectCost(utils.IntPointer(2), "")
- if testRPC.Cost != 1.5 {
- t.Errorf("\nExpecting: <1.5>,\n Received: <%+v>", testRPC.Cost)
- }
-}
-
-func TestRateSIncrementCompressEquals(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 3),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- inCr1 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- }
- inCr2 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- }
- result := inCr1.CompressEquals(inCr2)
- if result != true {
- t.Errorf("\nExpecting: ,\n Received: <%+v>", result)
- }
-}
-
-func TestRateSIncrementCompressEqualsCase1(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- },
- uID: "ID",
- }
- rate2 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- },
- uID: "ID2",
- }
- inCr1 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- }
- inCr2 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate2,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- }
- result := inCr1.CompressEquals(inCr2)
- if result != false {
- t.Errorf("\nExpecting: ,\n Received: <%+v>", result)
- }
-}
-func TestRateSIncrementCompressEqualsCase2(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- },
- }
- inCr1 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- }
- inCr2 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 1,
- CompressFactor: 1,
- }
- result := inCr1.CompressEquals(inCr2)
- if result != false {
- t.Errorf("\nExpecting: ,\n Received: <%+v>", result)
- }
-}
-
-func TestRateSIncrementCompressEqualsCase3(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- },
- }
- inCr1 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Second,
- Rate: rate1,
- IntervalRateIndex: 1,
- CompressFactor: 1,
- }
- inCr2 := &RateSIncrement{
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 1,
- CompressFactor: 1,
- }
- result := inCr1.CompressEquals(inCr2)
- if result != false {
- t.Errorf("\nExpecting: ,\n Received: <%+v>", result)
- }
-}
-
-func TestRateSIntervalCompressEqualsCase1(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 3),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- rateSintrv1 := &RateSInterval{
- UsageStart: 0,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- {
- UsageStart: time.Minute,
- Usage: time.Minute + 10*time.Second,
- Rate: rate1,
- IntervalRateIndex: 1,
- CompressFactor: 70,
- },
- },
- CompressFactor: 1,
- }
-
- rateSintrv2 := &RateSInterval{
- UsageStart: 0,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- },
- CompressFactor: 1,
- }
- result := rateSintrv1.CompressEquals(rateSintrv2)
- if result != false {
- t.Errorf("\nExpecting ,\nReceived <%+v>", result)
- }
-}
-
-func TestRateSIntervalCompressEqualsCase2(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
- secDecimal, err := utils.NewDecimalFromUsage("1s")
- if err != nil {
- t.Error(err)
- }
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 3),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- rate2 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- {
- IntervalStart: time.Minute,
- RecurrentFee: utils.NewDecimal(6, 3),
- Unit: minDecimal,
- Increment: secDecimal,
- },
- },
- }
- rateSintrv1 := &RateSInterval{
- UsageStart: 0,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- {
- UsageStart: 0,
- Usage: time.Second,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- },
- CompressFactor: 1,
- }
-
- rateSintrv2 := &RateSInterval{
- UsageStart: 1,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate2,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate2,
- IntervalRateIndex: 2,
- CompressFactor: 1,
- },
- },
- CompressFactor: 0,
- }
- result := rateSintrv1.CompressEquals(rateSintrv2)
- if result != false {
- t.Errorf("\nExpecting ,\nReceived <%+v>", result)
- }
-}
-
-func TestRateSIntervalCompressEqualsCase3(t *testing.T) {
- minDecimal, err := utils.NewDecimalFromUsage("1m")
- if err != nil {
- t.Error(err)
- }
-
- rate1 := &Rate{
- ID: "RATE1",
- Weights: utils.DynamicWeights{
- {
- Weight: 0,
- },
- },
- ActivationTimes: "* * * * *",
- IntervalRates: []*IntervalRate{
- {
- IntervalStart: 0,
- RecurrentFee: utils.NewDecimal(12, 2),
- Unit: minDecimal,
- Increment: minDecimal,
- },
- },
- }
- rateSintrv1 := &RateSInterval{
- UsageStart: 0,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- },
- CompressFactor: 1,
- }
-
- rateSintrv2 := &RateSInterval{
- UsageStart: 0,
- Increments: []*RateSIncrement{
- {
- UsageStart: 0,
- Usage: time.Minute,
- Rate: rate1,
- IntervalRateIndex: 0,
- CompressFactor: 1,
- },
- },
- CompressFactor: 1,
- }
- result := rateSintrv1.CompressEquals(rateSintrv2)
- if result != true {
- t.Errorf("\nExpecting ,\nReceived <%+v>", result)
- }
-}
diff --git a/engine/storage_interface.go b/engine/storage_interface.go
index fd4437720..7f9f15e1b 100644
--- a/engine/storage_interface.go
+++ b/engine/storage_interface.go
@@ -129,8 +129,8 @@ type DataDB interface {
GetDispatcherHostDrv(string, string) (*DispatcherHost, error)
SetDispatcherHostDrv(*DispatcherHost) error
RemoveDispatcherHostDrv(string, string) error
- GetRateProfileDrv(string, string) (*RateProfile, error)
- SetRateProfileDrv(*RateProfile) error
+ GetRateProfileDrv(string, string) (*utils.RateProfile, error)
+ SetRateProfileDrv(*utils.RateProfile) error
RemoveRateProfileDrv(string, string) error
GetActionProfileDrv(string, string) (*ActionProfile, error)
SetActionProfileDrv(*ActionProfile) error
diff --git a/engine/storage_internal_datadb.go b/engine/storage_internal_datadb.go
index c24d0ee7f..428899c37 100644
--- a/engine/storage_internal_datadb.go
+++ b/engine/storage_internal_datadb.go
@@ -819,15 +819,15 @@ func (iDB *InternalDB) RemoveDispatcherHostDrv(tenant, id string) (err error) {
return
}
-func (iDB *InternalDB) GetRateProfileDrv(tenant, id string) (rpp *RateProfile, err error) {
+func (iDB *InternalDB) GetRateProfileDrv(tenant, id string) (rpp *utils.RateProfile, err error) {
x, ok := Cache.Get(utils.CacheRateProfiles, utils.ConcatenatedKey(tenant, id))
if !ok || x == nil {
return nil, utils.ErrNotFound
}
- return x.(*RateProfile), nil
+ return x.(*utils.RateProfile), nil
}
-func (iDB *InternalDB) SetRateProfileDrv(rpp *RateProfile) (err error) {
+func (iDB *InternalDB) SetRateProfileDrv(rpp *utils.RateProfile) (err error) {
if err = rpp.Compile(); err != nil {
return
}
diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go
index 1d166f511..384c4a786 100644
--- a/engine/storage_mongo_datadb.go
+++ b/engine/storage_mongo_datadb.go
@@ -2015,8 +2015,8 @@ func (ms *MongoStorage) RemoveLoadIDsDrv() (err error) {
})
}
-func (ms *MongoStorage) GetRateProfileDrv(tenant, id string) (rpp *RateProfile, err error) {
- rpp = new(RateProfile)
+func (ms *MongoStorage) GetRateProfileDrv(tenant, id string) (rpp *utils.RateProfile, err error) {
+ rpp = new(utils.RateProfile)
err = ms.query(func(sctx mongo.SessionContext) (err error) {
cur := ms.getCol(ColRpp).FindOne(sctx, bson.M{"tenant": tenant, "id": id})
if err := cur.Decode(rpp); err != nil {
@@ -2031,7 +2031,7 @@ func (ms *MongoStorage) GetRateProfileDrv(tenant, id string) (rpp *RateProfile,
return
}
-func (ms *MongoStorage) SetRateProfileDrv(rpp *RateProfile) (err error) {
+func (ms *MongoStorage) SetRateProfileDrv(rpp *utils.RateProfile) (err error) {
return ms.query(func(sctx mongo.SessionContext) (err error) {
_, err = ms.getCol(ColRpp).UpdateOne(sctx, bson.M{"tenant": rpp.Tenant, "id": rpp.ID},
bson.M{"$set": rpp},
diff --git a/engine/storage_redis.go b/engine/storage_redis.go
index 09c1631a9..0b44935c7 100644
--- a/engine/storage_redis.go
+++ b/engine/storage_redis.go
@@ -1245,7 +1245,7 @@ func (rs *RedisStorage) RemoveLoadIDsDrv() (err error) {
return rs.Cmd(nil, redis_DEL, utils.LoadIDs)
}
-func (rs *RedisStorage) GetRateProfileDrv(tenant, id string) (rpp *RateProfile, err error) {
+func (rs *RedisStorage) GetRateProfileDrv(tenant, id string) (rpp *utils.RateProfile, err error) {
var values []byte
if err = rs.Cmd(&values, redis_GET, utils.RateProfilePrefix+utils.ConcatenatedKey(tenant, id)); err != nil {
return
@@ -1257,7 +1257,7 @@ func (rs *RedisStorage) GetRateProfileDrv(tenant, id string) (rpp *RateProfile,
return
}
-func (rs *RedisStorage) SetRateProfileDrv(rpp *RateProfile) (err error) {
+func (rs *RedisStorage) SetRateProfileDrv(rpp *utils.RateProfile) (err error) {
var result []byte
if result, err = rs.ms.Marshal(rpp); err != nil {
return
diff --git a/engine/tpreader.go b/engine/tpreader.go
index 3e3c74ed4..bd8219824 100644
--- a/engine/tpreader.go
+++ b/engine/tpreader.go
@@ -1867,7 +1867,7 @@ func (tpr *TpReader) WriteToDatabase(verbose, disableReverse bool) (err error) {
log.Print("RateProfiles:")
}
for _, tpTH := range tpr.rateProfiles {
- var th *RateProfile
+ var th *utils.RateProfile
if th, err = APItoRateProfile(tpTH, tpr.timezone); err != nil {
return
}
diff --git a/rates/librates.go b/rates/librates.go
index 672e54f71..6344d08b3 100644
--- a/rates/librates.go
+++ b/rates/librates.go
@@ -25,12 +25,11 @@ import (
"github.com/ericlagergren/decimal"
- "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type rpWithWeight struct {
- *engine.RateProfile
+ *utils.RateProfile
weight float64
}
@@ -77,7 +76,7 @@ func (rs *ratesWithWinner) has(rtID string) (has bool) {
// rateWithTimes activates a rate on an interval
type rateWithTimes struct {
uId string
- rt *engine.Rate
+ rt *utils.Rate
aTime,
iTime time.Time
weight float64
@@ -92,16 +91,20 @@ func (rWt *rateWithTimes) id() string {
}
type orderedRate struct {
- time.Duration
- *engine.Rate
+ *decimal.Big
+ *utils.Rate
}
// orderRatesOnIntervals will order the rates based on ActivationInterval and intervalStart of each Rate
// there can be only one winning Rate for each interval, prioritized by the Weight
-func orderRatesOnIntervals(aRts []*engine.Rate, wghts []float64, sTime time.Time, usage time.Duration,
+func orderRatesOnIntervals(aRts []*utils.Rate, wghts []float64, sTime time.Time, usage *decimal.Big,
isDuration bool, verbosity int) (ordRts []*orderedRate, err error) {
- endTime := sTime.Add(usage)
+ usageInt, ok := usage.Int64()
+ if !ok {
+ return nil, fmt.Errorf("<%s> cannot convert <%+v> increment to Int64", utils.RateS, usage)
+ }
+ endTime := sTime.Add(time.Duration(usageInt))
// index the received rates based on unique times they run
rtIdx := make(map[time.Time]*ratesWithWinner) // map[ActivationTimes]*ratesWithWinner
@@ -165,7 +168,7 @@ func orderRatesOnIntervals(aRts []*engine.Rate, wghts []float64, sTime time.Time
// compute the list of returned rates together with their index interval
if isDuration {
// add all the possible ActivationTimes from cron expressions
- var usageIndx time.Duration // the difference between setup and activation time of the rate
+ var usageIndx *decimal.Big // the difference between setup and activation time of the rate
for _, aTime := range sortedATimes {
if !endTime.After(aTime) {
break // we are not interested about further rates
@@ -175,87 +178,88 @@ func orderRatesOnIntervals(aRts []*engine.Rate, wghts []float64, sTime time.Time
continue
}
if sTime.Before(aTime) {
- usageIndx = aTime.Sub(sTime)
+ usageIndx = decimal.New(int64(aTime.Sub(sTime)), 0)
}
if len(ordRts) == 0 || wnr.rt.ID != ordRts[len(ordRts)-1].Rate.ID { // only add the winner if not already active
ordRts = append(ordRts, &orderedRate{usageIndx, rtIdx[aTime].winner().rt})
}
}
} else { // only first rate is considered for units
- ordRts = []*orderedRate{{time.Duration(0), rtIdx[sortedATimes[0]].winner().rt}}
+ ordRts = []*orderedRate{{decimal.New(0, 0), rtIdx[sortedATimes[0]].winner().rt}}
}
return
}
// computeRateSIntervals will give out the cost projection for the given orderedRates and usage
-func computeRateSIntervals(rts []*orderedRate, intervalStart, usage time.Duration) (rtIvls []*engine.RateSInterval, err error) {
+func computeRateSIntervals(rts []*orderedRate, intervalStart, usage *decimal.Big) (rtIvls []*utils.RateSInterval, err error) {
totalUsage := usage
- if intervalStart != 0 {
- totalUsage = usage + intervalStart
+ if intervalStart.Cmp(decimal.New(0, 0)) != 0 {
+ totalUsage = utils.SumBig(usage, intervalStart)
}
for i, rt := range rts {
isLastRt := i == len(rts)-1
- var rtUsageEIdx time.Duration
+ var rtUsageEIdx *decimal.Big
if !isLastRt {
- rtUsageEIdx = rts[i+1].Duration
+ rtUsageEIdx = rts[i+1].Big
} else {
rtUsageEIdx = totalUsage
}
- var rIcmts []*engine.RateSIncrement
+ var rIcmts []*utils.RateSIncrement
iRtUsageSIdx := intervalStart
iRtUsageEIdx := rtUsageEIdx
for j, iRt := range rt.IntervalRates {
//fmt.Printf("ivalStart: %v, ivalEnd: %v, rtID: %s, increment idx: %d, iRtUsageSIdx: %+v, iRtUsageEIdx: %+v, iRt: %s\n",
// intervalStart, rtUsageEIdx, rt.ID, j, iRtUsageSIdx, iRtUsageEIdx, utils.ToIJSON(iRt))
- if iRtUsageSIdx >= rtUsageEIdx { // charged enough for interval
+ if iRtUsageSIdx.Cmp(rtUsageEIdx) >= 0 { // charged enough for interval
break
}
// make sure we bill from start
- if iRt.IntervalStart > iRtUsageSIdx && j == 0 {
+ if iRt.IntervalStart.Cmp(iRtUsageSIdx) > 0 && j == 0 {
return nil, fmt.Errorf("intervalStart for rate: <%s> higher than usage: %v",
rt.UID(), iRtUsageSIdx)
}
isLastIRt := j == len(rt.IntervalRates)-1
- if !isLastIRt && rt.IntervalRates[j+1].IntervalStart <= iRtUsageSIdx {
+ if !isLastIRt && rt.IntervalRates[j+1].IntervalStart.Cmp(iRtUsageSIdx) <= 0 {
continue // the next interval changes the rating
}
if isLastIRt {
iRtUsageEIdx = rtUsageEIdx
- } else if rt.IntervalRates[j+1].IntervalStart > rtUsageEIdx {
+ } else if rt.IntervalRates[j+1].IntervalStart.Cmp(rtUsageEIdx) > 0 {
iRtUsageEIdx = rtUsageEIdx
} else {
- iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart
+ iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart.Big
}
if iRt.Increment.Cmp(decimal.New(0, 0)) == 0 {
return nil, fmt.Errorf("zero increment to be charged within rate: <%s>", rt.UID())
}
if rt.IntervalRates[j].FixedFee != nil && rt.IntervalRates[j].FixedFee.Cmp(decimal.New(0, 0)) != 0 { // Add FixedFee
- rIcmts = append(rIcmts, &engine.RateSIncrement{
- UsageStart: iRtUsageSIdx,
+ rIcmts = append(rIcmts, &utils.RateSIncrement{
+ IncrementStart: &utils.Decimal{iRtUsageSIdx},
Rate: rt.Rate,
IntervalRateIndex: j,
CompressFactor: 1,
- Usage: utils.InvalidDuration,
+ Usage: utils.NewDecimal(utils.InvalidUsage, 0),
})
}
- iRtUsage := iRtUsageEIdx - iRtUsageSIdx
- intUsage := int64(iRtUsage)
- intIncrm, ok := iRt.Increment.Int64()
+ iRtUsage := utils.SubstractBig(iRtUsageEIdx, iRtUsageSIdx)
+ intUsage := iRtUsage
+ intIncrm := iRt.Increment.Big
+ cmpFactor, rem := utils.DivideBigWithReminder(intUsage, intIncrm)
+ if rem.Cmp(decimal.New(0, 0)) != 0 {
+ cmpFactor = utils.SumBig(cmpFactor, decimal.New(1, 0)) // int division has used math.Floor, need Ceil
+ }
+ cmpFactorInt, ok := cmpFactor.Int64()
if !ok {
- return nil, fmt.Errorf("<%s> cannot convert <%+v> increment to Int64", utils.RateS, iRt.Increment)
+ return nil, fmt.Errorf("<%s> cannot convert <%+v> increment to Int64", utils.RateS, cmpFactor)
}
- cmpFactor := intUsage / intIncrm
- if intUsage%intIncrm != 0 {
- cmpFactor++ // int division has used math.Floor, need Ceil
- }
- rIcmts = append(rIcmts, &engine.RateSIncrement{
- UsageStart: iRtUsageSIdx,
+ rIcmts = append(rIcmts, &utils.RateSIncrement{
+ IncrementStart: &utils.Decimal{iRtUsageSIdx},
Rate: rt.Rate,
IntervalRateIndex: j,
- CompressFactor: cmpFactor,
- Usage: iRtUsage,
+ CompressFactor: cmpFactorInt,
+ Usage: &utils.Decimal{iRtUsage},
})
- iRtUsageSIdx += iRtUsage
+ iRtUsageSIdx = utils.SumBig(iRtUsageSIdx, iRtUsage)
}
usageStart := intervalStart
@@ -263,12 +267,12 @@ func computeRateSIntervals(rts []*orderedRate, intervalStart, usage time.Duratio
if len(rIcmts) == 0 { // no match found
continue
}
- rtIvls = append(rtIvls, &engine.RateSInterval{
- UsageStart: usageStart,
+ rtIvls = append(rtIvls, &utils.RateSInterval{
+ IntervalStart: &utils.Decimal{usageStart},
Increments: rIcmts,
CompressFactor: 1,
})
- if iRtUsageSIdx >= totalUsage { // charged enough for the usage
+ if iRtUsageSIdx.Cmp(totalUsage) >= 0 { // charged enough for the usage
break
}
diff --git a/rates/librates_test.go b/rates/librates_test.go
index 073cfb2b1..18b26e28d 100644
--- a/rates/librates_test.go
+++ b/rates/librates_test.go
@@ -29,14 +29,14 @@ import (
)
func TestOrderRatesOnIntervals(t *testing.T) {
- rt0 := &engine.Rate{
+ rt0 := &utils.Rate{
ID: "RATE0",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
- IntervalRates: []*engine.IntervalRate{
+ IntervalRates: []*utils.IntervalRate{
{
IntervalStart: 0,
},
diff --git a/rates/rates.go b/rates/rates.go
index dbc89ee91..b4137d1b1 100644
--- a/rates/rates.go
+++ b/rates/rates.go
@@ -22,6 +22,8 @@ import (
"fmt"
"time"
+ "github.com/ericlagergren/decimal"
+
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
@@ -69,7 +71,7 @@ func (rS *RateS) Call(serviceMethod string, args interface{}, reply interface{})
}
// matchingRateProfileForEvent returns the matched RateProfile for the given event
-func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *utils.ArgsCostForEvent) (rtPfl *engine.RateProfile, err error) {
+func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *utils.ArgsCostForEvent) (rtPfl *utils.RateProfile, err error) {
evNm := utils.MapStorage{
utils.MetaReq: args.CGREvent.Event,
utils.MetaOpts: args.Opts,
@@ -93,7 +95,7 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *
}
var rpWw *rpWithWeight
for _, rPfID := range rPfIDs {
- var rPf *engine.RateProfile
+ var rPf *utils.RateProfile
if rPf, err = rS.dm.GetRateProfile(tnt, rPfID,
true, true, utils.NonTransactional); err != nil {
if err == utils.ErrNotFound {
@@ -129,7 +131,7 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *
}
// rateProfileCostForEvent computes the rateProfileCost for an event based on a preselected rate profile
-func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.ArgsCostForEvent, verbosity int) (rpCost *engine.RateProfileCost, err error) {
+func (rS *RateS) rateProfileCostForEvent(rtPfl *utils.RateProfile, args *utils.ArgsCostForEvent, verbosity int) (rpCost *utils.RateProfileCost, err error) {
evNm := utils.MapStorage{
utils.MetaReq: args.CGREvent.Event,
utils.MetaOpts: args.Opts,
@@ -148,7 +150,7 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
); err != nil {
return
}
- aRates := make([]*engine.Rate, 0, len(rtIDs))
+ aRates := make([]*utils.Rate, 0, len(rtIDs))
for rtID := range rtIDs {
rt := rtPfl.Rates[rtID] // pick the rate directly from map based on matched ID
var pass bool
@@ -171,7 +173,7 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
if sTime, err = args.StartTime(rS.cfg.GeneralCfg().DefaultTimezone); err != nil {
return
}
- var usage time.Duration
+ var usage *decimal.Big
if usage, err = args.Usage(); err != nil {
return
}
@@ -179,7 +181,7 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
if ordRts, err = orderRatesOnIntervals(aRates, wghts, sTime, usage, true, verbosity); err != nil {
return
}
- rpCost = &engine.RateProfileCost{
+ rpCost = &utils.RateProfileCost{
ID: rtPfl.ID,
}
var ok bool
@@ -194,30 +196,30 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
}
}
- if rpCost.RateSIntervals, err = computeRateSIntervals(ordRts, 0, usage); err != nil {
+ if rpCost.RateSIntervals, err = computeRateSIntervals(ordRts, decimal.New(0, 0), usage); err != nil {
return nil, err
}
// in case we have error it is returned in the function from above
// this came to light in coverage tests
- rpCost.Cost, _ = engine.CostForIntervals(rpCost.RateSIntervals).Float64()
+ rpCost.Cost, _ = utils.CostForIntervals(rpCost.RateSIntervals).Float64()
return
}
// V1CostForEvent will be called to calculate the cost for an event
-func (rS *RateS) V1CostForEvent(args *utils.ArgsCostForEvent, rpCost *engine.RateProfileCost) (err error) {
+func (rS *RateS) V1CostForEvent(args *utils.ArgsCostForEvent, rpCost *utils.RateProfileCost) (err error) {
rPfIDs := make([]string, len(args.RateProfileIDs))
for i, rpID := range args.RateProfileIDs {
rPfIDs[i] = rpID
}
- var rtPrl *engine.RateProfile
+ var rtPrl *utils.RateProfile
if rtPrl, err = rS.matchingRateProfileForEvent(args.Tenant, rPfIDs, args); err != nil {
if err != utils.ErrNotFound {
err = utils.NewErrServerError(err)
}
return
}
- var rcvCost *engine.RateProfileCost
+ var rcvCost *utils.RateProfileCost
if rcvCost, err = rS.rateProfileCostForEvent(rtPrl, args, rS.cfg.RateSCfg().Verbosity); err != nil {
if err != utils.ErrNotFound {
err = utils.NewErrServerError(err)
diff --git a/utils/apitpdata.go b/utils/apitpdata.go
index 955d82c04..0316aa471 100755
--- a/utils/apitpdata.go
+++ b/utils/apitpdata.go
@@ -24,6 +24,8 @@ import (
"strconv"
"strings"
"time"
+
+ "github.com/ericlagergren/decimal"
)
// Used to extract ids from stordb
@@ -1511,20 +1513,16 @@ func (args *ArgsCostForEvent) StartTime(tmz string) (sTime time.Time, err error)
}
// usage returns the event time used to check active rate profiles
-func (args *ArgsCostForEvent) Usage() (usage time.Duration, err error) {
+func (args *ArgsCostForEvent) Usage() (usage *decimal.Big, err error) {
// first search for the usage in opts
if uIface, has := args.Opts[OptsRatesUsage]; has {
- return IfaceAsDuration(uIface)
+ return IfaceAsBig(uIface)
}
// if the usage is not present in opts search in event
- if usage, err = args.FieldAsDuration(Usage); err != nil {
- if err != ErrNotFound {
- return
- }
- // if the usage is not found in the event populate with default value and overwrite the NOT_FOUND error with nil
- usage, err = time.Duration(time.Minute), nil
+ if uIface, has := args.Event[Usage]; has {
+ return IfaceAsBig(uIface)
}
- return
+ return nil, ErrNotFound
}
type TPActionProfile struct {
diff --git a/utils/consts.go b/utils/consts.go
index 4745bd89a..9f0c4b81e 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -918,6 +918,7 @@ const (
HashtagSep = "#"
MetaRounding = "*rounding"
StatsNA = -1.0
+ InvalidUsage = -1
RateProfileMatched = "RateProfileMatched"
InvalidDuration = time.Duration(-1)
ActionS = "ActionS"
diff --git a/utils/decimal.go b/utils/decimal.go
index 7c1a9795d..71b491aef 100644
--- a/utils/decimal.go
+++ b/utils/decimal.go
@@ -32,6 +32,10 @@ func DivideBig(x, y *decimal.Big) *decimal.Big {
return new(decimal.Big).Quo(x, y)
}
+func DivideBigWithReminder(x, y *decimal.Big) (q *decimal.Big, r *decimal.Big) {
+ return new(decimal.Big).QuoRem(x, y, new(decimal.Big))
+}
+
func MultiplyBig(x, y *decimal.Big) *decimal.Big {
return new(decimal.Big).Mul(x, y)
}
diff --git a/utils/reflect.go b/utils/reflect.go
index 805723f3e..577ff76ae 100644
--- a/utils/reflect.go
+++ b/utils/reflect.go
@@ -24,7 +24,10 @@ import (
"net"
"reflect"
"strconv"
+ "strings"
"time"
+
+ "github.com/ericlagergren/decimal"
)
// StringToInterface will parse string into supported types
@@ -125,6 +128,61 @@ func IfaceAsTime(itm interface{}, timezone string) (t time.Time, err error) {
return
}
+func IfaceAsBig(itm interface{}) (b *decimal.Big, err error) {
+ switch it := itm.(type) {
+ case time.Duration:
+ return decimal.New(int64(it), 0), nil
+ case int: // check every int type
+ return decimal.New(int64(it), 0), nil
+ case int8:
+ return decimal.New(int64(it), 0), nil
+ case int16:
+ return decimal.New(int64(it), 0), nil
+ case int32:
+ return decimal.New(int64(it), 0), nil
+ case int64:
+ return decimal.New(it, 0), nil
+ case uint:
+ return new(decimal.Big).SetUint64(uint64(it)), nil
+ case uint8:
+ return new(decimal.Big).SetUint64(uint64(it)), nil
+ case uint16:
+ return new(decimal.Big).SetUint64(uint64(it)), nil
+ case uint32:
+ return new(decimal.Big).SetUint64(uint64(it)), nil
+ case uint64:
+ return new(decimal.Big).SetUint64(it), nil
+ case float32: // automatically hitting here also ints
+ return new(decimal.Big).SetFloat64(float64(it)), nil
+ case float64: // automatically hitting here also ints
+ return new(decimal.Big).SetFloat64(it), nil
+ case string:
+ if strings.HasSuffix(it, NsSuffix) ||
+ strings.HasSuffix(it, UsSuffix) ||
+ strings.HasSuffix(it, µSuffix) ||
+ strings.HasSuffix(it, MsSuffix) ||
+ strings.HasSuffix(it, SSuffix) ||
+ strings.HasSuffix(it, MSuffix) ||
+ strings.HasSuffix(it, HSuffix) {
+ var tm time.Duration
+ if tm, err = time.ParseDuration(it); err != nil {
+ return
+ }
+ return decimal.New(int64(tm), 0), nil
+ }
+ z, ok := new(decimal.Big).SetString(it)
+ // verify ok and check if the value was converted successfuly
+ // and the big is a valid number
+ if !ok || z.IsNaN(0) {
+ return nil, fmt.Errorf("can't convert <%+v> to decimal", it)
+ }
+ return z, nil
+ default:
+ err = fmt.Errorf("cannot convert field: %+v to time.Duration", it)
+ }
+ return
+}
+
func IfaceAsDuration(itm interface{}) (d time.Duration, err error) {
switch it := itm.(type) {
case time.Duration: