diff --git a/engine/balances.go b/engine/balances.go index 0130e15ae..36ff17046 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -83,7 +83,8 @@ func (b *Balance) Clone() *Balance { } // Returns the available number of seconds for a specified credit -func (b *Balance) GetMinutesForCredit(cd *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) { +func (b *Balance) GetMinutesForCredit(origCD *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) { + cd := origCD.Clone() duration = time.Duration(b.Value) * time.Second credit = initialCredit cc, err := b.GetCost(cd) @@ -112,6 +113,7 @@ func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) { if b.RateSubject != "" { cd.Subject = b.RateSubject cd.Account = cd.Subject + cd.RatingInfos = nil return cd.GetCost() } cc := cd.CreateCallCost() @@ -221,15 +223,21 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan if moneyBal != nil && b.Value >= seconds { b.Value -= seconds b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE) - moneyBal.Value -= cost + nInc.BalanceInfo.MinuteBalanceUuid = b.Uuid - nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid nInc.BalanceInfo.AccountId = ub.Id nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds} + if cost != 0 { + nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid + moneyBal.Value -= cost + moneyBal.Value = utils.Round(moneyBal.Value, roundingDecimals, utils.ROUNDING_MIDDLE) + } nInc.paid = true if count { ub.countUnits(&Action{BalanceType: MINUTES, Direction: newCC.Direction, Balance: &Balance{Value: seconds, DestinationId: newCC.Destination}}) - ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}}) + if cost != 0 { + ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}}) + } } } else { increment.paid = false diff --git a/engine/callcost.go b/engine/callcost.go index 41cdd5ba6..dff4be5c5 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -18,7 +18,6 @@ along with this program. If not, see package engine import ( - "fmt" "reflect" "time" ) @@ -32,7 +31,7 @@ type CallCost struct { } // Pretty printing for call cost -func (cc *CallCost) String() (r string) { +/*func (cc *CallCost) String() (r string) { connectFee := 0.0 if cc.deductConnectFee { connectFee = cc.GetConnectFee() @@ -43,7 +42,7 @@ func (cc *CallCost) String() (r string) { } r += " )" return -} +}*/ // Merges the received timespan if they are similar (same activation period, same interval, same minute info. func (cc *CallCost) Merge(other *CallCost) { diff --git a/engine/calldesc.go b/engine/calldesc.go index cf56d7aec..b1d467f67 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -397,7 +397,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } err := cd.LoadRatingPlans() if err != nil { - Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetAccountKey(), err)) + Logger.Err(fmt.Sprintf("error getting cost for key %s: %v", cd.GetKey(cd.Subject), err)) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans(nil) @@ -659,7 +659,7 @@ func (cd *CallDescriptor) Clone() *CallDescriptor { CallDuration: cd.CallDuration, Amount: cd.Amount, FallbackSubject: cd.FallbackSubject, - RatingInfos: cd.RatingInfos, - Increments: cd.Increments, + //RatingInfos: cd.RatingInfos, + //Increments: cd.Increments, } } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index bfd330919..43c02ebb2 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -422,7 +422,7 @@ func TestDebitAndMaxDebit(t *testing.T) { Subject: "minu_from_tm", Account: "minu", Destination: "0723", - Amount: 5400} + } cd2 := cd1.Clone() cc1, err1 := cd1.Debit() cc2, err2 := cd2.MaxDebit() @@ -434,6 +434,38 @@ func TestDebitAndMaxDebit(t *testing.T) { } } +func TestMaxDebitZeroDefinedRate(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP1000UKMOB_AT") + for _, at := range ap { + at.Execute() + } + ap, _ = accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd1 := &CallDescriptor{ + Direction: "*out", + TOR: "call", + Tenant: "cgrates.directvoip.co.uk", + Subject: "12345", + Account: "12345", + Destination: "447956", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 1, 0, 0, time.UTC), + LoopIndex: 0, + CallDuration: 0} + cc, err := cd1.MaxDebit() + if err != nil { + t.Error("Error maxdebiting: ", err) + } + if cc.GetDuration() != 49*time.Second { + t.Error("Error obtaining max debit duration: ", cc.GetDuration()) + } + if cc.Cost != 0.9 { + t.Error("Error in max debit cost: ", cc.Cost) + } +} + /*********************************** BENCHMARKS ***************************************/ func BenchmarkStorageGetting(b *testing.B) { b.StopTimer() diff --git a/engine/history_test.go b/engine/history_test.go index aa599ae6e..e77acca36 100644 --- a/engine/history_test.go +++ b/engine/history_test.go @@ -37,6 +37,7 @@ func TestHistoryDestinations(t *testing.T) { scribe := historyScribe.(*history.MockScribe) buf := scribe.BufMap[history.DESTINATIONS_FN] expected := `[{"Id":"ALL","Prefixes":["49","41","43"]}, +{"Id":"DST_UK_Mobile_BIG5","Prefixes":["447956"]}, {"Id":"GERMANY","Prefixes":["49"]}, {"Id":"GERMANY_O2","Prefixes":["41"]}, {"Id":"GERMANY_PREMIUM","Prefixes":["43"]}, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 8cee2496e..b07d14cea 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -43,12 +43,15 @@ RET,0724 PSTN_71,+4971 PSTN_72,+4972 PSTN_70,+4970 +DST_UK_Mobile_BIG5,447956 ` timings = ` WORKDAYS_00,*any,*any,*any,1;2;3;4;5,00:00:00 WORKDAYS_18,*any,*any,*any,1;2;3;4;5,18:00:00 WEEKENDS,*any,*any,*any,6;7,00:00:00 ONE_TIME_RUN,2012,,,,*asap +ALWAYS,*any,*any,*any,*any,00:00:00 +ASAP,*any,*any,*any,*any,*asap ` rates = ` R1,0,0.2,60s,1s,0,*middle,2 @@ -61,6 +64,8 @@ LANDLINE_OFFPEAK,0,1,1s,1s,60s,*up,4 GBP_71,0.000000,5.55555,1s,1s,0s,*up,4 GBP_72,0.000000,7.77777,1s,1s,0s,*up,4 GBP_70,0.000000,1,1s,1s,0s,*up,4 +RT_UK_Mobile_BIG5_PKG,0,0,20s,20s,0s,*up,8 +RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s,*up,8 ` destinationRates = ` RT_STANDARD,GERMANY,R1 @@ -75,8 +80,10 @@ T1,NAT,LANDLINE_OFFPEAK T2,GERMANY,GBP_72 T2,GERMANY_O2,GBP_70 T2,GERMANY_PREMIUM,GBP_71 +DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG +DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5 ` - destinationRateTimings = ` + ratingPlans = ` STANDARD,RT_STANDARD,WORKDAYS_00,10 STANDARD,RT_STD_WEEKEND,WORKDAYS_18,10 STANDARD,RT_STD_WEEKEND,WEEKENDS,10 @@ -91,6 +98,8 @@ TDRT,T1,WORKDAYS_00,10 TDRT,T2,WORKDAYS_00,10 G,RT_STANDARD,WORKDAYS_00,10 R,P1,WORKDAYS_00,10 +RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 +RP_UK,DR_UK_Mobile_BIG5,ALWAYS,10 ` ratingProfiles = ` CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb @@ -109,6 +118,8 @@ vdf,0,*out,fallback1,2013-11-18T13:45:00Z,G,fallback2 vdf,0,*out,fallback1,2013-11-18T13:46:00Z,G,fallback2 vdf,0,*out,fallback1,2013-11-18T13:47:00Z,G,fallback2 vdf,0,*out,fallback2,2013-11-18T13:45:00Z,R,rif +cgrates.directvoip.co.uk,call,*out,*any,2013-01-06T00:00:00Z,RP_UK, +cgrates.directvoip.co.uk,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK_Mobile_BIG5_PKG, ` sharedGroups = ` SG1,*any,*lowest_first,, @@ -119,24 +130,33 @@ SG2,*any,*lowest_first,EVENING, MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10 MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10 SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10 +TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10 +TOPUP1000UKMOB_AC,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 MORE_MINUTES,SHARED,ONE_TIME_RUN,10 +TOPUP10_AT,TOPUP10_AC,ASAP,10 +TOPUP1000UKMOB_AT,TOPUP1000UKMOB_AC,ASAP,10 ` actionTriggers = ` STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 STANDARD_TRIGGER,*minutes,*out,*max_balance,200,GERMANY,SOME_2,10 +STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,,LOG_WARNING,10 +STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,,LOG_WARNING,10 +STANDARD_TRIGGERS,*monetary,*out,*max_counter,5,FS_USERS,LOG_WARNING,10 ` accountActions = ` vdf,minitsboy;a1;a2,*out,MORE_MINUTES,STANDARD_TRIGGER +cgrates.directvoip.co.uk,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS +cgrates.directvoip.co.uk,12345,*out,TOPUP1000UKMOB_AT,STANDARD_TRIGGERS ` ) var csvr *CSVReader func init() { - csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, destinationRateTimings, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions) + csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions) csvr.LoadDestinations() csvr.LoadTimings() csvr.LoadRates() @@ -154,7 +174,7 @@ func init() { } func TestLoadDestinations(t *testing.T) { - if len(csvr.destinations) != 9 { + if len(csvr.destinations) != 10 { t.Error("Failed to load destinations: ", len(csvr.destinations)) } for _, d := range csvr.destinations { @@ -195,14 +215,12 @@ func TestLoadDestinations(t *testing.T) { if !reflect.DeepEqual(d.Prefixes, []string{`+4970`}) { t.Error("Faild to load destinations", d) } - default: - t.Error("Unknown destination tag!") } } } func TestLoadTimimgs(t *testing.T) { - if len(csvr.timings) != 4 { + if len(csvr.timings) != 6 { t.Error("Failed to load timings: ", csvr.timings) } timing := csvr.timings["WORKDAYS_00"] @@ -252,7 +270,7 @@ func TestLoadTimimgs(t *testing.T) { } func TestLoadRates(t *testing.T) { - if len(csvr.rates) != 9 { + if len(csvr.rates) != 11 { t.Error("Failed to load rates: ", csvr.rates) } rate := csvr.rates["R1"].RateSlots[0] @@ -322,7 +340,7 @@ func TestLoadRates(t *testing.T) { } func TestLoadDestinationRates(t *testing.T) { - if len(csvr.destinationRates) != 7 { + if len(csvr.destinationRates) != 9 { t.Error("Failed to load destinationrates: ", csvr.destinationRates) } drs := csvr.destinationRates["RT_STANDARD"] @@ -434,7 +452,7 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.ratingPlans) != 7 { + if len(csvr.ratingPlans) != 9 { t.Error("Failed to load rate timings: ", csvr.ratingPlans) } rplan := csvr.ratingPlans["STANDARD"] @@ -554,7 +572,7 @@ func TestLoadDestinationRateTimings(t *testing.T) { } func TestLoadRatingProfiles(t *testing.T) { - if len(csvr.ratingProfiles) != 12 { + if len(csvr.ratingProfiles) != 14 { t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles) } rp := csvr.ratingProfiles["*out:test:0:trp"] @@ -572,7 +590,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 2 { + if len(csvr.actions) != 4 { t.Error("Failed to load actions: ", csvr.actions) } as1 := csvr.actions["MINI"] @@ -681,7 +699,7 @@ func TestLoadSharedGroups(t *testing.T) { } func TestLoadActionTimings(t *testing.T) { - if len(csvr.actionsTimings) != 1 { + if len(csvr.actionsTimings) != 3 { t.Error("Failed to load action timings: ", csvr.actionsTimings) } atm := csvr.actionsTimings["MORE_MINUTES"][0] @@ -707,7 +725,7 @@ func TestLoadActionTimings(t *testing.T) { } func TestLoadActionTriggers(t *testing.T) { - if len(csvr.actionsTriggers) != 1 { + if len(csvr.actionsTriggers) != 2 { t.Error("Failed to load action triggers: ", csvr.actionsTriggers) } atr := csvr.actionsTriggers["STANDARD_TRIGGER"][0] @@ -743,8 +761,8 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 1 { - t.Error("Failed to load account actions: ", csvr.accountActions) + if len(csvr.accountActions) != 3 { + t.Error("Failed to load account actions: ", csvr.accountActions[2]) } aa := csvr.accountActions[0] expected := &Account{