diff --git a/rater/action_trigger.go b/rater/action_trigger.go index 93a95a208..53e65f41e 100644 --- a/rater/action_trigger.go +++ b/rater/action_trigger.go @@ -29,6 +29,7 @@ type ActionTrigger struct { BalanceId string Direction string ThresholdValue float64 + ThresholdType string DestinationId string Weight float64 ActionsId string diff --git a/rater/loader_csv.go b/rater/loader_csv.go index d6547c702..b42b65b3d 100644 --- a/rater/loader_csv.go +++ b/rater/loader_csv.go @@ -235,7 +235,7 @@ func (csvr *CSVReader) LoadRates() (err error) { continue } var r *Rate - r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5]) + r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7]) if err != nil { return err } @@ -492,7 +492,7 @@ func (csvr *CSVReader) LoadActionTriggers() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action trigger value: %v", err)) } - weight, err := strconv.ParseFloat(record[6], 64) + weight, err := strconv.ParseFloat(record[7], 64) if err != nil { return errors.New(fmt.Sprintf("Could not parse action trigger weight: %v", err)) } @@ -501,8 +501,9 @@ func (csvr *CSVReader) LoadActionTriggers() (err error) { BalanceId: record[1], Direction: record[2], ThresholdValue: value, - DestinationId: record[4], - ActionsId: record[5], + ThresholdType: record[4], + DestinationId: record[5], + ActionsId: record[6], Weight: weight, } csvr.actionsTriggers[tag] = append(csvr.actionsTriggers[tag], at) diff --git a/rater/loader_csv_test.go b/rater/loader_csv_test.go index fbf252637..8fa0ee4a3 100644 --- a/rater/loader_csv_test.go +++ b/rater/loader_csv_test.go @@ -44,11 +44,11 @@ WEEKENDS,*all,*all,*all,6;7,00:00:00 ONE_TIME_RUN,2012,,,,*asap ` rates = ` -R1,0,0.2,60,1,10 -R2,0,0.1,60,1,10 -R3,0,0.05,60,1,10 -R4,1,1,1,1,10 -R5,0,0.5,1,1,10 +R1,0,0.2,60,1,*middle,2,10 +R2,0,0.1,60,1,*middle,2,10 +R3,0,0.05,60,1,*middle,2,10 +R4,1,1,1,1,*up,2,10 +R5,0,0.5,1,1,*down,2,10 ` destinationRates = ` RT_STANDARD,GERMANY,R1 @@ -90,8 +90,8 @@ MINI,TOPUP,MINUTES,OUT,100,NAT,ABSOLUTE,0,10,10 MORE_MINUTES,MINI,ONE_TIME_RUN,10 ` actionTriggers = ` -STANDARD_TRIGGER,MINUTES,OUT,10,GERMANY_O2,SOME_1,10 -STANDARD_TRIGGER,MINUTES,OUT,200,GERMANY,SOME_2,10 +STANDARD_TRIGGER,MINUTES,OUT,10,COUNTER,GERMANY_O2,SOME_1,10 +STANDARD_TRIGGER,MINUTES,OUT,200,BALANCE,GERMANY,SOME_2,10 ` accountActions = ` vdf,minitsboy,OUT,MORE_MINUTES,STANDARD_TRIGGER diff --git a/rater/loader_helpers.go b/rater/loader_helpers.go index 1bd9008cd..4fe63340c 100644 --- a/rater/loader_helpers.go +++ b/rater/loader_helpers.go @@ -50,7 +50,7 @@ type Rate struct { Weight float64 } -func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, weight string) (r *Rate, err error) { +func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) { cf, err := strconv.ParseFloat(connectFee, 64) if err != nil { log.Printf("Error parsing connect fee from: %v", connectFee) @@ -76,13 +76,21 @@ func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, weight string) log.Printf("Error parsing rates increments from: %s", weight) return } + rd, err := strconv.Atoi(roundingDecimals) + if err != nil { + log.Printf("Error parsing rounding decimals: %s", roundingDecimals) + return + } + r = &Rate{ - Tag: tag, - ConnectFee: cf, - Price: p, - PricedUnits: pu, - RateIncrements: ri, - Weight: wght, + Tag: tag, + ConnectFee: cf, + Price: p, + PricedUnits: pu, + RateIncrements: ri, + Weight: wght, + RoundingMethod: roundingMethod, + RoundingDecimals: rd, } return } diff --git a/rater/simple_serializer.go b/rater/simple_serializer.go index 25c94db06..638a558ce 100644 --- a/rater/simple_serializer.go +++ b/rater/simple_serializer.go @@ -242,6 +242,7 @@ func (at *ActionTrigger) Store() (result string, err error) { result += at.DestinationId + ";" result += at.ActionsId + ";" result += strconv.FormatFloat(at.ThresholdValue, 'f', -1, 64) + ";" + result += at.ThresholdType + ";" result += strconv.FormatFloat(at.Weight, 'f', -1, 64) + ";" result += strconv.FormatBool(at.Executed) return @@ -249,7 +250,7 @@ func (at *ActionTrigger) Store() (result string, err error) { func (at *ActionTrigger) Restore(input string) error { elements := strings.Split(input, ";") - if len(elements) != 8 { + if len(elements) != 9 { return notEnoughElements } at.Id = elements[0] @@ -258,8 +259,9 @@ func (at *ActionTrigger) Restore(input string) error { at.DestinationId = elements[3] at.ActionsId = elements[4] at.ThresholdValue, _ = strconv.ParseFloat(elements[5], 64) - at.Weight, _ = strconv.ParseFloat(elements[6], 64) - at.Executed, _ = strconv.ParseBool(elements[7]) + at.ThresholdType = elements[6] + at.Weight, _ = strconv.ParseFloat(elements[7], 64) + at.Executed, _ = strconv.ParseBool(elements[8]) return nil } diff --git a/rater/simple_serializer_test.go b/rater/simple_serializer_test.go index 4a76a5232..f37e66140 100644 --- a/rater/simple_serializer_test.go +++ b/rater/simple_serializer_test.go @@ -117,12 +117,13 @@ func TestActionTriggerStoreRestore(t *testing.T) { BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100.0, + ThresholdType: "MAX_COUNTER", DestinationId: "NAT", Weight: 10.0, ActionsId: "Commando", } r, err := at.Store() - if err != nil || r != "some_uuid;MONETARY;OUT;NAT;Commando;100;10;false" { + if err != nil || r != "some_uuid;MONETARY;OUT;NAT;Commando;100;MAX_COUNTER;10;false" { t.Errorf("Error serializing action trigger: %v", string(r)) } o := &ActionTrigger{} diff --git a/rater/storage_sql.go b/rater/storage_sql.go index 0459f74f4..c76dca64e 100644 --- a/rater/storage_sql.go +++ b/rater/storage_sql.go @@ -977,8 +977,8 @@ func (self *SQLStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*Act for rows.Next() { var id int var threshold, weight float64 - var tpid, tag, balances_tag, direction, destinations_tag, actions_tag string - if err := rows.Scan(&id, &tpid, &tag, &balances_tag, &direction, &threshold, &destinations_tag, &actions_tag, &weight); err != nil { + var tpid, tag, balances_tag, direction, destinations_tag, actions_tag, thresholdType string + if err := rows.Scan(&id, &tpid, &tag, &balances_tag, &direction, &threshold, &thresholdType, &destinations_tag, &actions_tag, &weight); err != nil { return nil, err } @@ -987,6 +987,7 @@ func (self *SQLStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*Act BalanceId: balances_tag, Direction: direction, ThresholdValue: threshold, + ThresholdType: thresholdType, DestinationId: destinations_tag, ActionsId: actions_tag, Weight: weight, diff --git a/rater/userbalance.go b/rater/userbalance.go index c4f200246..6f1346cad 100644 --- a/rater/userbalance.go +++ b/rater/userbalance.go @@ -21,6 +21,7 @@ package rater import ( "errors" "sort" + "strings" "time" ) @@ -261,19 +262,65 @@ func (ub *UserBalance) executeActionTriggers() { // the next reset (see RESET_TRIGGERS action type) continue } - for _, uc := range ub.UnitCounters { - if uc.BalanceId == at.BalanceId { - if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safty - for _, mb := range uc.MinuteBuckets { - if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { - // run the actions - at.Execute(ub) + if strings.Contains(at.ThresholdType, "COUNTER") { + for _, uc := range ub.UnitCounters { + if uc.BalanceId == at.BalanceId { + if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety + for _, mb := range uc.MinuteBuckets { + if strings.Contains(at.ThresholdType, "MAX") { + if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } else { //MIN + if mb.DestinationId == at.DestinationId && mb.Seconds <= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } + } + } else { + if strings.Contains(at.ThresholdType, "MAX") { + if uc.Units >= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } else { //MIN + if uc.Units <= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } + } + } + } + } else { // BALANCE + for _, b := range ub.BalanceMap[at.BalanceId] { + if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety + for _, mb := range ub.MinuteBuckets { + if strings.Contains(at.ThresholdType, "MAX") { + if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } else { //MIN + if mb.DestinationId == at.DestinationId && mb.Seconds <= at.ThresholdValue { + // run the actions + at.Execute(ub) + } } } } else { - if uc.Units >= at.ThresholdValue { - // run the actions - at.Execute(ub) + if strings.Contains(at.ThresholdType, "MAX") { + if b.Value >= at.ThresholdValue { + // run the actions + at.Execute(ub) + } + } else { //MIN + if b.Value <= at.ThresholdValue { + // run the actions + at.Execute(ub) + } } } } diff --git a/rater/userbalance_test.go b/rater/userbalance_test.go index 87e2f00e7..fad779cfd 100644 --- a/rater/userbalance_test.go +++ b/rater/userbalance_test.go @@ -417,38 +417,51 @@ func TestUserBalanceAddMinutBucketEmpty(t *testing.T) { } } -/* func TestUserBalanceExecuteTriggeredActions(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND] != 110 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND], ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) } // are set to executed ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND] != 110 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND], ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) } // we can reset them ub.resetActionTriggers() ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND] != 120 || ub.MinuteBuckets[0].Seconds != 30 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND], ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.MinuteBuckets[0].Seconds != 30 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) } -}*/ +} + +func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { + ub := &UserBalance{ + Id: "TEST_UB", + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "MIN_COUNTER", ActionsId: "TEST_ACTIONS"}}, + } + ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) + } +} func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB_OREDER", BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS_ORDER"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 {