diff --git a/engine/timespans.go b/engine/timespans.go index 2d47da876..a10986944 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -52,6 +52,17 @@ type MinuteInfo struct { Price float64 } +func (incr *Increment) Clone() *Increment { + return &Increment{ + Duration: incr.Duration, + Cost: incr.Cost, + BalanceUuid: incr.BalanceUuid, + BalanceType: incr.BalanceType, + BalanceRateInterval: incr.BalanceRateInterval, + MinuteInfo: incr.MinuteInfo, + } +} + type Increments []*Increment func (incs Increments) GetTotalCost() float64 { @@ -184,13 +195,47 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { return } -// Split the interval at the given increment start -func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan { - timeStart := ts.GetTimeStartForIncrement(index, increment) +// Split the timespan at the given increment start +func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan { + if index <= 0 || index >= len(ts.Increments) { + return nil + } + timeStart := ts.GetTimeStartForIncrement(index) newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd} newTs.CallDuration = ts.CallDuration ts.TimeEnd = timeStart - ts.Increments = ts.Increments[0:index] + newTs.Increments = ts.Increments[index:] + ts.Increments = ts.Increments[:index] + ts.SetNewCallDuration(newTs) + return newTs +} + +// Split the timespan at the given second +func (ts *TimeSpan) SplitByDuration(duration time.Duration) *TimeSpan { + if duration <= 0 || duration >= ts.GetDuration() { + return nil + } + timeStart := ts.TimeStart.Add(duration) + newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd} + newTs.CallDuration = ts.CallDuration + ts.TimeEnd = timeStart + // split the increment + for incrIndex, incr := range ts.Increments { + if duration-incr.Duration >= 0 { + duration -= incr.Duration + } else { + + splitIncrement := ts.Increments[incrIndex].Clone() + splitIncrement.Duration -= duration + ts.Increments[incrIndex].Duration = duration + newTs.Increments = Increments{splitIncrement} + if incrIndex < len(ts.Increments)-1 { + newTs.Increments = append(newTs.Increments, ts.Increments[incrIndex+1:]...) + } + ts.Increments = ts.Increments[:incrIndex+1] + break + } + } ts.SetNewCallDuration(newTs) return newTs } @@ -230,8 +275,8 @@ func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) { } // returns a time for the specified second in the time span -func (ts *TimeSpan) GetTimeStartForIncrement(index int, increment *Increment) time.Time { - return ts.TimeStart.Add(time.Duration(int64(index) * increment.Duration.Nanoseconds())) +func (ts *TimeSpan) GetTimeStartForIncrement(index int) time.Time { + return ts.TimeStart.Add(time.Duration(int64(index) * ts.Increments[0].Duration.Nanoseconds())) } func (ts *TimeSpan) RoundToDuration(duration time.Duration) { diff --git a/engine/timespans_test.go b/engine/timespans_test.go index fbeaab1d2..e330bcf75 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -19,7 +19,7 @@ along with this program. If not, see package engine import ( - //"github.com/cgrates/cgrates/utils" + "github.com/cgrates/cgrates/utils" "testing" "time" ) @@ -450,7 +450,6 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } -/* func TestTimespanExpandingCallDuration(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -468,11 +467,11 @@ func TestTimespanExpandingCallDuration(t *testing.T) { cd := &CallDescriptor{} timespans = cd.roundTimeSpansToIncrement(timespans) - if timespans[0].CallDuration != time.Minute { + if len(timespans) != 1 || timespans[0].GetDuration() != time.Minute { t.Error("Error setting call duration: ", timespans[0]) } } -*/ + func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -626,8 +625,7 @@ func TestTimespanCreateSecondsSlice(t *testing.T) { } } -/* -func TestTimespanCreateSecondsFract(t *testing.T) { +func TestTimespanCreateIncrements(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), @@ -635,15 +633,18 @@ func TestTimespanCreateSecondsFract(t *testing.T) { RoundingMethod: utils.ROUNDING_MIDDLE, RoundingDecimals: 2, Rates: RateGroups{ - &Rate{Value: 2.0}, + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, }, }, } ts.createIncrementsSlice() - if len(ts.Increments) != 31 { - t.Error("Error creating second slice: ", ts.Increments) + if len(ts.Increments) != 3 { + t.Error("Error creating increment slice: ", len(ts.Increments)) } - if len(ts.Increments) < 31 || ts.Increments[30].Cost != 0.2 { + if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20 { t.Error("Wrong second slice: ", ts.Increments) } } @@ -651,16 +652,130 @@ func TestTimespanCreateSecondsFract(t *testing.T) { func TestTimespanSplitByIncrement(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 19, 18, 30, 30, 0, time.UTC), - CallDuration: 50 * time.Second, + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, } - i := &Increment{Duration: time.Second} - newTs := ts.SplitByIncrement(5, i) - if ts.GetDuration() != 5*time.Second || newTs.GetDuration() != 25*time.Second { - t.Error("Error spliting by second: ", ts.GetDuration(), newTs.GetDuration()) + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) } - if ts.CallDuration != 25*time.Second || newTs.CallDuration != 50*time.Second { - t.Error("Error spliting by second at setting call duration: ", ts.GetDuration(), newTs.GetDuration()) + newTs := ts.SplitByIncrement(5) + if ts.GetDuration() != 50*time.Second || newTs.GetDuration() != 10*time.Second { + t.Error("Error spliting by increment: ", ts.GetDuration(), newTs.GetDuration()) + } + if ts.CallDuration != 50*time.Second || newTs.CallDuration != 60*time.Second { + t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration, newTs.CallDuration) + } + if len(ts.Increments) != 5 || len(newTs.Increments) != 1 { + t.Error("Error spliting increments: ", ts.Increments, newTs.Increments) + } +} + +func TestTimespanSplitByIncrementStart(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByIncrement(0) + if ts.GetDuration() != 60*time.Second || newTs != nil { + t.Error("Error spliting by increment: ", ts.GetDuration()) + } + if ts.CallDuration != 60*time.Second { + t.Error("Error spliting by incrementat setting call duration: ", ts.CallDuration) + } + if len(ts.Increments) != 6 { + t.Error("Error spliting increments: ", ts.Increments) + } +} + +func TestTimespanSplitByIncrementEnd(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByIncrement(6) + if ts.GetDuration() != 60*time.Second || newTs != nil { + t.Error("Error spliting by increment: ", ts.GetDuration()) + } + if ts.CallDuration != 60*time.Second { + t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration) + } + if len(ts.Increments) != 6 { + t.Error("Error spliting increments: ", ts.Increments) + } +} + +func TestTimespanSplitByDuration(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByDuration(46 * time.Second) + if ts.GetDuration() != 46*time.Second || newTs.GetDuration() != 14*time.Second { + t.Error("Error spliting by duration: ", ts.GetDuration(), newTs.GetDuration()) + } + if ts.CallDuration != 46*time.Second || newTs.CallDuration != 60*time.Second { + t.Error("Error spliting by duration at setting call duration: ", ts.CallDuration, newTs.CallDuration) + } + if len(ts.Increments) != 5 || len(newTs.Increments) != 2 { + t.Error("Error spliting increments: ", ts.Increments, newTs.Increments) + } + if ts.Increments[4].Duration != 6*time.Second || newTs.Increments[0].Duration != 4*time.Second { + t.Error("Error spliting increment: ", ts.Increments[4], newTs.Increments[0]) } } -*/ diff --git a/engine/userbalance.go b/engine/userbalance.go index e8e0f0ac5..565d239c1 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -82,7 +82,7 @@ func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit continue } if cc.Cost > 0 && cc.GetDuration() > 0 { - // TODO: fix this + // TODO: improve this secondCost := cc.Cost / cc.GetDuration().Seconds() credit -= s * secondCost } @@ -181,9 +181,9 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ { ts := cc.Timespans[tsIndex] ts.createIncrementsSlice() - tsWasSplited := false + tsWasSplit := false for incrementIndex, increment := range ts.Increments { - if tsWasSplited { + if tsWasSplit { break } paid := false @@ -208,7 +208,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { newTs := ts if incrementIndex != 0 { // if increment it's not at the begining we must split the timespan - newTs = ts.SplitByIncrement(incrementIndex, increment) + newTs = ts.SplitByIncrement(incrementIndex) } newTs.RoundToDuration(time.Minute) newTs.RateInterval = &RateInterval{ @@ -236,7 +236,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { cc.Timespans = append(cc.Timespans, nil) copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) cc.Timespans[tsIndex] = newTs - tsWasSplited = true + tsWasSplit = true } var newTimespans []*TimeSpan @@ -260,7 +260,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { // newTs.SplitByIncrement() // get the new rate cd := cc.CreateCallDescriptor() - cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration newCC, err := b.GetCost(cd) @@ -269,25 +269,76 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { continue } //debit new callcost + var paidTs []*TimeSpan for _, nts := range newCC.Timespans { - for _, nIncrement := range nts.Increments { + paidTs = append(paidTs, nts) + for nIdx, nInc := range nts.Increments { // debit minutes and money - _ = nIncrement + amount := nInc.Cost + if b.Value >= amount { + b.Value -= amount + nInc.BalanceUuid = b.Uuid + if count { + ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}}) + } + } else { + nts.SplitByIncrement(nIdx) + } } } + // calculate overlaped timespans + var paidDuration time.Duration + for _, pts := range paidTs { + paidDuration += pts.GetDuration() + } + if paidDuration > 0 { + // split from current increment + newTs := ts.SplitByIncrement(incrementIndex) + remainingTs := []*TimeSpan{newTs} + + for tsi := tsIndex + 1; tsi < len(cc.Timespans); tsi++ { + remainingTs = append(remainingTs, cc.Timespans[tsi]) + } + for remainingIndex, rts := range remainingTs { + if paidDuration >= rts.GetDuration() { + paidDuration -= rts.GetDuration() + } else { + if paidDuration > 0 { + // this ts was not fully paid + fragment := rts.SplitByDuration(paidDuration) + paidTs = append(paidTs, fragment) + } + // delete from tsIndex to current + cc.Timespans = append(cc.Timespans[:tsIndex], cc.Timespans[remainingIndex:]...) + break + } + } + + // append the timpespans to outer timespans + for _, pts := range paidTs { + tsIndex++ + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) + cc.Timespans[tsIndex] = pts + } + paid = true + tsWasSplit = true + } } if paid { continue } else { // Split if some increments were processed by minutes if incrementIndex > 0 && ts.Increments[incrementIndex-1].MinuteInfo != nil { - newTs := ts.SplitByIncrement(incrementIndex, increment) - idx := tsIndex + 1 - cc.Timespans = append(cc.Timespans, nil) - copy(cc.Timespans[idx+1:], cc.Timespans[idx:]) - cc.Timespans[idx] = newTs - newTs.createIncrementsSlice() - tsWasSplited = true + newTs := ts.SplitByIncrement(incrementIndex) + if newTs != nil { + idx := tsIndex + 1 + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[idx+1:], cc.Timespans[idx:]) + cc.Timespans[idx] = newTs + newTs.createIncrementsSlice() + tsWasSplit = true + } break } } @@ -308,7 +359,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } else { // get the new rate cd := cc.CreateCallDescriptor() - cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration newCC, err := b.GetCost(cd) @@ -326,11 +377,11 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } if !paid { // no balance was attached to this increment: cut the rest of increments/timespans - ts.SplitByIncrement(incrementIndex, increment) - if len(ts.Increments) == 0 { - // if there are no increments left in the ts leav it out + if incrementIndex == 0 { + // if we are right at the begining in the ts leave it out cc.Timespans = cc.Timespans[:tsIndex] } else { + ts.SplitByIncrement(incrementIndex) cc.Timespans = cc.Timespans[:tsIndex+1] } return nil