diff --git a/engine/rateprofile.go b/engine/rateprofile.go index 5d2f42fa6..1c12ccb3c 100644 --- a/engine/rateprofile.go +++ b/engine/rateprofile.go @@ -173,6 +173,12 @@ type RateSIncrement struct { cost *utils.Decimal // unexported total increment cost } +type RateProfileCost struct { + ID string // RateProfileID + Cost float64 + RateSIntervals []*RateSInterval +} + // Sort will sort the IntervalRates from each Rate based on IntervalStart func (rpp *RateProfile) Sort() { for _, rate := range rpp.Rates { diff --git a/rates/librates.go b/rates/librates.go index c76996e40..bc7478c3a 100644 --- a/rates/librates.go +++ b/rates/librates.go @@ -205,9 +205,10 @@ func computeRateSIntervals(rts []*orderedRate, intervalStart, usage time.Duratio } isLastIRt := j == len(rt.IntervalRates)-1 if iRt.IntervalStart > iRtUsageSIdx { - break - } else if !isLastIRt && rt.IntervalRates[j+1].IntervalStart <= iRtUsageSIdx { - continue // the rates should be already ordered, break here + break // we are past the start + } + if !isLastIRt && rt.IntervalRates[j+1].IntervalStart <= iRtUsageSIdx { + continue // the next interval changes the rating } if !isLastIRt { iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart diff --git a/rates/rates.go b/rates/rates.go index ce3c9b790..f2d3f5576 100644 --- a/rates/rates.go +++ b/rates/rates.go @@ -71,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, args *ArgsCostForEvent, rPfIDs []string) (rtPfl *engine.RateProfile, err error) { +func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *ArgsCostForEvent) (rtPfl *engine.RateProfile, err error) { evNm := utils.MapStorage{ utils.MetaReq: args.CGREvent.Event, utils.MetaOpts: args.Opts, @@ -93,10 +93,6 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, args *ArgsCostForEvent, } rPfIDs = rPfIDMp.AsSlice() } - var sTime time.Time - if sTime, err = args.StartTime(rS.cfg.GeneralCfg().DefaultTimezone); err != nil { - return - } for _, rPfID := range rPfIDs { var rPf *engine.RateProfile if rPf, err = rS.dm.GetRateProfile(tnt, rPfID, @@ -107,8 +103,8 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, args *ArgsCostForEvent, } return } - if rPf.ActivationInterval != nil && - !rPf.ActivationInterval.IsActiveAtTime(sTime) { // not active + if rPf.ActivationInterval != nil && args.CGREvent.Time != nil && + !rPf.ActivationInterval.IsActiveAtTime(*args.CGREvent.Time) { // not active continue } var pass bool @@ -128,15 +124,8 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, args *ArgsCostForEvent, return } -type RateProfileCost struct { - Tenant string - ID string - Cost float64 - RateSIntervals []*engine.RateSInterval -} - // costForEvent computes the cost for an event based on a preselected rating profile -func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *ArgsCostForEvent) (rpCost *RateProfileCost, err error) { +func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *ArgsCostForEvent) (rpCost *engine.RateProfileCost, err error) { var rtIDs utils.StringSet if rtIDs, err = engine.MatchingItemIDsForEvent( args.CGREventWithOpts.CGREvent.Event, @@ -175,7 +164,7 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *ArgsCo if ordRts, err = orderRatesOnIntervals(aRates, sTime, usage, true, 1000000); err != nil { return } - rpCost = &RateProfileCost{Tenant: rtPfl.Tenant, ID: rtPfl.ID} + rpCost = &engine.RateProfileCost{ID: rtPfl.ID} if rpCost.RateSIntervals, err = computeRateSIntervals(ordRts, 0, usage); err != nil { return nil, err } @@ -194,22 +183,6 @@ func (args *ArgsCostForEvent) StartTime(tmz string) (sTime time.Time, err error) if tIface, has := args.Opts[utils.OptsRatesStartTime]; has { return utils.IfaceAsTime(tIface, tmz) } - if sTime, err = args.CGREvent.FieldAsTime(utils.AnswerTime, tmz); err != nil { - if err != utils.ErrNotFound { - return - } - // not found, try SetupTime - if sTime, err = args.CGREvent.FieldAsTime(utils.SetupTime, tmz); err != nil && - err != utils.ErrNotFound { - return - } - } - if err == nil { - return - } - if args.CGREvent.Time != nil { - return *args.CGREvent.Time, nil - } return time.Now(), nil } @@ -218,15 +191,23 @@ func (args *ArgsCostForEvent) Usage() (usage time.Duration, err error) { if uIface, has := args.Opts[utils.OptsRatesUsage]; has { return utils.IfaceAsDuration(uIface) } - if usage, err = args.CGREvent.FieldAsDuration(utils.Usage); err != nil { - if err != utils.ErrNotFound { - return - } - } return time.Duration(time.Minute), nil } // V1CostForEvent will be called to calculate the cost for an event -func (rS *RateS) V1CostForEvent(args *ArgsCostForEvent, cC *utils.ChargedCost) (err error) { +func (rS *RateS) V1CostForEvent(args *ArgsCostForEvent, rpCost *engine.RateProfileCost) (err error) { + rPfIDs := make([]string, len(args.RateProfileIDs)) + for i, rpID := range args.RateProfileIDs { + rPfIDs[i] = rpID + } + var rtPrl *engine.RateProfile + if rtPrl, err = rS.matchingRateProfileForEvent(args.CGREventWithOpts.Tenant, rPfIDs, args); err != nil { + return utils.NewErrServerError(err) + } + if rcvCost, errCost := rS.rateProfileCostForEvent(rtPrl, args); errCost != nil { + return utils.NewErrServerError(errCost) + } else { + *rpCost = *rcvCost + } return } diff --git a/rates/rates_test.go b/rates/rates_test.go index fb5dcfa90..c62d68b6a 100644 --- a/rates/rates_test.go +++ b/rates/rates_test.go @@ -61,137 +61,6 @@ func TestNewRateS(t *testing.T) { } } -func TestStartTime(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Time: utils.TimePointer(time.Date(2020, 12, 24, 0, 0, 0, 0, time.UTC)), - Event: map[string]interface{}{ - utils.ToR: utils.SMS, - }, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - expectedTime := time.Date(2020, 12, 24, 0, 0, 0, 0, time.UTC) - if sTime, err := argsCost.StartTime(utils.EmptyString); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(expectedTime, sTime) { - t.Errorf("Expected %+v, received %+v", expectedTime, sTime) - } -} - -func TestStartTimeError(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Time: utils.TimePointer(time.Now()), - Event: map[string]interface{}{ - utils.ToR: utils.SMS, - }, - }, - Opts: map[string]interface{}{ - utils.OptsRatesStartTime: "invalidStartTime", - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - expectedErr := "Unsupported time format" - if _, err := argsCost.StartTime(utils.EmptyString); err == nil || err.Error() != expectedErr { - t.Errorf("Expected %+v, received %+v", expectedErr, err) - } -} - -func TestStartTimeFieldAsTimeErrorAnswerTime(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Time: utils.TimePointer(time.Now()), - Event: map[string]interface{}{ - utils.AnswerTime: "0s", - }, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - expectedErr := "Unsupported time format" - if _, err := argsCost.StartTime(utils.EmptyString); err == nil || err.Error() != expectedErr { - t.Errorf("Expected %+v, received %+v", expectedErr, err) - } -} - -func TestStartTimeFieldAsTimeErrorSetupTime(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Time: utils.TimePointer(time.Now()), - Event: map[string]interface{}{ - utils.SetupTime: "1h0m0s", - }, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - expectedErr := "Unsupported time format" - if _, err := argsCost.StartTime(utils.EmptyString); err == nil || err.Error() != expectedErr { - t.Errorf("Expected %+v, received %+v", expectedErr, err) - } -} - -func TestStartTimeFieldAsTimeNilTime(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Event: map[string]interface{}{ - utils.AnswerTime: time.Time{}, - }, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - if _, err := argsCost.StartTime(utils.EmptyString); err != nil { - t.Error(err) - } -} - -func TestStartTimeFieldAsTimeNow(t *testing.T) { - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Event: map[string]interface{}{}, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - if _, err := argsCost.StartTime(utils.EmptyString); err != nil { - t.Error(err) - } -} - func TestCallRates(t *testing.T) { newRates := &RateS{} var reply *string @@ -201,25 +70,6 @@ func TestCallRates(t *testing.T) { } } -func TestV1CostForEvent(t *testing.T) { - rts := &RateS{} - cgrEvent := &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "randomID", - Event: map[string]interface{}{}, - }, - } - rateProfileIDs := []string{"randomIDs"} - argsCost := &ArgsCostForEvent{ - rateProfileIDs, - cgrEvent, - } - if err := rts.V1CostForEvent(argsCost, nil); err != nil { - t.Error(err) - } -} - func TestMatchingRateProfileEvent(t *testing.T) { defaultCfg, err := config.NewDefaultCGRConfig() if err != nil { @@ -247,7 +97,7 @@ func TestMatchingRateProfileEvent(t *testing.T) { if err != nil { t.Error(err) } - if rtPRf, err := rate.matchingRateProfileForEvent("cgrates.org", + if rtPRf, err := rate.matchingRateProfileForEvent("cgrates.org", []string{}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -260,14 +110,13 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{}); err != nil { + }); err != nil { t.Error(err) } else if !reflect.DeepEqual(rtPRf, rpp) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(rpp), utils.ToJSON(rtPRf)) } - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -280,11 +129,10 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{}); err != utils.ErrNotFound { + }); err != utils.ErrNotFound { t.Error(err) } - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -297,11 +145,10 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{}); err != utils.ErrNotFound { + }); err != utils.ErrNotFound { t.Error(err) } - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -314,12 +161,11 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{}); err != utils.ErrNotFound { + }); err != utils.ErrNotFound { t.Error(err) } - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{"rp2"}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -332,30 +178,12 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{"rp2"}); err != utils.ErrNotFound { - t.Error(err) - } - - if _, err := rate.matchingRateProfileForEvent("cgrates.org", - &ArgsCostForEvent{ - CGREventWithOpts: &utils.CGREventWithOpts{ - CGREvent: &utils.CGREvent{ - Tenant: "cgrates.org", - ID: "CACHE1", - Event: map[string]interface{}{}, - }, - Opts: map[string]interface{}{ - utils.OptsRatesStartTime: 0, - }, - }, - }, - []string{"randomProfileID"}); err.Error() != "cannot convert field: 0 to time.Time" { + }); err != utils.ErrNotFound { t.Error(err) } rpp.FilterIDs = []string{"*string:~*req.Account:1001;1002;1003", "*gt:~*req.Cost{*:10"} - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -368,13 +196,12 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{}); err.Error() != "invalid converter terminator in rule: <~*req.Cost{*>" { + }); err.Error() != "invalid converter terminator in rule: <~*req.Cost{*>" { t.Error(err) } rate.dm = nil - if _, err := rate.matchingRateProfileForEvent("cgrates.org", + if _, err := rate.matchingRateProfileForEvent("cgrates.org", []string{"rp3"}, &ArgsCostForEvent{ CGREventWithOpts: &utils.CGREventWithOpts{ CGREvent: &utils.CGREvent{ @@ -387,8 +214,7 @@ func TestMatchingRateProfileEvent(t *testing.T) { }, }, }, - }, - []string{"rp3"}); err != utils.ErrNoDatabaseConn { + }); err != utils.ErrNoDatabaseConn { t.Error(err) } }