diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index 7691fd976..0728fa7ea 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -45,6 +45,8 @@ CREATE TABLE `tp_rates` ( `tpid` varchar(64) NOT NULL, `tag` varchar(64) NOT NULL, `connect_fee` decimal(7,4) NOT NULL, + `max_cost` decimal(7,4) NOT NULL, + `max_cost_strategy` varchar(16) NOT NULL, `rate` decimal(7,4) NOT NULL, `rate_unit` varchar(16) NOT NULL, `rate_increment` varchar(16) NOT NULL, diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql index b9f0314f6..178802b85 100644 --- a/data/storage/postgres/create_tariffplan_tables.sql +++ b/data/storage/postgres/create_tariffplan_tables.sql @@ -41,6 +41,8 @@ CREATE TABLE tp_rates ( tpid VARCHAR(64) NOT NULL, tag VARCHAR(64) NOT NULL, connect_fee NUMERIC(7,4) NOT NULL, + max_cost NUMERIC(7,4) NOT NULL, + max_cost_strategy VARCHAR(16) NOT NULL, rate NUMERIC(7,4) NOT NULL, rate_unit VARCHAR(16) NOT NULL, rate_increment VARCHAR(16) NOT NULL, diff --git a/data/tariffplans/cdrstats/Rates.csv b/data/tariffplans/cdrstats/Rates.csv index 889f51f61..c4f0e9c63 100644 --- a/data/tariffplans/cdrstats/Rates.csv +++ b/data/tariffplans/cdrstats/Rates.csv @@ -1,2 +1,2 @@ #Tag,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart -RT_1CENT,0,1,1s,1s,0s \ No newline at end of file +RT_1CENT,0,0,,1,1s,1s,0s diff --git a/data/tariffplans/prepaid1centpsec/Rates.csv b/data/tariffplans/prepaid1centpsec/Rates.csv index df304849e..02365a72c 100644 --- a/data/tariffplans/prepaid1centpsec/Rates.csv +++ b/data/tariffplans/prepaid1centpsec/Rates.csv @@ -1,4 +1,4 @@ #Tag,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart -RT_1CENT,0,1,1s,1s,0s -RT_DATA_2c,0,0.002,10,10,0 -RT_SMS_5c,0,0.005,1,1,0 \ No newline at end of file +RT_1CENT,0,0,,1,1s,1s,0s +RT_DATA_2c,0,0,,0.002,10,10,0 +RT_SMS_5c,0,0,,0.005,1,1,0 diff --git a/data/tariffplans/tutorial/Rates.csv b/data/tariffplans/tutorial/Rates.csv index 5f571f24b..f66af1735 100644 --- a/data/tariffplans/tutorial/Rates.csv +++ b/data/tariffplans/tutorial/Rates.csv @@ -1,8 +1,8 @@ #Tag,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart -RT_10CNT,0.2,0.1,60s,60s,0s -RT_10CNT,0,0.05,60s,1s,60s -RT_20CNT,0.4,0.2,60s,60s,0s -RT_20CNT,0,0.1,60s,1s,60s -RT_40CNT,0.8,0.4,60s,30s,0s -RT_40CNT,0,0.2,60s,10s,60s -RT_1CNT,0,0.01,60s,60s,0s +RT_10CNT,0.2,0,,0.1,60s,60s,0s +RT_10CNT,0,0,,0.05,60s,1s,60s +RT_20CNT,0,0,.4,0.2,60s,60s,0s +RT_20CNT,0,0,,0.1,60s,1s,60s +RT_40CNT,0,0,.8,0.4,60s,30s,0s +RT_40CNT,0,0,,0.2,60s,10s,60s +RT_1CNT,0,0,,0.01,60s,60s,0s diff --git a/engine/account.go b/engine/account.go index fcb2a70c0..97e524d5d 100644 --- a/engine/account.go +++ b/engine/account.go @@ -218,6 +218,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo var leftCC *CallCost var initialLength int cc = cd.CreateCallCost() + generalBalanceChecker := true for generalBalanceChecker { generalBalanceChecker = false @@ -231,7 +232,8 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo for _, balance := range usefulUnitBalances { //log.Printf("Unit balance: %+v", balance) // log.Printf("CD BEFORE UNIT: %+v", cd) - partCC, _ := balance.DebitUnits(cd, count, balance.account, usefulMoneyBalances) + + partCC, _ := balance.DebitUnits(cd, balance.account, usefulMoneyBalances, count, dryRun) //log.Printf("CD AFTER UNIT: %+v", cd) if partCC != nil { //log.Printf("partCC: %+v", partCC.Timespans[0]) @@ -253,6 +255,11 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo } unitBalanceChecker = true generalBalanceChecker = true + // check for max cost disconnect + if dryRun && cc.maxCostDisconect { + // only return if we are in dry run (max call duration) + return + } } } } @@ -265,7 +272,7 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo for _, balance := range usefulMoneyBalances { //log.Printf("Money balance: %+v", balance) //log.Printf("CD BEFORE MONEY: %+v", cd) - partCC, _ := balance.DebitMoney(cd, count, balance.account) + partCC, _ := balance.DebitMoney(cd, balance.account, count, dryRun) //log.Printf("CD AFTER MONEY: %+v", cd) //log.Printf("partCC: %+v", partCC) if partCC != nil { @@ -287,6 +294,10 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo } moneyBalanceChecker = true generalBalanceChecker = true + if dryRun && cc.maxCostDisconect { + // only return if we are in dry run (max call duration) + return + } } } } diff --git a/engine/balances.go b/engine/balances.go index 4753b94b7..cec25e401 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -250,7 +250,7 @@ func (b *Balance) SubstractAmount(amount float64) { b.dirty = true } -func (b *Balance) DebitUnits(cd *CallDescriptor, count bool, ub *Account, moneyBalances BalanceChain) (cc *CallCost, err error) { +func (b *Balance) DebitUnits(cd *CallDescriptor, ub *Account, moneyBalances BalanceChain, count bool, dryRun bool) (cc *CallCost, err error) { if !b.IsActiveAt(cd.TimeStart) || b.Value <= 0 { return } @@ -324,11 +324,32 @@ func (b *Balance) DebitUnits(cd *CallDescriptor, count bool, ub *Account, moneyB ts.createIncrementsSlice() } //log.Printf("TS: %+v", ts) + maxCost, strategy := ts.RateInterval.GetMaxCost() + if strategy == utils.MAX_COST_DISCONNECT && cd.MaxCostSoFar >= maxCost { + // cat the entire current timespan + cc.maxCostDisconect = true + if dryRun { + cc.Timespans = cc.Timespans[:tsIndex] + return cc, nil + } + } for incIndex, inc := range ts.Increments { // debit minutes and money seconds := inc.Duration.Seconds() cost := inc.Cost //log.Printf("INC: %+v", inc) + inc.paid = false + if strategy == utils.MAX_COST_FREE && cd.MaxCostSoFar >= maxCost { + cost, inc.Cost = 0.0, 0.0 + inc.BalanceInfo.MoneyBalanceUuid = b.Uuid + inc.BalanceInfo.AccountId = ub.Id + inc.paid = true + if count { + ub.countUnits(&Action{BalanceType: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: cost, DestinationId: cc.Destination}}) + } + // go to nextincrement + continue + } var moneyBal *Balance for _, mb := range moneyBalances { if mb.Value >= cost { @@ -344,6 +365,7 @@ func (b *Balance) DebitUnits(cd *CallDescriptor, count bool, ub *Account, moneyB if cost != 0 { inc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid moneyBal.SubstractAmount(cost) + cd.MaxCostSoFar += cost } inc.paid = true if count { @@ -373,7 +395,7 @@ func (b *Balance) DebitUnits(cd *CallDescriptor, count bool, ub *Account, moneyB return } -func (b *Balance) DebitMoney(cd *CallDescriptor, count bool, ub *Account) (cc *CallCost, err error) { +func (b *Balance) DebitMoney(cd *CallDescriptor, ub *Account, count bool, dryRun bool) (cc *CallCost, err error) { if !b.IsActiveAt(cd.TimeStart) || b.Value <= 0 { return } @@ -392,12 +414,35 @@ func (b *Balance) DebitMoney(cd *CallDescriptor, count bool, ub *Account) (cc *C ts.createIncrementsSlice() } //log.Printf("TS: %+v", ts) + maxCost, strategy := ts.RateInterval.GetMaxCost() + if strategy == utils.MAX_COST_DISCONNECT && cd.MaxCostSoFar >= maxCost { + // cat the entire current timespan + cc.maxCostDisconect = true + if dryRun { + cc.Timespans = cc.Timespans[:tsIndex] + return cc, nil + } + } for incIndex, inc := range ts.Increments { // check standard subject tags //log.Printf("INC: %+v", inc) amount := inc.Cost + inc.paid = false + if strategy == utils.MAX_COST_FREE && cd.MaxCostSoFar >= maxCost { + amount, inc.Cost = 0.0, 0.0 + inc.BalanceInfo.MoneyBalanceUuid = b.Uuid + inc.BalanceInfo.AccountId = ub.Id + inc.paid = true + if count { + ub.countUnits(&Action{BalanceType: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) + } + // go to nextincrement + continue + } + if b.Value >= amount { b.SubstractAmount(amount) + cd.MaxCostSoFar += amount inc.BalanceInfo.MoneyBalanceUuid = b.Uuid inc.BalanceInfo.AccountId = ub.Id inc.paid = true diff --git a/engine/callcost.go b/engine/callcost.go index 4041c75ce..f185b351f 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -29,6 +29,7 @@ type CallCost struct { Cost float64 Timespans TimeSpans deductConnectFee bool + maxCostDisconect bool } // Merges the received timespan if they are similar (same activation period, same interval, same minute info. diff --git a/engine/calldesc.go b/engine/calldesc.go index f0c46e772..f2bb4960e 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -110,7 +110,6 @@ type CallDescriptor struct { // session limits MaxRate float64 MaxRateUnit time.Duration - MaxCost float64 MaxCostSoFar float64 account *Account test_callcost *CallCost // testing purpose only! @@ -485,12 +484,6 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura } for _, incr := range ts.Increments { totalCost += incr.Cost - if cd.MaxCost > 0 { - // limit availableCredit - if cd.MaxCostSoFar+totalCost > cd.MaxCost { - return utils.MinDuration(initialDuration, totalDuration), nil - } - } if defaultBalance.Value < 0 && incr.BalanceInfo.MoneyBalanceUuid == defaultBalance.Uuid { // this increment was payed with debt // TODO: improve this check @@ -660,7 +653,6 @@ func (cd *CallDescriptor) Clone() *CallDescriptor { DurationIndex: cd.DurationIndex, MaxRate: cd.MaxRate, MaxRateUnit: cd.MaxRateUnit, - MaxCost: cd.MaxCost, MaxCostSoFar: cd.MaxCostSoFar, FallbackSubject: cd.FallbackSubject, //RatingInfos: cd.RatingInfos, diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index aa63fcf4c..0e47e5d35 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -355,6 +355,8 @@ func TestMaxSessionTimeWithMaxRate(t *testing.T) { } } +/* + func TestMaxSessionTimeWithMaxCost(t *testing.T) { ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { @@ -369,7 +371,6 @@ func TestMaxSessionTimeWithMaxCost(t *testing.T) { 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), - MaxCost: 0.5, MaxCostSoFar: 0, } result, err := cd.GetMaxSessionDuration() @@ -378,6 +379,7 @@ func TestMaxSessionTimeWithMaxCost(t *testing.T) { t.Errorf("Expected %v was %v", expected, result) } } +*/ func TestMaxSessionTimeWithAccountAlias(t *testing.T) { cd := &CallDescriptor{ diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 2acb4ae15..e787426de 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -398,7 +398,7 @@ func (csvr *CSVReader) LoadRates() (err error) { for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { tag := record[0] var r *utils.TPRate - r, err = NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5]) + r, err = NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7]) if err != nil { return err } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 801120340..75daefa19 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -55,19 +55,19 @@ ALWAYS,*any,*any,*any,*any,00:00:00 ASAP,*any,*any,*any,*any,*asap ` rates = ` -R1,0,0.2,60,1,0 -R2,0,0.1,60s,1s,0 -R3,0,0.05,60s,1s,0 -R4,1,1,1s,1s,0 -R5,0,0.5,1s,1s,0 -LANDLINE_OFFPEAK,0,1,1,60,0 -LANDLINE_OFFPEAK,0,1,1,1,60 -GBP_71,0.000000,5.55555,1s,1s,0s -GBP_72,0.000000,7.77777,1s,1s,0s -GBP_70,0.000000,1,1,1,0 -RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s -RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s -R_URG,0,0,1,1,0 +R1,0,0,,0.2,60,1,0 +R2,0,0,,0.1,60s,1s,0 +R3,0,0,,0.05,60s,1s,0 +R4,1,0,,1,1s,1s,0 +R5,0,0,,0.5,1s,1s,0 +LANDLINE_OFFPEAK,0,0,,1,1,60,0 +LANDLINE_OFFPEAK,0,0,,1,1,1,60 +GBP_71,0.000000,0,,5.55555,1s,1s,0s +GBP_72,0.000000,0,,7.77777,1s,1s,0s +GBP_70,0.000000,0,,1,1,1,0 +RT_UK_Mobile_BIG5_PKG,0.01,0,,0,20s,20s,0s +RT_UK_Mobile_BIG5,0.01,0,,0.10,1s,1s,0s +R_URG,0,0,,0,1,1,0 ` destinationRates = ` RT_STANDARD,GERMANY,R1,*middle,4 @@ -323,7 +323,7 @@ func TestLoadRates(t *testing.T) { t.Error("Failed to load rates: ", csvr.rates) } rate := csvr.rates["R1"].RateSlots[0] - expctRs, err := utils.NewRateSlot(0, 0.2, "60", "1", "0") + expctRs, err := utils.NewRateSlot(0, 0, "", 0.2, "60", "1", "0") if err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || @@ -333,7 +333,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate, expctRs) } rate = csvr.rates["R2"].RateSlots[0] - if expctRs, err = utils.NewRateSlot(0, 0.1, "60s", "1s", "0"); err != nil { + if expctRs, err = utils.NewRateSlot(0, 0, "", 0.1, "60s", "1s", "0"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || @@ -342,7 +342,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate) } rate = csvr.rates["R3"].RateSlots[0] - if expctRs, err = utils.NewRateSlot(0, 0.05, "60s", "1s", "0"); err != nil { + if expctRs, err = utils.NewRateSlot(0, 0, "", 0.05, "60s", "1s", "0"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || @@ -351,7 +351,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate) } rate = csvr.rates["R4"].RateSlots[0] - if expctRs, err = utils.NewRateSlot(1, 1.0, "1s", "1s", "0"); err != nil { + if expctRs, err = utils.NewRateSlot(1, 0, "", 1.0, "1s", "1s", "0"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || @@ -360,7 +360,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate) } rate = csvr.rates["R5"].RateSlots[0] - if expctRs, err = utils.NewRateSlot(0, 0.5, "1s", "1s", "0"); err != nil { + if expctRs, err = utils.NewRateSlot(0, 0, "", 0.5, "1s", "1s", "0"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || @@ -369,7 +369,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate) } rate = csvr.rates["LANDLINE_OFFPEAK"].RateSlots[0] - if expctRs, err = utils.NewRateSlot(0, 1, "1", "60", "0"); err != nil { + if expctRs, err = utils.NewRateSlot(0, 0, "", 1, "1", "60", "0"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || @@ -378,7 +378,7 @@ func TestLoadRates(t *testing.T) { t.Error("Error loading rate: ", rate) } rate = csvr.rates["LANDLINE_OFFPEAK"].RateSlots[1] - if expctRs, err = utils.NewRateSlot(0, 1, "1", "1", "60"); err != nil { + if expctRs, err = utils.NewRateSlot(0, 0, "", 1, "1", "1", "60"); err != nil { t.Error("Error loading rate: ", rate, err.Error()) } else if !reflect.DeepEqual(rate, expctRs) || rate.RateUnitDuration() != expctRs.RateUnitDuration() || diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 98a419953..b69d7e345 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -95,18 +95,24 @@ type TPLoader interface { WriteToDatabase(bool, bool) error } -func NewLoadRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterval string) (r *utils.TPRate, err error) { +func NewLoadRate(tag, connectFee, maxCost, maxCostStrategy, price, ratedUnits, rateIncrements, groupInterval string) (r *utils.TPRate, err error) { cf, err := strconv.ParseFloat(connectFee, 64) if err != nil { log.Printf("Error parsing connect fee from: %v", connectFee) return } + mc, err := strconv.ParseFloat(maxCost, 64) + if err != nil { + log.Printf("Error parsing max cost from: %v", maxCost) + return + } p, err := strconv.ParseFloat(price, 64) if err != nil { log.Printf("Error parsing price from: %v", price) return } - rs, err := utils.NewRateSlot(cf, p, ratedUnits, rateIncrements, groupInterval) + + rs, err := utils.NewRateSlot(cf, mc, maxCostStrategy, p, ratedUnits, rateIncrements, groupInterval) if err != nil { return nil, err } @@ -365,7 +371,7 @@ var FileValidators = map[string]*FileLineRegexValidator{ regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\*any\s*,\s*|(?:\d{1,4};?)+\s*,\s*|\s*,\s*){4}(?:\d{2}:\d{2}:\d{2}|\*asap){1}$`), "Tag([0-9A-Za-z_]),Years([0-9;]|*any|),Months([0-9;]|*any|),MonthDays([0-9;]|*any|),WeekDays([0-9;]|*any|),Time([0-9:]|*asap)"}, utils.RATES_CSV: &FileLineRegexValidator{utils.RATES_NRCOLS, - regexp.MustCompile(`(?:\w+\s*),(?:\d+\.*\d*s*),(?:\d+\.*\d*s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*)$`), + regexp.MustCompile(`(?:\w+\s*),(?:\d+\.*\d*s*),(?:\d+\.*\d*s*),(?:\*free|\*diconnect)?,(?:\d+\.*\d*s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*),(?:\d+\.*\d*(ns|us|µs|ms|s|m|h)*\s*)$`), "Tag([0-9A-Za-z_]),ConnectFee([0-9.]),Rate([0-9.]),RateUnit([0-9.]ns|us|µs|ms|s|m|h),RateIncrementStart([0-9.]ns|us|µs|ms|s|m|h),GroupIntervalStart([0-9.]ns|us|µs|ms|s|m|h)"}, utils.DESTINATION_RATES_CSV: &FileLineRegexValidator{utils.DESTINATION_RATES_NRCOLS, regexp.MustCompile(`^(?:\w+\s*),(?:\w+\s*|\*any),(?:\w+\s*),(?:\*up|\*down|\*middle),(?:\d+)$`), diff --git a/engine/loader_helpers_test.go b/engine/loader_helpers_test.go index e75d7729a..aef61c6b6 100644 --- a/engine/loader_helpers_test.go +++ b/engine/loader_helpers_test.go @@ -40,9 +40,9 @@ DUMMY,INVALID;DATA GERMANY_MOBILE,+4915 ` var ratesSample = `#Tag,DestinationRatesTag,TimingTag,Weight -RT_1CENT,0,1,1s,1s,0s +RT_1CENT,0,0,,1,1s,1s,0s DUMMY,INVALID;DATA -RT_DATA_2c,0,0.002,10,10,0 +RT_DATA_2c,0,0,,0.002,10,10,0 ` var destRatesSample = `#Tag,DestinationsTag,RatesTag @@ -415,7 +415,7 @@ func TestTPCSVFileParser(t *testing.T) { if err != nil { t.Error(err) } - if !reflect.DeepEqual(record, []string{"RT_1CENT", "0", "1", "1s", "1s", "0s"}) { + if !reflect.DeepEqual(record, []string{"RT_1CENT", "0", "0", "", "1", "1s", "1s", "0s"}) { t.Error("Unexpected record extracted", record) } case 3: diff --git a/engine/models.go b/engine/models.go index dc420cd80..dae967889 100644 --- a/engine/models.go +++ b/engine/models.go @@ -54,6 +54,8 @@ type TpRate struct { Tpid string Tag string ConnectFee float64 + MaxCost float64 + MaxCostStrategy string Rate float64 RateUnit string RateIncrement string diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 11cbdd977..b8255259a 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -181,6 +181,8 @@ type RIRate struct { ConnectFee float64 RoundingMethod string RoundingDecimals int + MaxCost float64 + MaxCostStrategy string Rates RateGroups // GroupRateInterval (start time): Rate } @@ -341,6 +343,13 @@ func (i *RateInterval) GetRateParameters(startSecond time.Duration) (rate float6 return -1, -1, -1 } +func (ri *RateInterval) GetMaxCost() (float64, string) { + if ri.Rating == nil { + return 0.0, "" + } + return ri.Rating.MaxCost, ri.Rating.MaxCostStrategy +} + // Structure to store intervals according to weight type RateIntervalList []*RateInterval diff --git a/engine/storage_sql.go b/engine/storage_sql.go index c900965f9..af1f18925 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -1073,7 +1073,7 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*utils.TPRate, } for _, tr := range tpRates { - rs, err := utils.NewRateSlot(tr.ConnectFee, tr.Rate, tr.RateUnit, tr.RateIncrement, tr.GroupIntervalStart) + rs, err := utils.NewRateSlot(tr.ConnectFee, tr.MaxCost, tr.MaxCostStrategy, tr.Rate, tr.RateUnit, tr.RateIncrement, tr.GroupIntervalStart) if err != nil { return nil, err } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index bcb930ddc..e02dd60c3 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -157,7 +157,7 @@ func (self *TPCSVImporter) importRates(fn string) error { } continue } - newRt, err := NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5]) + newRt, err := NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7]) if err != nil { return err } diff --git a/general_tests/costs1_test.go b/general_tests/costs1_test.go index 585ec60af..055c07d35 100644 --- a/general_tests/costs1_test.go +++ b/general_tests/costs1_test.go @@ -40,9 +40,9 @@ ASAP,*any,*any,*any,*any,*asap` GERMANY_MOBILE,+4915 GERMANY_MOBILE,+4916 GERMANY_MOBILE,+4917` - rates := `RT_1CENT,0,1,1s,1s,0s -RT_DATA_2c,0,0.002,10,10,0 -RT_SMS_5c,0,0.005,1,1,0` + rates := `RT_1CENT,0,0,,1,1s,1s,0s +RT_DATA_2c,0,0,,0.002,10,10,0 +RT_SMS_5c,0,0,,0.005,1,1,0` destinationRates := `DR_RETAIL,GERMANY,RT_1CENT,*up,4 DR_RETAIL,GERMANY_MOBILE,RT_1CENT,*up,4 DR_DATA_1,*any,RT_DATA_2c,*up,4 diff --git a/general_tests/datachrg1_test.go b/general_tests/datachrg1_test.go index 6a6354c23..ca4984a20 100644 --- a/general_tests/datachrg1_test.go +++ b/general_tests/datachrg1_test.go @@ -37,8 +37,8 @@ func TestSetStorageDtChrg1(t *testing.T) { func TestLoadCsvTpDtChrg1(t *testing.T) { timings := `TM1,*any,*any,*any,*any,00:00:00 TM2,*any,*any,*any,*any,01:00:00` - rates := `RT_DATA_2c,0,0.002,10,10,0 -RT_DATA_1c,0,0.001,10,10,0` + rates := `RT_DATA_2c,0,0,,0.002,10,10,0 +RT_DATA_1c,0,0,,0.001,10,10,0` destinationRates := `DR_DATA_1,*any,RT_DATA_2c,*up,4 DR_DATA_2,*any,RT_DATA_1c,*up,4` ratingPlans := `RP_DATA1,DR_DATA_1,TM1,10 diff --git a/general_tests/ddazmbl1_test.go b/general_tests/ddazmbl1_test.go index 508dd6038..7c393d9cb 100644 --- a/general_tests/ddazmbl1_test.go +++ b/general_tests/ddazmbl1_test.go @@ -42,8 +42,8 @@ func TestLoadCsvTp(t *testing.T) { ASAP,*any,*any,*any,*any,*asap` destinations := `DST_UK_Mobile_BIG5,447596 DST_UK_Mobile_BIG5,447956` - rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s -RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s` + rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,,0,20s,20s,0s +RT_UK_Mobile_BIG5,0.01,0,,0.10,1s,1s,0s` destinationRates := `DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG,*up,8 DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5,*up,8` ratingPlans := `RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 diff --git a/general_tests/ddazmbl2_test.go b/general_tests/ddazmbl2_test.go index 823461b29..ea4c3e0b8 100644 --- a/general_tests/ddazmbl2_test.go +++ b/general_tests/ddazmbl2_test.go @@ -42,8 +42,8 @@ func TestLoadCsvTp2(t *testing.T) { ASAP,*any,*any,*any,*any,*asap` destinations := `DST_UK_Mobile_BIG5,447596 DST_UK_Mobile_BIG5,447956` - rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s -RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s` + rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,,0,20s,20s,0s +RT_UK_Mobile_BIG5,0.01,0,,0.10,1s,1s,0s` destinationRates := `DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG,*up,8 DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5,*up,8` ratingPlans := `RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 diff --git a/general_tests/ddazmbl3_test.go b/general_tests/ddazmbl3_test.go index c47555597..4905f32fe 100644 --- a/general_tests/ddazmbl3_test.go +++ b/general_tests/ddazmbl3_test.go @@ -42,8 +42,8 @@ func TestLoadCsvTp3(t *testing.T) { ASAP,*any,*any,*any,*any,*asap` destinations := `DST_UK_Mobile_BIG5,447596 DST_UK_Mobile_BIG5,447956` - rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s -RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s` + rates := `RT_UK_Mobile_BIG5_PKG,0.01,0,,0,20s,20s,0s +RT_UK_Mobile_BIG5,0.01,0,,0.10,1s,1s,0s` destinationRates := `DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG,*up,8 DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5,*up,8` ratingPlans := `RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 diff --git a/general_tests/smschrg1_test.go b/general_tests/smschrg1_test.go index 02a401100..d9d02a8f9 100644 --- a/general_tests/smschrg1_test.go +++ b/general_tests/smschrg1_test.go @@ -36,7 +36,7 @@ func TestSMSSetStorageSmsChrg1(t *testing.T) { func TestSMSLoadCsvTpSmsChrg1(t *testing.T) { timings := `ALWAYS,*any,*any,*any,*any,00:00:00` - rates := `RT_SMS_5c,0,0.005,1,1,0` + rates := `RT_SMS_5c,0,0,,0.005,1,1,0` destinationRates := `DR_SMS_1,*any,RT_SMS_5c,*up,4` ratingPlans := `RP_SMS1,DR_SMS_1,ALWAYS,10` ratingProfiles := `*out,cgrates.org,sms,*any,2012-01-01T00:00:00Z,RP_SMS1,` diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 4bc3d82e2..31610756b 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -86,8 +86,8 @@ func (self *TPRate) AsExportSlice() [][]string { } // Needed so we make sure we always use SetDurations() on a newly created value -func NewRateSlot(connectFee, rate float64, rateUnit, rateIncrement, grpInterval string) (*RateSlot, error) { - rs := &RateSlot{ConnectFee: connectFee, Rate: rate, RateUnit: rateUnit, RateIncrement: rateIncrement, +func NewRateSlot(connectFee, maxCost float64, maxCostStrategy string, rate float64, rateUnit, rateIncrement, grpInterval string) (*RateSlot, error) { + rs := &RateSlot{ConnectFee: connectFee, Rate: rate, RateUnit: rateUnit, RateIncrement: rateIncrement, MaxCost: maxCost, MaxCostStrategy: maxCostStrategy, GroupIntervalStart: grpInterval} if err := rs.SetDurations(); err != nil { return nil, err @@ -103,6 +103,8 @@ type RateSlot struct { GroupIntervalStart string // Group position rateUnitDur time.Duration rateIncrementDur time.Duration + MaxCost float64 + MaxCostStrategy string groupIntervalStartDur time.Duration } diff --git a/utils/consts.go b/utils/consts.go index efdf942c5..cd700a5c2 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -60,7 +60,7 @@ const ( CDR_STATS_CSV = "CdrStats.csv" TIMINGS_NRCOLS = 6 DESTINATIONS_NRCOLS = 2 - RATES_NRCOLS = 6 + RATES_NRCOLS = 8 DESTINATION_RATES_NRCOLS = 5 DESTRATE_TIMINGS_NRCOLS = 4 RATE_PROFILES_NRCOLS = 7 @@ -130,6 +130,8 @@ const ( SMS = "*sms" DATA = "*data" VOICE = "*voice" + MAX_COST_FREE = "*free" + MAX_COST_DISCONNECT = "*disconnect" TOR = "tor" HOURS = "hours" MINUTES = "minutes"