From e18420ea234c00028ca25026fd6b1a3816cf5dda Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 12 Sep 2013 21:09:42 +0300 Subject: [PATCH] rated seconds slice --- engine/balances.go | 1 + engine/callcost.go | 2 +- engine/callcost_test.go | 4 +- engine/calldesc.go | 66 +++------ engine/rateinterval.go | 14 +- engine/timespans.go | 92 +++++------- engine/timespans_test.go | 231 +++++++++++++---------------- sessionmanager/fssessionmanager.go | 27 +--- 8 files changed, 162 insertions(+), 275 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 20de21adb..1b0ebb0a2 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -34,6 +34,7 @@ type Balance struct { SpecialPriceType string SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) DestinationId string + RateSubject string precision int } diff --git a/engine/callcost.go b/engine/callcost.go index 7c75f29ab..3c3c371e4 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -48,7 +48,7 @@ func (cc *CallCost) Merge(other *CallCost) { ts := cc.Timespans[len(cc.Timespans)-1] otherTs := other.Timespans[0] if reflect.DeepEqual(ts.RatingPlan, otherTs.RatingPlan) && - reflect.DeepEqual(ts.MinuteInfo, otherTs.MinuteInfo) && reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { + reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { // extend the last timespan with ts.TimeEnd = ts.TimeEnd.Add(otherTs.GetDuration()) // add the rest of the timspans diff --git a/engine/callcost_test.go b/engine/callcost_test.go index d1e9f364b..0c2d49328 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -86,7 +86,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { if cc1.Cost != 90 { t.Errorf("expected 90 was %v", cc1.Cost) } - t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) + /*t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC) cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() @@ -99,7 +99,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { } if cc1.Cost != 120 { t.Errorf("Exdpected 120 was %v", cc1.Cost) - } + }*/ } func TestMultipleInputRightMerge(t *testing.T) { diff --git a/engine/calldesc.go b/engine/calldesc.go index 9211af136..ca1cd976d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -197,26 +197,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti firstSpan = &TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd, CallDuration: cd.CallDuration} } timespans = append(timespans, firstSpan) - // split on (free) minute buckets - if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { - _, _, minuteBalances := userBalance.getSecondsForPrefix(cd.Destination) - for _, b := range minuteBalances { - for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue - } - newTs := timespans[i].SplitByMinuteBalance(b) - if newTs != nil { - timespans = append(timespans, newTs) - firstSpan = newTs // we move the firstspan to the newly created one for further spliting - break - } - } - } - } - if firstSpan.MinuteInfo != nil { - return // all the timespans are on minutes - } if len(cd.RatingPlans) == 0 { return } @@ -230,9 +210,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } else { afterStart = true for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue - } newTs := timespans[i].SplitByRatingPlan(ap) if newTs != nil { timespans = append(timespans, newTs) @@ -245,9 +222,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } // split on price intervals for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue // cont try to split timespans payed with minutes - } ap := timespans[i].RatingPlan //timespans[i].RatingPlan = nil ap.RateIntervals.Sort() @@ -262,27 +236,29 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } } } - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) return } // if the rate interval for any timespan has a RatingIncrement larger than the timespan duration // the timespan must expand potentially overlaping folowing timespans and may exceed call // descriptor's initial duration -func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { +func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*TimeSpan { for i, ts := range timespans { if ts.RateInterval != nil { _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) // if the timespan duration is larger than the rate increment make sure it is a multiple of it - if rateIncrement < ts.GetDuration() { + if rateIncrement != time.Second && rateIncrement < ts.GetDuration() { rateIncrement = utils.RoundTo(rateIncrement, ts.GetDuration()) } if rateIncrement > ts.GetDuration() { ts.TimeEnd = ts.TimeStart.Add(rateIncrement) - ts.SetNewCallDuration(ts) // set new call duration for this timespan + ts.CallDuration = ts.CallDuration + rateIncrement + // overlap the rest of the timespans + i += 1 for ; i < len(timespans); i++ { - if timespans[i].TimeEnd.Before(ts.TimeEnd) { + if timespans[i].TimeEnd.Before(ts.TimeEnd) || timespans[i].TimeEnd.Equal(ts.TimeEnd) { timespans[i].overlapped = true } else if timespans[i].TimeStart.Before(ts.TimeEnd) { timespans[i].TimeStart = ts.TimeEnd @@ -292,14 +268,15 @@ func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { } } } + var newTimespans []*TimeSpan // remove overlapped - for i, ts := range timespans { - if ts.overlapped { - timespans = timespans[:i] - break + for _, ts := range timespans { + if !ts.overlapped { + ts.createRatedSecondSlice() + newTimespans = append(newTimespans, ts) } } - return timespans + return newTimespans } /* @@ -317,10 +294,10 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { for i, ts := range timespans { // only add connect fee if this is the first/only call cost request - if cd.LoopIndex == 0 && i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { connectionFee = ts.RateInterval.ConnectFee } - cost += ts.getCost(cd) + cost += ts.getCost() } cost = utils.Round(cost, roundingDecimals, roundingMethod) cc := &CallCost{ @@ -376,10 +353,10 @@ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float6 cost := 0.0 for i, ts := range timespans { - if i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + if i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.ConnectFee } - cost += ts.getCost(cd) + cost += ts.Cost } //logger.Print(availableCredit, availableSeconds, cost) if cost < availableCredit { @@ -408,14 +385,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { Logger.Debug(fmt.Sprintf(" Attempting to debit from %v, value: %v", cd.GetUserBalanceKey(), cc.Cost+cc.ConnectFee)) defer storageGetter.SetUserBalance(userBalance) if cc.Cost != 0 || cc.ConnectFee != 0 { - userBalance.debitBalance(CREDIT, cc.Cost+cc.ConnectFee, true) - } - for _, ts := range cc.Timespans { - if ts.MinuteInfo != nil { - if err = userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true); err != nil { - return cc, err - } - } + userBalance.debitBalance(CREDIT+OUTBOUND, cc.Cost+cc.ConnectFee, true) } } return diff --git a/engine/rateinterval.go b/engine/rateinterval.go index db8c8b0fe..9857be0f1 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -21,7 +21,6 @@ package engine import ( "fmt" "github.com/cgrates/cgrates/utils" - "math" "reflect" "sort" "strconv" @@ -40,7 +39,7 @@ type RateInterval struct { StartTime, EndTime string // ##:##:## format Weight, ConnectFee float64 Rates RateGroups // GroupRateInterval (start time): Rate - RoundingMethod string + RoundingMethod string //ROUNDING_UP, ROUNDING_DOWN, ROUNDING_MIDDLE RoundingDecimals int } @@ -194,13 +193,12 @@ func (i *RateInterval) Equal(o *RateInterval) bool { i.EndTime == o.EndTime } -func (i *RateInterval) GetCost(duration, startSecond time.Duration) (cost float64) { - price, rateIncrement, rateUnit := i.GetRateParameters(startSecond) - d := float64(duration.Seconds()) +func (i *RateInterval) GetCost(duration, startSecond time.Duration) float64 { + price, _, rateUnit := i.GetRateParameters(startSecond) + d := duration.Seconds() price /= rateUnit.Seconds() - ri := rateIncrement.Seconds() - cost = math.Ceil(d/ri) * ri * price - return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod) + + return utils.Round(d*price, i.RoundingDecimals, i.RoundingMethod) } // Gets the price for a the provided start second diff --git a/engine/timespans.go b/engine/timespans.go index b2abd493a..65d64138b 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -19,7 +19,7 @@ along with this program. If not, see package engine import ( - "fmt" + "github.com/cgrates/cgrates/utils" "time" ) @@ -31,16 +31,14 @@ type TimeSpan struct { Cost float64 RatingPlan *RatingPlan RateInterval *RateInterval - MinuteInfo *MinuteInfo CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one + ratedSeconds []*rated_second } -// Holds the bonus minute information related to a specified timespan -type MinuteInfo struct { - DestinationId string - Quantity float64 - Price float64 +type rated_second struct { + index int + rate float64 } // Returns the duration of the timespan @@ -48,27 +46,20 @@ func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) } +// Returns true if the given time is inside timespan range. +func (ts *TimeSpan) Contains(t time.Time) bool { + return t.After(ts.TimeStart) && t.Before(ts.TimeEnd) +} + // Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refound on session // manager debit loop where the cost cannot be recalculated) -func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { - if ts.MinuteInfo != nil { - return ts.GetDuration().Seconds() * ts.MinuteInfo.Price - } +func (ts *TimeSpan) getCost() float64 { if ts.RateInterval == nil { return 0 } - i := ts.RateInterval - cost = i.GetCost(ts.GetDuration(), ts.GetGroupStart()) - ts.Cost = cost - return -} - -/* -Returns true if the given time is inside timespan range. -*/ -func (ts *TimeSpan) Contains(t time.Time) bool { - return t.After(ts.TimeStart) && t.Before(ts.TimeEnd) + ts.Cost = ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) + return ts.Cost } /* @@ -87,6 +78,26 @@ func (ts *TimeSpan) SetRateInterval(i *RateInterval) { } } +func (ts *TimeSpan) createRatedSecondSlice() { + if ts.RateInterval == nil { + return + } + // create rated seconds series + rate, _, rate_unit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + secondCost := rate / rate_unit.Seconds() + totalCost := 0.0 + for s := 0; s < int(ts.GetDuration().Seconds()); s++ { + ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{s, secondCost}) + totalCost += secondCost + } + cCost := ts.getCost() + // here there might be some subsecond duration left + if totalCost < cCost { + // add one extra second with the fractional cost + ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{len(ts.ratedSeconds), utils.Round(cCost-totalCost, ts.RateInterval.RoundingDecimals, ts.RateInterval.RoundingMethod)}) + } +} + /* Splits the given timespan according to how it relates to the interval. It will modify the endtime of the received timespan and it will return @@ -171,43 +182,6 @@ func (ts *TimeSpan) SplitByRatingPlan(ap *RatingPlan) (newTs *TimeSpan) { return } -/* -Splits the given timespan on minute bucket's duration. -*/ -func (ts *TimeSpan) SplitByMinuteBalance(mb *Balance) (newTs *TimeSpan) { - // if mb expired skip it - if !mb.ExpirationDate.IsZero() && (ts.TimeStart.Equal(mb.ExpirationDate) || ts.TimeStart.After(mb.ExpirationDate)) { - return nil - } - - // expiring before time spans end - - if !mb.ExpirationDate.IsZero() && ts.TimeEnd.After(mb.ExpirationDate) { - newTs = &TimeSpan{TimeStart: mb.ExpirationDate, TimeEnd: ts.TimeEnd} - newTs.CallDuration = ts.CallDuration - ts.TimeEnd = mb.ExpirationDate - ts.SetNewCallDuration(newTs) - } - - s := ts.GetDuration().Seconds() - ts.MinuteInfo = &MinuteInfo{mb.DestinationId, s, mb.SpecialPrice} - if s <= mb.Value { - mb.Value -= s - return newTs - } - secDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", mb.Value)) - - newTimeEnd := ts.TimeStart.Add(secDuration) - newTs = &TimeSpan{TimeStart: newTimeEnd, TimeEnd: ts.TimeEnd} - ts.TimeEnd = newTimeEnd - newTs.CallDuration = ts.CallDuration - ts.MinuteInfo.Quantity = mb.Value - ts.SetNewCallDuration(newTs) - mb.Value = 0 - - return -} - // Returns the starting time of this timespan func (ts *TimeSpan) GetGroupStart() time.Duration { s := ts.CallDuration - ts.GetDuration() diff --git a/engine/timespans_test.go b/engine/timespans_test.go index ff2ec3bad..01afd4650 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "github.com/cgrates/cgrates/utils" "testing" "time" ) @@ -188,17 +189,17 @@ func TestTimespanGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) ts1 := TimeSpan{TimeStart: t1, TimeEnd: t2} - cd := &CallDescriptor{Subject: "other"} - if ts1.getCost(cd) != 0 { + if ts1.getCost() != 0 { t.Error("No interval and still kicking") } - ts1.RateInterval = &RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}} - if ts1.getCost(cd) != 600 { - t.Error("Expected 10 got ", ts1.getCost(cd)) + ts1.SetRateInterval(&RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}}) + if ts1.getCost() != 600 { + t.Error("Expected 10 got ", ts1.Cost) } - ts1.RateInterval.Rates[0].RateUnit = 60 * time.Second - if ts1.getCost(cd) != 10 { - t.Error("Expected 6000 got ", ts1.getCost(cd)) + ts1.RateInterval = nil + ts1.SetRateInterval(&RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 60 * time.Second}}}) + if ts1.getCost() != 10 { + t.Error("Expected 6000 got ", ts1.Cost) } } @@ -217,118 +218,6 @@ func TestSetRateInterval(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketPlenty(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Bad extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarce(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 60} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpired(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 39, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo != nil { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Bad extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpiring(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpiringEnd(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringSame(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 120, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentExpFirst(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 140, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 1, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { - t.Error("Not enough minutes on minute bucket split: ", ts.MinuteInfo.Quantity) - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentScarceFirst(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 61, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 30, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - func TestTimespanSplitGroupedRates(t *testing.T) { i := &RateInterval{ EndTime: "17:59:00", @@ -366,19 +255,38 @@ func TestTimespanSplitGroupedRates(t *testing.T) { func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { i := &RateInterval{ EndTime: "17:59:00", - Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{30 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 2, + RateIncrement: time.Second, + RateUnit: time.Second}, + &Rate{ + GroupIntervalStart: 30 * time.Second, + Value: 1, + RateIncrement: time.Minute, + RateUnit: time.Second, + }}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 31, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 60 * time.Second} oldDuration := ts.GetDuration() nts := ts.SplitByRateInterval(i) + cd := &CallDescriptor{} + timespans := cd.roundTimeSpansToIncrement([]*TimeSpan{ts, nts}) + if len(timespans) != 2 { + t.Error("Error rounding timespans: ", timespans) + } + ts = timespans[0] + nts = timespans[1] splitTime := time.Date(2012, time.February, 3, 17, 30, 30, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts) } - if nts.TimeStart != splitTime || nts.TimeEnd != t2 { - t.Error("Incorrect second half", nts) + t3 := time.Date(2012, time.February, 3, 17, 31, 30, 0, time.UTC) + if nts.TimeStart != splitTime || nts.TimeEnd != t3 { + t.Error("Incorrect second half", nts.TimeStart, nts.TimeEnd) } if ts.RateInterval != i { t.Error("RateInterval not attached correctly") @@ -389,10 +297,10 @@ func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { t.Error("Wrong costs: ", c1, c2) } - if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 0.5*60 { + if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 1*60 { t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } - if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + if ts.GetDuration()+nts.GetDuration() != oldDuration+30*time.Second { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } } @@ -533,7 +441,7 @@ func TestTimespanExpandingPastEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -542,6 +450,28 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } +func TestTimespanExpandingCallDuration(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 60 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.roundTimeSpansToIncrement(timespans) + + if timespans[0].CallDuration != time.Minute { + t.Error("Error setting call duration: ", timespans[0]) + } +} + func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -557,7 +487,7 @@ func TestTimespanExpandingRoundingPastEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 2 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -585,7 +515,7 @@ func TestTimespanExpandingPastEndMultiple(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -613,7 +543,7 @@ func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -637,7 +567,7 @@ func TestTimespanExpandingBeforeEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 2 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -667,7 +597,7 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 3 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -677,3 +607,42 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { t.Error("Error expanding timespan: ", timespans[0]) } } + +func TestTimespanCreateSecondsSlice(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{Value: 2.0}, + }}, + } + ts.createRatedSecondSlice() + if len(ts.ratedSeconds) != 30 { + t.Error("Error creating second slice: ", ts.ratedSeconds) + } + if ts.ratedSeconds[0].rate != 2.0 { + t.Error("Wrong second slice: ", ts.ratedSeconds[0]) + } +} + +func TestTimespanCreateSecondsFract(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 100000000, time.UTC), + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{Value: 2.0}, + }, + }, + } + ts.createRatedSecondSlice() + if len(ts.ratedSeconds) != 31 { + t.Error("Error creating second slice: ", ts.ratedSeconds) + } + t.Log(ts.getCost()) + if ts.ratedSeconds[30].rate != 0.2 { + t.Error("Wrong second slice: ", ts.ratedSeconds[30]) + } +} diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index a466bb2bc..1e22acfd1 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -247,7 +247,6 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refoundDuration := end.Sub(hangupTime).Seconds() cost := 0.0 - seconds := 0.0 engine.Logger.Info(fmt.Sprintf("Refund duration: %v", refoundDuration)) for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] @@ -258,18 +257,11 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { tmpCost := (procentage * ts.Cost) / 100 ts.Cost -= tmpCost cost += tmpCost - if ts.MinuteInfo != nil { - // DestinationPrefix and Price take from lastCC and above caclulus - seconds += (procentage * ts.MinuteInfo.Quantity) / 100 - } // set the end time to now ts.TimeEnd = hangupTime break // do not go to other timespans } else { cost += ts.Cost - if ts.MinuteInfo != nil { - seconds += ts.MinuteInfo.Quantity - } // remove the timestamp entirely lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refound @@ -293,25 +285,8 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { engine.Logger.Err(fmt.Sprintf("Debit cents failed: %v", err)) } } - if seconds > 0 { - cd := &engine.CallDescriptor{ - Direction: lastCC.Direction, - TOR: lastCC.TOR, - Tenant: lastCC.Tenant, - Subject: lastCC.Subject, - Account: lastCC.Account, - Destination: lastCC.Destination, - Amount: -seconds, - // FallbackSubject: lastCC.FallbackSubject, // ToDo: check how to best add it - } - var response float64 - err := sm.connector.DebitSeconds(*cd, &response) - if err != nil { - engine.Logger.Err(fmt.Sprintf("Debit seconds failed: %v", err)) - } - } lastCC.Cost -= cost - engine.Logger.Info(fmt.Sprintf("Rambursed %v cents, %v seconds", cost, seconds)) + engine.Logger.Info(fmt.Sprintf("Rambursed %v cents", cost)) }