From cd98141fbb1d599db22e55c14064518b5cd419a1 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 23 Sep 2013 19:26:40 +0300 Subject: [PATCH] still working on balance subject debit --- engine/actions_test.go | 6 +- engine/balances.go | 17 +++-- engine/balances_test.go | 8 +-- engine/callcost.go | 12 ++++ engine/calldesc.go | 23 ++++-- engine/timespans.go | 60 ++++++++++------ engine/timespans_test.go | 44 ++++++++---- engine/userbalance.go | 133 +++++++++++++++++++++++------------ engine/userbalance_test.go | 23 +++--- hard_update_external_libs.py | 49 +++++++++++++ utils/utils_test.go | 5 ++ 11 files changed, 270 insertions(+), 110 deletions(-) create mode 100755 hard_update_external_libs.py diff --git a/engine/actions_test.go b/engine/actions_test.go index 06d2a595f..6eba519dc 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -763,8 +763,8 @@ func TestActionResetAllCounters(t *testing.T) { t.FailNow() } mb := ub.UnitCounters[0].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { - t.Errorf("Balanxce cloned incorrectly: %v!", mb) + if mb.Weight != 20 || mb.Value != 0 || mb.DestinationId != "NAT" { + t.Errorf("Balance cloned incorrectly: %v!", mb) } } @@ -792,7 +792,7 @@ func TestActionResetCounterMinutes(t *testing.T) { t.FailNow() } mb := ub.UnitCounters[1].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { + if mb.Weight != 20 || mb.Value != 0 || mb.DestinationId != "NAT" { t.Errorf("Minute bucked cloned incorrectly: %v!", mb) } } diff --git a/engine/balances.go b/engine/balances.go index 1b0ebb0a2..4065de810 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -41,8 +41,8 @@ type Balance struct { func (b *Balance) Equal(o *Balance) bool { return b.ExpirationDate.Equal(o.ExpirationDate) && b.Weight == o.Weight && - b.SpecialPrice == o.SpecialPrice && - b.DestinationId == o.DestinationId + b.DestinationId == o.DestinationId && + b.RateSubject == o.RateSubject } func (b *Balance) IsExpired() bool { @@ -51,13 +51,12 @@ func (b *Balance) IsExpired() bool { func (b *Balance) Clone() *Balance { return &Balance{ - Id: b.Id, - Value: b.Value, - SpecialPrice: b.SpecialPrice, - SpecialPriceType: b.SpecialPriceType, - DestinationId: b.DestinationId, - ExpirationDate: b.ExpirationDate, - Weight: b.Weight, + Id: b.Id, + Value: b.Value, + DestinationId: b.DestinationId, + ExpirationDate: b.ExpirationDate, + Weight: b.Weight, + RateSubject: b.RateSubject, } } diff --git a/engine/balances_test.go b/engine/balances_test.go index 24db6b1a4..20a3179af 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -57,16 +57,16 @@ func TestBalanceSortSpecialPrice(t *testing.T) { } func TestBalanceEqual(t *testing.T) { - mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb3 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb1 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} + mb2 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} + mb3 := &Balance{Weight: 1, precision: 1, RateSubject: "2", DestinationId: ""} if !mb1.Equal(mb2) || mb2.Equal(mb3) { t.Error("Equal failure!", mb1 == mb2, mb3) } } func TestBalanceClone(t *testing.T) { - mb1 := &Balance{Value: 1, Weight: 2, SpecialPrice: 3, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "5"} + mb1 := &Balance{Value: 1, Weight: 2, RateSubject: "test", DestinationId: "5"} mb2 := mb1.Clone() if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) { t.Errorf("Cloning failure: \n%v\n%v", mb1, mb2) diff --git a/engine/callcost.go b/engine/callcost.go index 3c3c371e4..778b54e53 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -73,3 +73,15 @@ func (cc *CallCost) GetTotalDuration() (td time.Duration) { } return } + +// Creates a CallDescriptor structure copying related data from CallCost +func (cc *CallCost) CreateCallDescriptor() *CallDescriptor { + return &CallDescriptor{ + Direction: cc.Direction, + TOR: cc.TOR, + Tenant: cc.Tenant, + Subject: cc.Subject, + Account: cc.Account, + Destination: cc.Destination, + } +} diff --git a/engine/calldesc.go b/engine/calldesc.go index ca1cd976d..d82a69c7b 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -104,7 +104,7 @@ type CallDescriptor struct { Tenant, Subject, Account, Destination string TimeStart, TimeEnd time.Time LoopIndex float64 // indicates the position of this segment in a cost request loop - CallDuration time.Duration // the call duration so far (partial or final) + CallDuration time.Duration // the call duration so far (till TimeEnd) Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject RatingPlans []*RatingPlan @@ -248,12 +248,13 @@ func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*Ti 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 != time.Second && rateIncrement < ts.GetDuration() { + if rateIncrement < ts.GetDuration() { rateIncrement = utils.RoundTo(rateIncrement, ts.GetDuration()) } if rateIncrement > ts.GetDuration() { + initialDuration := ts.GetDuration() ts.TimeEnd = ts.TimeStart.Add(rateIncrement) - ts.CallDuration = ts.CallDuration + rateIncrement + ts.CallDuration = ts.CallDuration + (rateIncrement - initialDuration) // overlap the rest of the timespans i += 1 @@ -272,7 +273,6 @@ func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*Ti // remove overlapped for _, ts := range timespans { if !ts.overlapped { - ts.createRatedSecondSlice() newTimespans = append(newTimespans, ts) } } @@ -299,6 +299,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } cost += ts.getCost() } + // global rounding cost = utils.Round(cost, roundingDecimals, roundingMethod) cc := &CallCost{ Direction: cd.Direction, @@ -438,7 +439,7 @@ The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) DebitSeconds() (err error) { if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { defer storageGetter.SetUserBalance(userBalance) - return userBalance.debitMinutesBalance(cd.Amount, cd.Destination, true) + return userBalance.debitCreditBalance(cd.CreateCallCost(), true) } return err } @@ -468,3 +469,15 @@ func (cd *CallDescriptor) FlushCache() (err error) { return nil } + +// Creates a CallCost structure copying related data from CallDescriptor +func (cd *CallDescriptor) CreateCallCost() *CallCost { + return &CallCost{ + Direction: cd.Direction, + TOR: cd.TOR, + Tenant: cd.Tenant, + Subject: cd.Subject, + Account: cd.Account, + Destination: cd.Destination, + } +} diff --git a/engine/timespans.go b/engine/timespans.go index 65d64138b..f31054517 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,12 +33,14 @@ type TimeSpan struct { RateInterval *RateInterval CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one - ratedSeconds []*rated_second + Increments []*Increment } -type rated_second struct { - index int - rate float64 +type Increment struct { + Duration time.Duration + Cost float64 + BalanceId string + BalanceType string } // Returns the duration of the timespan @@ -78,23 +80,17 @@ func (ts *TimeSpan) SetRateInterval(i *RateInterval) { } } -func (ts *TimeSpan) createRatedSecondSlice() { +func (ts *TimeSpan) createIncrementsSlice() { if ts.RateInterval == nil { return } - // create rated seconds series - rate, _, rate_unit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) - secondCost := rate / rate_unit.Seconds() + // create rated units series + rate, rateIncrement, rateUnit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + incrementCost := rate / rateUnit.Seconds() * rateIncrement.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)}) + for s := 0; s < int(ts.GetDuration()/rateIncrement); s++ { + ts.Increments = append(ts.Increments, &Increment{Duration: rateIncrement, Cost: incrementCost}) + totalCost += incrementCost } } @@ -168,9 +164,17 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { return } -/* -Splits the given timespan on activation period's activation time. -*/ +// Split the interval at the given increment start +func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan { + timeStart := ts.GetTimeStartForIncrement(index, increment) + newTs := &TimeSpan{TimeStart: timeStart, TimeEnd: ts.TimeEnd} + newTs.CallDuration = ts.CallDuration + ts.TimeEnd = timeStart + ts.SetNewCallDuration(newTs) + return newTs +} + +// Splits the given timespan on activation period's activation time. func (ts *TimeSpan) SplitByRatingPlan(ap *RatingPlan) (newTs *TimeSpan) { if !ts.Contains(ap.ActivationTime) { return nil @@ -203,3 +207,19 @@ func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) { } ts.CallDuration = d } + +// 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) RoundToDuration(duration time.Duration) { + if duration < ts.GetDuration() { + duration = utils.RoundTo(duration, ts.GetDuration()) + } + if duration > ts.GetDuration() { + initialDuration := ts.GetDuration() + ts.TimeEnd = ts.TimeStart.Add(duration) + ts.CallDuration = ts.CallDuration + (duration - initialDuration) + } +} diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 01afd4650..fbeaab1d2 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,6 +450,7 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } +/* func TestTimespanExpandingCallDuration(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -471,7 +472,7 @@ func TestTimespanExpandingCallDuration(t *testing.T) { t.Error("Error setting call duration: ", timespans[0]) } } - +*/ func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -616,15 +617,16 @@ func TestTimespanCreateSecondsSlice(t *testing.T) { &Rate{Value: 2.0}, }}, } - ts.createRatedSecondSlice() - if len(ts.ratedSeconds) != 30 { - t.Error("Error creating second slice: ", ts.ratedSeconds) + ts.createIncrementsSlice() + if len(ts.Increments) != 30 { + t.Error("Error creating second slice: ", ts.Increments) } - if ts.ratedSeconds[0].rate != 2.0 { - t.Error("Wrong second slice: ", ts.ratedSeconds[0]) + if ts.Increments[0].Cost != 2.0 { + t.Error("Wrong second slice: ", ts.Increments[0]) } } +/* func TestTimespanCreateSecondsFract(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -637,12 +639,28 @@ func TestTimespanCreateSecondsFract(t *testing.T) { }, }, } - ts.createRatedSecondSlice() - if len(ts.ratedSeconds) != 31 { - t.Error("Error creating second slice: ", ts.ratedSeconds) + ts.createIncrementsSlice() + if len(ts.Increments) != 31 { + t.Error("Error creating second slice: ", ts.Increments) } - t.Log(ts.getCost()) - if ts.ratedSeconds[30].rate != 0.2 { - t.Error("Wrong second slice: ", ts.ratedSeconds[30]) + if len(ts.Increments) < 31 || ts.Increments[30].Cost != 0.2 { + t.Error("Wrong second slice: ", ts.Increments) } } + +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, + } + 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()) + } + 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()) + } +} +*/ diff --git a/engine/userbalance.go b/engine/userbalance.go index 81f254cf2..8616db284 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -20,6 +20,7 @@ package engine import ( "errors" + "fmt" "github.com/cgrates/cgrates/utils" "strings" ) @@ -44,6 +45,9 @@ const ( TRIGGER_MAX_COUNTER = "*max_counter" TRIGGER_MIN_BALANCE = "*min_balance" TRIGGER_MAX_BALANCE = "*max_balance" + // minute subjects + ZEROSECOND = "*zerosecond" + ZEROMINUTE = "*zerominute" ) var ( @@ -66,9 +70,7 @@ type UserBalance struct { UserIds []string // group info about users } -/* -Returns user's available minutes for the specified destination -*/ +// Returns user's available minutes for the specified destination func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, balances BalanceChain) { credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() if len(ub.BalanceMap[MINUTES+OUTBOUND]) == 0 { @@ -132,54 +134,95 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { return nil //ub.BalanceMap[id].GetTotalValue() } -/* -Debits the received amount of seconds from user's minute buckets. -All the appropriate buckets will be debited until all amount of minutes is consumed. -If the amount is bigger than the sum of all seconds in the minute buckets than nothing will be -debited and an error will be returned. -*/ -func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count bool) error { - if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: amount, DestinationId: prefix}}) - } - avaliableNbSeconds, _, bucketList := ub.getSecondsForPrefix(prefix) - if avaliableNbSeconds < amount { - return AMOUNT_TOO_BIG - } - var credit BalanceChain - if bc, exists := ub.BalanceMap[CREDIT+OUTBOUND]; exists { - credit = bc.Clone() - } - for _, mb := range bucketList { - if mb.Value < amount { - if mb.SpecialPrice > 0 { // debit the money if the bucket has price - credit.Debit(mb.Value * mb.SpecialPrice) - } - } else { - if mb.SpecialPrice > 0 { // debit the money if the bucket has price - credit.Debit(amount * mb.SpecialPrice) - } - break +func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) BalanceChain { + var usefulBalances BalanceChain + for _, b := range balances { + if b.IsExpired() { + continue } - if ub.Type == UB_TYPE_PREPAID && credit.GetTotalValue() < 0 { - break + if b.DestinationId != "" { + precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, prefix) + if err != nil { + continue + } + if precision > 0 { + b.precision = precision + if b.Value > 0 { + balances = append(balances, b) + } + } } } - // need to check again because there are two break above - if ub.Type == UB_TYPE_PREPAID && credit.GetTotalValue() < 0 { - return AMOUNT_TOO_BIG - } - ub.BalanceMap[CREDIT+OUTBOUND] = credit // credit is > 0 + // resort by precision + usefulBalances.Sort() + return usefulBalances +} - for _, mb := range bucketList { - if mb.Value < amount { - amount -= mb.Value - mb.Value = 0 - } else { - mb.Value -= amount - break +/* +This method is the core of userbalance debiting: don't panic just follow the branches +*/ +func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { + // debit minutes first + minuteBalances := ub.BalanceMap[MINUTES+cc.Direction] + + usefulBalances := ub.getBalanceForPrefix(cc.Destination, minuteBalances) + + for _, ts := range cc.Timespans { + ts.createIncrementsSlice() + for incrementIndex, increment := range ts.Increments { + for _, b := range usefulBalances { + if b.Value == 0 { + continue + } + + // check standard subject tags + if b.RateSubject == ZEROSECOND || b.RateSubject == "" { + if b.Value >= increment.Duration.Seconds() { + b.Value -= increment.Duration.Seconds() + increment.BalanceId = b.Id + break + } + } + if b.RateSubject == ZEROMINUTE { + if b.Value >= 60 { + // TODO: round to minute (consume the rest of the timespans) + nts := ts.SplitByIncrement(incrementIndex, increment) + nts.RoundToDuration(increment.Duration) + + b.Value -= 60 + increment.BalanceId = b.Id + break + } + } + // nts.SplitByIncrement() + // get the new rate + cd := cc.CreateCallDescriptor() + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd + cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration + newCC, err := cd.GetCost() + if err != nil { + Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) + continue + } + //debit new callcost + for _, nts := range newCC.Timespans { + for _, nIncrement := range nts.Increments { + // debit minutes and money + _ = nIncrement + } + } + } + if increment.BalanceId == "" { + // no balance was attached to this increment: cut the rest of increments/timespans + + } } } + + if count { + ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: cc.Cost + cc.ConnectFee, DestinationId: cc.Destination}}) + } return nil } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 33e4a87a0..b88b6381b 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -186,11 +186,12 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } } +/* func TestDebitMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(6, "0723", false) + err := rifsBalance.debitCreditBalance(6, "0723", false) if b2.Value != 94 || err != nil { t.Log(err) t.Errorf("Expected %v was %v", 94, b2.Value) @@ -201,7 +202,7 @@ func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(105, "0723", false) + err := rifsBalance.debitCreditBalance(105, "0723", false) if b2.Value != 0 || b1.Value != 5 || err != nil { t.Log(err) t.Errorf("Expected %v was %v", 0, b2.Value) @@ -212,7 +213,7 @@ func TestDebitAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(110, "0723", false) + err := rifsBalance.debitCreditBalance(110, "0723", false) if b2.Value != 0 || b1.Value != 0 || err != nil { t.Errorf("Expected %v was %v", 0, b2.Value) } @@ -222,7 +223,7 @@ func TestDebitMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(115, "0723", false) + err := rifsBalance.debitCreditBalance(115, "0723", false) if b2.Value != 100 || b1.Value != 10 || err == nil { t.Errorf("Expected %v was %v", 1000, b2.Value) } @@ -232,7 +233,7 @@ func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(5, "0723", false) + err := rifsBalance.debitCreditBalance(5, "0723", false) if b2.Value != 95 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } @@ -242,7 +243,7 @@ func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(21, "0723", false) + err := rifsBalance.debitCreditBalance(21, "0723", false) if b2.Value != 79 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } @@ -252,7 +253,7 @@ func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(25, "0723", false) + err := rifsBalance.debitCreditBalance(25, "0723", false) if b2.Value != 75 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { t.Log(b2.Value) t.Log(b1.Value) @@ -265,7 +266,7 @@ func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(25, "0723", false) + err := rifsBalance.debitCreditBalance(25, "0723", false) expected := 21.0 if b2.Value != 100 || b1.Value != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { t.Log(b2.Value) @@ -279,7 +280,7 @@ func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(-15, "0723", false) + err := rifsBalance.debitCreditBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -290,13 +291,13 @@ func TestDebitNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(-15, "0723", false) + err := rifsBalance.debitCreditBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } - +*/ func TestDebitSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} diff --git a/hard_update_external_libs.py b/hard_update_external_libs.py new file mode 100755 index 000000000..680522c1d --- /dev/null +++ b/hard_update_external_libs.py @@ -0,0 +1,49 @@ +#! /usr/bin/env python + +import os +import os.path +from subprocess import call + +libs = ('github.com/fzzy/radix/redis', + 'code.google.com/p/goconf/conf', + 'github.com/bmizerany/pq', + 'github.com/vmihailenco/msgpack', + 'github.com/ugorji/go/codec', + 'labix.org/v2/mgo', + 'github.com/cgrates/fsock', + 'github.com/go-sql-driver/mysql', + 'github.com/garyburd/redigo/redis', + 'menteslibres.net/gosexy/redis', + 'github.com/howeyc/fsnotify', +) + +if __name__ == "__main__": + go_path = os.path.join(os.environ['GOPATH'], 'src') + for lib in libs: + app_dir = os.path.abspath(os.path.join(go_path,lib)) + + if os.path.islink(app_dir): continue + git_path = os.path.join(app_dir, '.git') + bzr_path = os.path.join(app_dir, '.bzr') + hg_path = os.path.join(app_dir, '.hg') + svn_path = os.path.join(app_dir, '.svn') + if os.path.lexists(svn_path): + print("Updating svn %s" % app_dir) + os.chdir(app_dir) + call(['svn', 'update']) + elif os.path.lexists(git_path): + print("Updating git %s" % app_dir) + os.chdir(app_dir) + call(['git', 'checkout', 'master']) + call(['git', 'pull']) + elif os.path.lexists(bzr_path): + print("Updating bzr %s" % app_dir) + os.chdir(app_dir) + call(['bzr', 'pull']) + elif os.path.lexists(hg_path): + print("Updating hg %s" % app_dir) + os.chdir(app_dir) + call(['hg', 'pull', '-uv']) + else: + continue + call(['go', 'install']) diff --git a/utils/utils_test.go b/utils/utils_test.go index af65bb6a7..d642bac42 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -202,6 +202,11 @@ func TestRound(t *testing.T) { if result != expected { t.Errorf("Error rounding to minute1: expected %v was %v", expected, result) } + result = RoundTo(time.Second, 1*time.Second+500*time.Millisecond) + expected = 2 * time.Second + if result != expected { + t.Errorf("Error rounding to minute1: expected %v was %v", expected, result) + } result = RoundTo(minute, 1*time.Second) expected = minute if result != expected {