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: