diff --git a/apier/apier.go b/apier/apier.go index e72af86a0..bc27d36c9 100644 --- a/apier/apier.go +++ b/apier/apier.go @@ -126,7 +126,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { Balance: &engine.Balance{ Value: attr.Value, ExpirationDate: attr.ExpirationDate, - RateSubject: attr.RatingSubject, + RatingSubject: attr.RatingSubject, DestinationId: attr.DestinationId, Weight: attr.Weight, }, @@ -300,7 +300,7 @@ func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error { Value: apiAct.Units, Weight: apiAct.BalanceWeight, DestinationId: apiAct.DestinationId, - RateSubject: apiAct.RatingSubject, + RatingSubject: apiAct.RatingSubject, }, } storeActions[idx] = a diff --git a/charging_tests/ddazmbl1_test.go b/charging_tests/ddazmbl1_test.go new file mode 100644 index 000000000..9f9669ab5 --- /dev/null +++ b/charging_tests/ddazmbl1_test.go @@ -0,0 +1,156 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2012-2014 ITsysCOM GmbH + +This program is free software: you can Storagetribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITH*out ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package engine + +import ( + "testing" + "time" + + "github.com/cgrates/cgrates/cache2go" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/scheduler" +) + +var ratingDb engine.RatingStorage +var acntDb engine.AccountingStorage + +func init() { + ratingDb, _ = engine.NewMapStorageJson() + engine.SetRatingStorage(ratingDb) + acntDb, _ = engine.NewMapStorageJson() + engine.SetAccountingStorage(acntDb) +} + +func TestLoadCsvTp(t *testing.T) { + timings := `ALWAYS,*any,*any,*any,*any,00:00:00 +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,*up,8 +RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s,*up,8` + destinationRates := `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` + ratingPlans := `RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10 +RP_UK,DR_UK_Mobile_BIG5,ALWAYS,10` + ratingProfiles := `cgrates.org,call,*out,*any,2013-01-06T00:00:00Z,RP_UK, +cgrates.org,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK_Mobile_BIG5_PKG,` + sharedGroups := `` + actions := `TOPUP10_AC,*topup_reset,*monetary,*out,10,*unlimited,*any,,10,,,10 +TOPUP10_AC1,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10` + actionPlans := `TOPUP10_AT,TOPUP10_AC,ASAP,10 +TOPUP10_AT,TOPUP10_AC1,ASAP,10` + actionTriggers := `` + accountActions := `cgrates.org,12345,*out,TOPUP10_AT,` + csvr := engine.NewStringCSVReader(ratingDb, acntDb, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles, sharedGroups, actions, actionPlans, actionTriggers, accountActions) + if err := csvr.LoadDestinations(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadTimings(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadRates(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadDestinationRates(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadRatingPlans(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadRatingProfiles(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadSharedGroups(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadActions(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadActionTimings(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadActionTriggers(); err != nil { + t.Fatal(err) + } + if err := csvr.LoadAccountActions(); err != nil { + t.Fatal(err) + } + csvr.WriteToDatabase(false, false) + if acnt, err := acntDb.GetAccount("*out:cgrates.org:12345"); err != nil { + t.Error(err) + } else if acnt == nil { + t.Error("No account saved") + } + ratingDb.CacheRating(nil, nil, nil, nil) + acntDb.CacheAccounting(nil, nil, nil) + if cachedDests := cache2go.CountEntries(engine.DESTINATION_PREFIX); cachedDests != 2 { + t.Error("Wrong number of cached destinations found", cachedDests) + } + if cachedRPlans := cache2go.CountEntries(engine.RATING_PLAN_PREFIX); cachedRPlans != 2 { + t.Error("Wrong number of cached rating plans found", cachedRPlans) + } + if cachedRProfiles := cache2go.CountEntries(engine.RATING_PROFILE_PREFIX); cachedRProfiles != 2 { + t.Error("Wrong number of cached rating profiles found", cachedRProfiles) + } + if cachedActions := cache2go.CountEntries(engine.ACTION_PREFIX); cachedActions != 2 { + t.Error("Wrong number of cached actions found", cachedActions) + } +} + +func TestExecuteActions(t *testing.T) { + scheduler.NewScheduler().LoadActionTimings(acntDb) + time.Sleep(time.Duration(1) * time.Microsecond) // Give time to scheduler to topup the account + if acnt, err := acntDb.GetAccount("*out:cgrates.org:12345"); err != nil { + t.Error(err) + } else if len(acnt.BalanceMap) != 2 { + t.Error("Account does not have enough balances: ", acnt.BalanceMap) + } else if acnt.BalanceMap[engine.MINUTES+engine.OUTBOUND][0].Value != 40 { + t.Errorf("Account does not have enough minutes in balance", acnt.BalanceMap[engine.MINUTES+engine.OUTBOUND][0].Value) + } else if acnt.BalanceMap[engine.CREDIT+engine.OUTBOUND][0].Value != 10 { + t.Error("Account does not have enough monetary balance", acnt.BalanceMap[engine.CREDIT+engine.OUTBOUND][0].Value) + } +} + +func TestDebit(t *testing.T) { + cd := &engine.CallDescriptor{ + Direction: "*out", + TOR: "call", + Tenant: "cgrates.org", + Subject: "12345", + Account: "12345", + Destination: "447956933443", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), + } + if cc, err := cd.Debit(); err != nil { + t.Error(err) + } else if cc.Cost != 0.01 { + t.Error("Wrong cost returned: ", cc.Cost) + } + acnt, err := acntDb.GetAccount("*out:cgrates.org:12345") + if err != nil { + t.Error(err) + } + if acnt.BalanceMap[engine.MINUTES+engine.OUTBOUND][0].Value != 20 { + t.Error("Account does not have expected minutes in balance", acnt.BalanceMap[engine.MINUTES+engine.OUTBOUND][0].Value) + } + if acnt.BalanceMap[engine.CREDIT+engine.OUTBOUND][0].Value != 9.99 { + t.Error("Account does not have expected monetary balance", acnt.BalanceMap[engine.CREDIT+engine.OUTBOUND][0].Value) + } +} diff --git a/cmd/cgr-tester/cgr-tester.go b/cmd/cgr-tester/cgr-tester.go index bbef40b7d..e659d33c8 100644 --- a/cmd/cgr-tester/cgr-tester.go +++ b/cmd/cgr-tester/cgr-tester.go @@ -53,9 +53,9 @@ var ( dbdata_encoding = flag.String("dbdata_encoding", cgrConfig.DBDataEncoding, "The encoding used to store object data in strings.") raterAddress = flag.String("rater_address", "", "Rater address for remote tests. Empty for internal rater.") tor = flag.String("tor", "call", "The type of record to use in queries.") - tenant = flag.String("tenant", "call", "The type of record to use in queries.") + tenant = flag.String("tenant", "cgrates.org", "The type of record to use in queries.") subject = flag.String("subject", "1001", "The rating subject to use in queries.") - destination = flag.String("destination", "+4986517174963", "The destination to use in queries.") + destination = flag.String("destination", "1002", "The destination to use in queries.") nilDuration = time.Duration(0) ) @@ -150,8 +150,8 @@ func main() { defer pprof.StopCPUProfile() } cd := &engine.CallDescriptor{ - TimeStart: time.Date(2013, time.December, 13, 22, 30, 0, 0, time.UTC), - TimeEnd: time.Date(2013, time.December, 13, 22, 31, 0, 0, time.UTC), + TimeStart: time.Date(2014, time.December, 11, 55, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2014, time.December, 11, 55, 31, 0, 0, time.UTC), CallDuration: 60 * time.Second, Direction: "*out", TOR: *tor, diff --git a/console/debit_balance.go b/console/debit_balance.go index 210ad61fd..03aa03d2c 100644 --- a/console/debit_balance.go +++ b/console/debit_balance.go @@ -38,7 +38,7 @@ type CmdDebitBalance struct { // name should be exec's name func (self *CmdDebitBalance) Usage(name string) string { - return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] debit_balance ") + return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] debit_balance ") } // set param defaults @@ -50,17 +50,17 @@ func (self *CmdDebitBalance) defaults() error { // Parses command line args and builds CmdBalance value func (self *CmdDebitBalance) FromArgs(args []string) error { - if len(args) != 8 { + if len(args) != 9 { return fmt.Errorf(self.Usage("")) } // Args look OK, set defaults before going further self.defaults() var tStart time.Time var err error - if args[6] == "*now" { + if args[7] == "*now" { tStart = time.Now() } else { - tStart, err = utils.ParseDate(args[6]) + tStart, err = utils.ParseDate(args[7]) if err != nil { fmt.Println("\n*start_time* should have one of the formats:") fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC") @@ -70,14 +70,15 @@ func (self *CmdDebitBalance) FromArgs(args []string) error { return fmt.Errorf(self.Usage("")) } } - callDur, err := utils.ParseDurationWithSecs(args[7]) + callDur, err := utils.ParseDurationWithSecs(args[8]) if err != nil { fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n") } self.rpcParams.TOR = args[2] self.rpcParams.Tenant = args[3] - self.rpcParams.Subject = args[4] - self.rpcParams.Destination = args[5] + self.rpcParams.Account = args[4] + self.rpcParams.Subject = args[5] + self.rpcParams.Destination = args[6] self.rpcParams.TimeStart = tStart self.rpcParams.CallDuration = callDur self.rpcParams.TimeEnd = tStart.Add(callDur) diff --git a/data/tester/cgr-tester.py b/data/tester/cgr-tester.py index 8993fa4d9..dbb99029d 100644 --- a/data/tester/cgr-tester.py +++ b/data/tester/cgr-tester.py @@ -47,9 +47,9 @@ cd = {"Direction":"*out", "TOR":"call", "Tenant": "cgrates.org", "Subject": "1001", - "Destination": "+49", - "TimeStart": "2013-08-07T17:30:00Z", - "TimeEnd": "2013-08-07T18:30:00Z", + "Destination": "1002", + "TimeStart": "2014-04-03T11:12:23.190554134+02:00", + "TimeEnd": "2014-04-03T11:13:23.190554134+02:00", "CallDuration": 60000000000, } @@ -60,7 +60,7 @@ cd = {"Direction":"*out", start_time = time.time() i = 0 -runs = 1e5 +runs = 5e5 result = "" for i in range(int(runs) + 1): result = rpc.call("Responder.GetCost", cd) diff --git a/engine/account.go b/engine/account.go index 9d93276fd..d847db712 100644 --- a/engine/account.go +++ b/engine/account.go @@ -182,9 +182,33 @@ func (ub *Account) getBalancesForPrefix(prefix string, balances BalanceChain, sh } // resort by precision usefulBalances.Sort() + // clear precision + for _, b := range usefulBalances { + b.precision = 0 + } return usefulBalances } +// like getBalancesForPrefix but expanding shared balances +func (account *Account) getAlldBalancesForPrefix(destination, balanceType string) (bc BalanceChain) { + balances := account.getBalancesForPrefix(destination, account.BalanceMap[balanceType], "") + for _, b := range balances { + if b.SharedGroup != "" { + sharedGroup, err := accountingStorage.GetSharedGroup(b.SharedGroup, false) + if err != nil { + Logger.Warning(fmt.Sprintf("Could not get shared group: %v", b.SharedGroup)) + continue + } + sharedBalances := sharedGroup.GetBalances(destination, balanceType, account) + sharedBalances = sharedGroup.SortBalancesByStrategy(b, sharedBalances) + bc = append(bc, sharedBalances...) + } else { + bc = append(bc, b) + } + } + return +} + func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) { usefulMinuteBalances := ub.getAlldBalancesForPrefix(cc.Destination, MINUTES+cc.Direction) usefulMoneyBalances := ub.getAlldBalancesForPrefix(cc.Destination, CREDIT+cc.Direction) @@ -482,23 +506,3 @@ func (account *Account) GetUniqueSharedGroupMembers(destination, direction strin } return memberIds, nil } - -// like getBalancesForPrefix but expanding shared balances -func (account *Account) getAlldBalancesForPrefix(destination, balanceType string) (bc BalanceChain) { - balances := account.getBalancesForPrefix(destination, account.BalanceMap[balanceType], "") - for _, b := range balances { - if b.SharedGroup != "" { - sharedGroup, err := accountingStorage.GetSharedGroup(b.SharedGroup, false) - if err != nil { - Logger.Warning(fmt.Sprintf("Could not get shared group: %v", b.SharedGroup)) - continue - } - sharedBalances := sharedGroup.GetBalances(destination, balanceType, account) - sharedBalances = sharedGroup.SortBalancesByStrategy(b, sharedBalances) - bc = append(bc, sharedBalances...) - } else { - bc = append(bc, b) - } - } - return -} diff --git a/engine/account_test.go b/engine/account_test.go index 3ed7247d0..b400e0679 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -115,8 +115,8 @@ func TestGetSecondsForPrefix(t *testing.T) { } func TestGetSpecialPricedSeconds(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} - b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET", RateSubject: "minu"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT", RatingSubject: "minu"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET", RatingSubject: "minu"} ub1 := &Account{ Id: "OUT:CUSTOMER_1:rif", @@ -158,7 +158,7 @@ func TestAccountStorageStore(t *testing.T) { } func TestDebitCreditZeroSecond(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -187,7 +187,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { } func TestDebitCreditZeroMinute(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -220,8 +220,8 @@ func TestDebitCreditZeroMinute(t *testing.T) { } } func TestDebitCreditZeroMixedMinute(t *testing.T) { - b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1m"} - b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} + b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RatingSubject: "*zero1m"} + b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -255,7 +255,7 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { } func TestDebitCreditNoCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -295,7 +295,7 @@ func TestDebitCreditNoCredit(t *testing.T) { } func TestDebitCreditHasCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -337,7 +337,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } func TestDebitCreditSplitMinutesMoney(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -374,7 +374,7 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } func TestDebitCreditMoreTimespans(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -411,8 +411,8 @@ func TestDebitCreditMoreTimespans(t *testing.T) { } func TestDebitCreditMoreTimespansMixed(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} - b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1s"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} + b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RatingSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -450,7 +450,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { } func TestDebitCreditNoConectFeeCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -527,7 +527,7 @@ func TestDebitCreditMoneyOnly(t *testing.T) { } func TestDebitCreditSubjectMinutes(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 250, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + b1 := &Balance{Uuid: "testb", Value: 250, Weight: 10, DestinationId: "NAT", RatingSubject: "minu"} cc := &CallCost{ Tenant: "vdf", TOR: "0", @@ -586,7 +586,7 @@ func TestDebitCreditSubjectMoney(t *testing.T) { deductConnectFee: true, } rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ - CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 75, DestinationId: "NAT", RateSubject: "minu"}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 75, DestinationId: "NAT", RatingSubject: "minu"}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { @@ -606,7 +606,7 @@ func TestDebitCreditSubjectMoney(t *testing.T) { } func TestDebitCreditSubjectMixed(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 40, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + b1 := &Balance{Uuid: "testb", Value: 40, Weight: 10, DestinationId: "NAT", RatingSubject: "minu"} cc := &CallCost{ Tenant: "vdf", TOR: "0", @@ -624,7 +624,7 @@ func TestDebitCreditSubjectMixed(t *testing.T) { } rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 150, RateSubject: "minu"}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 150, RatingSubject: "minu"}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { @@ -649,7 +649,7 @@ func TestDebitCreditSubjectMixed(t *testing.T) { } func TestDebitCreditSubjectMixedMoreTS(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "minu"} cc := &CallCost{ Tenant: "vdf", TOR: "0", @@ -673,7 +673,7 @@ func TestDebitCreditSubjectMixedMoreTS(t *testing.T) { } rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50, RateSubject: "minu"}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50, RatingSubject: "minu"}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err == nil { @@ -700,7 +700,7 @@ func TestDebitCreditSubjectMixedMoreTS(t *testing.T) { } func TestDebitCreditSubjectMixedPartPay(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RatingSubject: "minu"} cc := &CallCost{ Tenant: "vdf", TOR: "0", @@ -724,7 +724,7 @@ func TestDebitCreditSubjectMixedPartPay(t *testing.T) { } rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 75, RateSubject: "minu"}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 75, RatingSubject: "minu"}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err == nil { diff --git a/engine/action.go b/engine/action.go index c0dc08373..00ed811e6 100644 --- a/engine/action.go +++ b/engine/action.go @@ -48,22 +48,22 @@ type Action struct { } const ( - LOG = "*log" - RESET_TRIGGERS = "*reset_triggers" - ALLOW_NEGATIVE = "*allow_negative" - DENY_NEGATIVE = "*deny_negative" - RESET_ACCOUNT = "*reset_account" - TOPUP_RESET = "*topup_reset" - TOPUP = "*topup" - DEBIT = "*debit" - RESET_COUNTER = "*reset_counter" - RESET_COUNTERS = "*reset_counters" - ENABLE_USER = "*enable_user" - DISABLE_USER = "*disable_user" - CALL_URL = "*call_url" - CALL_URL_ASYNC = "*call_url_async" - MAIL_ASYNC = "*mail_async" - UNLIMITED = "*unlimited" + LOG = "*log" + RESET_TRIGGERS = "*reset_triggers" + ALLOW_NEGATIVE = "*allow_negative" + DENY_NEGATIVE = "*deny_negative" + RESET_ACCOUNT = "*reset_account" + TOPUP_RESET = "*topup_reset" + TOPUP = "*topup" + DEBIT = "*debit" + RESET_COUNTER = "*reset_counter" + RESET_COUNTERS = "*reset_counters" + ENABLE_ACCOUNT = "*enable_account" + DISABLE_ACCOUNT = "*disable_account" + CALL_URL = "*call_url" + CALL_URL_ASYNC = "*call_url_async" + MAIL_ASYNC = "*mail_async" + UNLIMITED = "*unlimited" ) type actionTypeFunc func(*Account, *Action) error @@ -90,9 +90,9 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { return resetCounterAction, true case RESET_COUNTERS: return resetCountersAction, true - case ENABLE_USER: + case ENABLE_ACCOUNT: return enableUserAction, true - case DISABLE_USER: + case DISABLE_ACCOUNT: return disableUserAction, true case CALL_URL: return callUrl, true diff --git a/engine/action_timing.go b/engine/action_timing.go index 9067b67d4..0ba9ea304 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -235,7 +235,10 @@ func (at *ActionTiming) Execute() (err error) { for _, ubId := range at.AccountIds { _, err := AccLock.Guard(ubId, func() (float64, error) { ub, err := accountingStorage.GetAccount(ubId) - if ub.Disabled { + if err != nil { + Logger.Warning(fmt.Sprintf("Could not get user balances for this id: %s. Skipping!", ubId)) + return 0, err + } else if ub.Disabled { return 0, fmt.Errorf("User %s is disabled", ubId) } if err != nil { diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 26c1a8020..3effe16af 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -21,8 +21,8 @@ package engine import ( "encoding/json" "fmt" - "sort" "github.com/cgrates/cgrates/utils" + "sort" ) type ActionTrigger struct { diff --git a/engine/balances.go b/engine/balances.go index 59cdfe84b..2f285c633 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -33,7 +33,7 @@ type Balance struct { ExpirationDate time.Time Weight float64 DestinationId string - RateSubject string + RatingSubject string SharedGroup string precision int account *Account // used to store ub reference for shared balances @@ -50,14 +50,14 @@ func (b *Balance) Equal(o *Balance) bool { return b.ExpirationDate.Equal(o.ExpirationDate) && b.Weight == o.Weight && b.DestinationId == o.DestinationId && - b.RateSubject == o.RateSubject && + b.RatingSubject == o.RatingSubject && b.SharedGroup == o.SharedGroup } // the default balance has no destinationid, Expirationdate or ratesubject func (b *Balance) IsDefault() bool { return (b.DestinationId == "" || b.DestinationId == utils.ANY) && - b.RateSubject == "" && + b.RatingSubject == "" && b.ExpirationDate.IsZero() && b.SharedGroup == "" } @@ -81,7 +81,7 @@ func (b *Balance) Clone() *Balance { DestinationId: b.DestinationId, ExpirationDate: b.ExpirationDate, Weight: b.Weight, - RateSubject: b.RateSubject, + RatingSubject: b.RatingSubject, } } @@ -125,8 +125,8 @@ func (b *Balance) GetMinutesForCredit(origCD *CallDescriptor, initialCredit floa } func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) { - if b.RateSubject != "" { - cd.Subject = b.RateSubject + if b.RatingSubject != "" { + cd.Subject = b.RatingSubject cd.Account = cd.Subject cd.RatingInfos = nil return cd.GetCost() @@ -162,13 +162,13 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan if increment.paid { continue } - if duration, err := utils.ParseZeroRatingSubject(b.RateSubject); err == nil { + if duration, err := utils.ParseZeroRatingSubject(b.RatingSubject); err == nil { seconds := duration.Seconds() amount := seconds if seconds == 1 { amount = increment.Duration.Seconds() } - if b.Value >= amount { // balance has at least 60 seconds + if b.Value >= amount { newTs := ts inc := increment if seconds > 1 { // we need to recreate increments @@ -215,7 +215,7 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan } // get the new rate cd := cc.CreateCallDescriptor() - cd.Subject = b.RateSubject + cd.Subject = b.RatingSubject cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration @@ -240,7 +240,7 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan break } } - if moneyBal != nil && b.Value >= seconds { + if (cost == 0 || moneyBal != nil) && b.Value >= seconds { b.SubstractAmount(seconds) nInc.BalanceInfo.MinuteBalanceUuid = b.Uuid nInc.BalanceInfo.AccountId = ub.Id @@ -310,7 +310,7 @@ func (b *Balance) DebitMoney(cc *CallCost, count bool, ub *Account) error { continue } // check standard subject tags - if b.RateSubject == "" { + if b.RatingSubject == "" { amount := increment.Cost if b.Value >= amount { b.SubstractAmount(amount) @@ -324,7 +324,7 @@ func (b *Balance) DebitMoney(cc *CallCost, count bool, ub *Account) error { } else { // get the new rate cd := cc.CreateCallDescriptor() - cd.Subject = b.RateSubject + cd.Subject = b.RatingSubject cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration diff --git a/engine/balances_test.go b/engine/balances_test.go index 8423652d1..89e821613 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -79,16 +79,16 @@ func TestBalanceSortWeightLess(t *testing.T) { } func TestBalanceEqual(t *testing.T) { - 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: ""} + mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationId: ""} + mb2 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationId: ""} + mb3 := &Balance{Weight: 1, precision: 1, RatingSubject: "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, RateSubject: "test", DestinationId: "5"} + mb1 := &Balance{Value: 1, Weight: 2, RatingSubject: "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/calldesc_test.go b/engine/calldesc_test.go index df4b3a303..4718a3932 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -58,7 +58,7 @@ func populateDB() { Id: "*out:vdf:broker", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{ - &Balance{Value: 20, DestinationId: "NAT", Weight: 10, RateSubject: "rif"}, + &Balance{Value: 20, DestinationId: "NAT", Weight: 10, RatingSubject: "rif"}, &Balance{Value: 100, DestinationId: "RET", Weight: 20}, }}, } @@ -67,7 +67,7 @@ func populateDB() { Id: "*out:vdf:minitsboy", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{ - &Balance{Value: 20, DestinationId: "NAT", Weight: 10, RateSubject: "rif"}, + &Balance{Value: 20, DestinationId: "NAT", Weight: 10, RatingSubject: "rif"}, &Balance{Value: 100, DestinationId: "RET", Weight: 20}, }, CREDIT + OUTBOUND: BalanceChain{ @@ -575,7 +575,7 @@ func TestMaxDebitZeroDefinedRate(t *testing.T) { cd1 := &CallDescriptor{ Direction: "*out", TOR: "call", - Tenant: "cgrates.directvoip.co.uk", + Tenant: "cgrates.org", Subject: "12345", Account: "12345", Destination: "447956", @@ -603,7 +603,7 @@ func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) { cd1 := &CallDescriptor{ Direction: "*out", TOR: "call", - Tenant: "cgrates.directvoip.co.uk", + Tenant: "cgrates.org", Subject: "12345", Account: "12345", Destination: "447956", @@ -613,7 +613,7 @@ func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) { CallDuration: 0} cc, err := cd1.MaxDebit() if err != nil { - t.Error("Error maxdebiting: ", err) + t.Fatal("Error maxdebiting: ", err) } if cc.GetDuration() != 40*time.Second { t.Error("Error obtaining max debit duration: ", cc.GetDuration()) @@ -623,7 +623,29 @@ func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) { } } -/*********************************** BENCHMARKS ***************************************/ +func TestMaxDebitConsumesMinutes(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd1 := &CallDescriptor{ + Direction: "*out", + TOR: "call", + Tenant: "cgrates.org", + 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, 0, 5, 0, time.UTC), + LoopIndex: 0, + CallDuration: 0} + cd1.MaxDebit() + if cd1.account.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { + t.Error("Error using minutes: ", cd1.account.BalanceMap[MINUTES+OUTBOUND][0].Value) + } +} + +/*************** BENCHMARKS ********************/ func BenchmarkStorageGetting(b *testing.B) { b.StopTimer() t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) diff --git a/engine/loader_csv.go b/engine/loader_csv.go index c3274ee45..930f621be 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -518,16 +518,16 @@ func (csvr *CSVReader) LoadSharedGroups() (err error) { sg, found := csvr.sharedGroups[tag] if found { sg.AccountParameters[record[1]] = &SharingParameters{ - Strategy: record[2], - RateSubject: record[3], + Strategy: record[2], + RatingSubject: record[3], } } else { sg = &SharedGroup{ Id: tag, AccountParameters: map[string]*SharingParameters{ record[1]: &SharingParameters{ - Strategy: record[2], - RateSubject: record[3], + Strategy: record[2], + RatingSubject: record[3], }, }, } @@ -584,7 +584,7 @@ func (csvr *CSVReader) LoadActions() (err error) { Value: units, Weight: balanceWeight, DestinationId: record[6], - RateSubject: record[7], + RatingSubject: record[7], SharedGroup: record[9], }, } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index ef17ae785..b3c4b641a 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -118,8 +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, +cgrates.org,call,*out,*any,2013-01-06T00:00:00Z,RP_UK, +cgrates.org,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK_Mobile_BIG5_PKG, ` sharedGroups = ` SG1,*any,*lowest, @@ -157,7 +157,7 @@ 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.org,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS vdf,empty0,*out,TOPUP_SHARED0_AT, vdf,empty10,*out,TOPUP_SHARED10_AT, vdf,emptyX,*out,TOPUP_EMPTY_AT, @@ -633,7 +633,7 @@ func TestLoadActions(t *testing.T) { Uuid: as1[1].Balance.Uuid, Value: 100, Weight: 10, - RateSubject: "test", + RatingSubject: "test", DestinationId: "NAT", }, }, @@ -673,8 +673,8 @@ func TestLoadSharedGroups(t *testing.T) { Id: "SG1", AccountParameters: map[string]*SharingParameters{ "*any": &SharingParameters{ - Strategy: "*lowest", - RateSubject: "", + Strategy: "*lowest", + RatingSubject: "", }, }, } @@ -686,8 +686,8 @@ func TestLoadSharedGroups(t *testing.T) { Id: "SG2", AccountParameters: map[string]*SharingParameters{ "*any": &SharingParameters{ - Strategy: "*lowest", - RateSubject: "one", + Strategy: "*lowest", + RatingSubject: "one", }, }, } diff --git a/engine/loader_db.go b/engine/loader_db.go index 27744e584..cc128bfaf 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -473,7 +473,7 @@ func (dbr *DbReader) LoadActions() (err error) { Uuid: utils.GenUUID(), Value: tpact.Units, Weight: tpact.BalanceWeight, - RateSubject: tpact.RatingSubject, + RatingSubject: tpact.RatingSubject, DestinationId: tpact.DestinationId, }, } @@ -712,7 +712,7 @@ func (dbr *DbReader) LoadAccountActionsFiltered(qriedAA *utils.TPAccountActions) Uuid: utils.GenUUID(), Value: tpact.Units, Weight: tpact.BalanceWeight, - RateSubject: tpact.RatingSubject, + RatingSubject: tpact.RatingSubject, DestinationId: tpact.DestinationId, }, } diff --git a/engine/sharedgroup.go b/engine/sharedgroup.go index 00e4c1a02..eb21c3dba 100644 --- a/engine/sharedgroup.go +++ b/engine/sharedgroup.go @@ -45,8 +45,8 @@ type SharedGroup struct { } type SharingParameters struct { - Strategy string - RateSubject string + Strategy string + RatingSubject string } func (sg *SharedGroup) GetMembersExceptUser(ubId string) []string { diff --git a/engine/storage_map.go b/engine/storage_map.go index 46b9dab3e..219d99787 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -41,6 +41,10 @@ func NewMapStorage() (*MapStorage, error) { return &MapStorage{dict: make(map[string][]byte), ms: NewCodecMsgpackMarshaler()}, nil } +func NewMapStorageJson() (*MapStorage, error) { + return &MapStorage{dict: make(map[string][]byte), ms: new(JSONBufMarshaler)}, nil +} + func (ms *MapStorage) Close() {} func (ms *MapStorage) Flush() error { @@ -182,8 +186,9 @@ func (ms *MapStorage) SetRatingPlan(rp *RatingPlan) (err error) { w.Close() ms.dict[RATING_PLAN_PREFIX+rp.Id] = b.Bytes() response := 0 - - go historyScribe.Record(rp.GetHistoryRecord(), &response) + if historyScribe != nil { + go historyScribe.Record(rp.GetHistoryRecord(), &response) + } //cache2go.Cache(RATING_PLAN_PREFIX+rp.Id, rp) return } @@ -211,7 +216,9 @@ func (ms *MapStorage) SetRatingProfile(rpf *RatingProfile) (err error) { result, err := ms.ms.Marshal(rpf) ms.dict[RATING_PROFILE_PREFIX+rpf.Id] = result response := 0 - go historyScribe.Record(rpf.GetHistoryRecord(), &response) + if historyScribe != nil { + go historyScribe.Record(rpf.GetHistoryRecord(), &response) + } //cache2go.Cache(RATING_PROFILE_PREFIX+rpf.Id, rpf) return } @@ -342,7 +349,9 @@ func (ms *MapStorage) SetDestination(dest *Destination) (err error) { w.Close() ms.dict[DESTINATION_PREFIX+dest.Id] = b.Bytes() response := 0 - go historyScribe.Record(dest.GetHistoryRecord(), &response) + if historyScribe != nil { + go historyScribe.Record(dest.GetHistoryRecord(), &response) + } //cache2go.Cache(DESTINATION_PREFIX+dest.Id, dest) return } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index aa1aa30d3..4f3d1cfa0 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -294,7 +294,7 @@ func (self *SQLStorage) SetTPSharedGroups(tpid string, sgs map[string]*SharedGro buffer.WriteRune(',') } buffer.WriteString(fmt.Sprintf("('%s','%s','%s','%s','%s')", - tpid, sgId, account, params.Strategy, params.RateSubject)) + tpid, sgId, account, params.Strategy, params.RatingSubject)) i++ } } @@ -962,7 +962,7 @@ func (self *SQLStorage) GetTpTimings(tpid, tag string) (map[string]*utils.TPTimi func (self *SQLStorage) GetTpRatingPlans(tpid, tag string) (map[string][]*utils.TPRatingPlanBinding, error) { rpbns := make(map[string][]*utils.TPRatingPlanBinding) - q := fmt.Sprintf("SELECT * FROM %s WHERE tpid='%s'", utils.TBL_TP_RATING_PLANS, tpid) + q := fmt.Sprintf("SELECT tpid, id, destrates_id, timing_id, weight FROM %s WHERE tpid='%s'", utils.TBL_TP_RATING_PLANS, tpid) if tag != "" { q += fmt.Sprintf(" AND id='%s'", tag) } @@ -972,10 +972,9 @@ func (self *SQLStorage) GetTpRatingPlans(tpid, tag string) (map[string][]*utils. } defer rows.Close() for rows.Next() { - var id int var weight float64 - var tpid, tag, destination_rates_tag, timings_tag string - if err := rows.Scan(&id, &tpid, &tag, &destination_rates_tag, &timings_tag, &weight); err != nil { + var tpid, id, destination_rates_tag, timings_tag string + if err := rows.Scan(&tpid, &id, &destination_rates_tag, &timings_tag, &weight); err != nil { return nil, err } rpb := &utils.TPRatingPlanBinding{ @@ -983,10 +982,11 @@ func (self *SQLStorage) GetTpRatingPlans(tpid, tag string) (map[string][]*utils. TimingId: timings_tag, Weight: weight, } - if rpBnLst, exists := rpbns[tag]; exists { - rpBnLst = append(rpBnLst, rpb) + // Logger.Debug(fmt.Sprintf("For RatingPlan id: %s, loading RatingPlanBinding: %v", tag, rpb)) + if _, exists := rpbns[id]; exists { + rpbns[id] = append(rpbns[id], rpb) } else { // New - rpbns[tag] = []*utils.TPRatingPlanBinding{rpb} + rpbns[id] = []*utils.TPRatingPlanBinding{rpb} } } return rpbns, nil @@ -1055,16 +1055,16 @@ func (self *SQLStorage) GetTpSharedGroups(tpid, tag string) (map[string]*SharedG sg, found := sgs[tag] if found { sg.AccountParameters[account] = &SharingParameters{ - Strategy: strategy, - RateSubject: rateSubject, + Strategy: strategy, + RatingSubject: rateSubject, } } else { sg = &SharedGroup{ Id: tag, AccountParameters: map[string]*SharingParameters{ account: &SharingParameters{ - Strategy: strategy, - RateSubject: rateSubject, + Strategy: strategy, + RatingSubject: rateSubject, }, }, } diff --git a/test.sh b/test.sh index 0789794ca..831b73e97 100755 --- a/test.sh +++ b/test.sh @@ -15,6 +15,8 @@ go test -i github.com/cgrates/cgrates/cdre go test github.com/cgrates/cgrates/engine en=$? +go test github.com/cgrates/cgrates/charging_tests +ct=$? go test github.com/cgrates/cgrates/sessionmanager sm=$? go test github.com/cgrates/cgrates/config @@ -39,4 +41,4 @@ go test github.com/cgrates/cgrates/cdre cdre=$? -exit $en && $sm && $cfg && $bl && $cr && $md && $cdrs && $cdrc && $fs && $ut && $hs && $c2g && $cdre +exit $en && $ct && $sm && $cfg && $bl && $cr && $md && $cdrs && $cdrc && $fs && $ut && $hs && $c2g && $cdre