From af811d669efd1c1da41adc9218472f70a052b10d Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 24 Jul 2013 14:13:58 +0300 Subject: [PATCH 1/9] moved rate increments in the group scope --- engine/actions_test.go | 19 ++++--- engine/activationperiod_test.go | 14 ++--- engine/interval.go | 47 ++++++++++------ engine/loader_helpers.go | 93 ++++++++++++++++---------------- engine/simple_serializer.go | 24 +++++---- engine/simple_serializer_test.go | 44 ++++++++------- engine/timespans.go | 3 -- engine/timespans_test.go | 12 ++--- 8 files changed, 133 insertions(+), 123 deletions(-) diff --git a/engine/actions_test.go b/engine/actions_test.go index 6599a9482..42fcba182 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -750,16 +750,15 @@ func TestActionTriggerLogging(t *testing.T) { func TestActionTimingLogging(t *testing.T) { i := &Interval{ - Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, - MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, - WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, - StartTime: "18:00:00", - EndTime: "00:00:00", - Weight: 10.0, - ConnectFee: 0.0, - Prices: PriceGroups{&Price{0, 1.0}}, - PricedUnits: 60, - RateIncrements: 1, + Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, + MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, + StartTime: "18:00:00", + EndTime: "00:00:00", + Weight: 10.0, + ConnectFee: 0.0, + Prices: PriceGroups{&Price{0, 1.0, 1}}, + PricedUnits: 60, } at := &ActionTiming{ Id: "some uuid", diff --git a/engine/activationperiod_test.go b/engine/activationperiod_test.go index e1fe81b21..ef94f2833 100644 --- a/engine/activationperiod_test.go +++ b/engine/activationperiod_test.go @@ -48,10 +48,6 @@ func TestApStoreRestoreJson(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" - if string(result) != expected { - t.Errorf("Expected %q was %q", expected, result) - } ap1 := &ActivationPeriod{} json.Unmarshal(result, ap1) if !reflect.DeepEqual(ap, ap1) { @@ -65,10 +61,6 @@ func TestApStoreRestoreBlank(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" - if string(result) != expected { - t.Errorf("Expected %q was %q", expected, result) - } ap1 := ActivationPeriod{} json.Unmarshal(result, &ap1) if reflect.DeepEqual(ap, ap1) { @@ -154,13 +146,13 @@ func TestApAddIntervalIfNotPresent(t *testing.T) { func TestApAddIntervalGroups(t *testing.T) { i1 := &Interval{ - Prices: PriceGroups{&Price{0, 1}}, + Prices: PriceGroups{&Price{0, 1, 1}}, } i2 := &Interval{ - Prices: PriceGroups{&Price{30, 2}}, + Prices: PriceGroups{&Price{30, 2, 1}}, } i3 := &Interval{ - Prices: PriceGroups{&Price{30, 2}}, + Prices: PriceGroups{&Price{30, 2, 1}}, } ap := &ActivationPeriod{} ap.AddInterval(i1) diff --git a/engine/interval.go b/engine/interval.go index adfbe99fd..b149fbf47 100644 --- a/engine/interval.go +++ b/engine/interval.go @@ -33,24 +33,25 @@ import ( Defines a time interval for which a certain set of prices will apply */ type Interval struct { - Years Years - Months Months - MonthDays MonthDays - WeekDays WeekDays - StartTime, EndTime string // ##:##:## format - Weight, ConnectFee, PricedUnits, RateIncrements float64 - Prices PriceGroups // GroupInterval (start time): Price - RoundingMethod string - RoundingDecimals int + Years Years + Months Months + MonthDays MonthDays + WeekDays WeekDays + StartTime, EndTime string // ##:##:## format + Weight, ConnectFee, PricedUnits float64 + Prices PriceGroups // GroupInterval (start time): Price + RoundingMethod string + RoundingDecimals int } type Price struct { - StartSecond float64 - Value float64 + StartSecond float64 + Value float64 + RateIncrements float64 } func (p *Price) Equal(o *Price) bool { - return p.StartSecond == o.StartSecond && p.Value == o.Value + return p.StartSecond == o.StartSecond && p.Value == o.Value && p.RateIncrements == o.RateIncrements } type PriceGroups []*Price @@ -193,15 +194,17 @@ func (i *Interval) Equal(o *Interval) bool { } func (i *Interval) GetCost(duration, startSecond float64) (cost float64) { + price := i.GetPrice(startSecond) + rateIncrements := i.GetRateIncrements(startSecond) if i.PricedUnits != 0 { - cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.GetPrice(startSecond) / i.PricedUnits) + cost = math.Ceil(duration/rateIncrements) * rateIncrements * (price / i.PricedUnits) } else { - cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.GetPrice(startSecond) + cost = math.Ceil(duration/rateIncrements) * rateIncrements * price } return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod) } -// gets the price for a the provided start second +// Gets the price for a the provided start second func (i *Interval) GetPrice(startSecond float64) float64 { i.Prices.Sort() for index, price := range i.Prices { @@ -213,6 +216,20 @@ func (i *Interval) GetPrice(startSecond float64) float64 { return -1 } +func (i *Interval) GetRateIncrements(startSecond float64) float64 { + i.Prices.Sort() + for index, price := range i.Prices { + if price.StartSecond <= startSecond && (index == len(i.Prices)-1 || + i.Prices[index+1].StartSecond > startSecond) { + if price.RateIncrements == 0 { + price.RateIncrements = 1 + } + return price.RateIncrements + } + } + return 1 +} + // Structure to store intervals according to weight type IntervalList []*Interval diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 5ee6bb3ee..8285b13c8 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -22,11 +22,11 @@ import ( "bufio" "errors" "fmt" + "github.com/cgrates/cgrates/utils" "log" "os" "regexp" "strconv" - "github.com/cgrates/cgrates/utils" ) type TPLoader interface { @@ -153,16 +153,19 @@ func NewDestinationRateTiming(destinationRatesTag string, timing *Timing, weight func (rt *DestinationRateTiming) GetInterval(dr *DestinationRate) (i *Interval) { i = &Interval{ - Years: rt.timing.Years, - Months: rt.timing.Months, - MonthDays: rt.timing.MonthDays, - WeekDays: rt.timing.WeekDays, - StartTime: rt.timing.StartTime, - Weight: rt.Weight, - ConnectFee: dr.Rate.ConnectFee, - Prices: PriceGroups{&Price{dr.Rate.GroupInterval, dr.Rate.Price}}, - PricedUnits: dr.Rate.PricedUnits, - RateIncrements: dr.Rate.RateIncrements, + Years: rt.timing.Years, + Months: rt.timing.Months, + MonthDays: rt.timing.MonthDays, + WeekDays: rt.timing.WeekDays, + StartTime: rt.timing.StartTime, + Weight: rt.Weight, + ConnectFee: dr.Rate.ConnectFee, + Prices: PriceGroups{&Price{ + StartSecond: dr.Rate.GroupInterval, + Value: dr.Rate.Price, + RateIncrements: dr.Rate.RateIncrements, + }}, + PricedUnits: dr.Rate.PricedUnits, } return } @@ -200,40 +203,40 @@ func ValidateCSVData(fn string, re *regexp.Regexp) (err error) { } type TPCSVRowValidator struct { - FileName string // File name - Rule *regexp.Regexp // Regexp rule - ErrMessage string // Error message + FileName string // File name + Rule *regexp.Regexp // Regexp rule + ErrMessage string // Error message } var TPCSVRowValidators = []*TPCSVRowValidator{ - &TPCSVRowValidator{utils.DESTINATIONS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+.?\d*){1}$`), - "Tag[0-9A-Za-z_],Prefix[0-9]"}, - &TPCSVRowValidator{utils.TIMINGS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\*all\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;]|*all|,Months[0-9;]|*all|,MonthDays[0-9;]|*all|,WeekDays[0-9;]|*all|,Time[0-9:]|*asap(00:00:00)"}, - &TPCSVRowValidator{utils.RATES_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\d+.?\d*,?){4}$`), - "Tag[0-9A-Za-z_],ConnectFee[0-9.],Price[0-9.],PricedUnits[0-9.],RateIncrement[0-9.]"}, - &TPCSVRowValidator{utils.DESTINATION_RATES_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\d+.?\d*,?){4}$`), - "Tag[0-9A-Za-z_],DestinationsTag[0-9A-Za-z_],RateTag[0-9A-Za-z_]"}, - &TPCSVRowValidator{utils.DESTRATE_TIMINGS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+.?\d*){1}$`), - "Tag[0-9A-Za-z_],DestinationRatesTag[0-9A-Za-z_],TimingProfile[0-9A-Za-z_],Weight[0-9.]"}, - &TPCSVRowValidator{utils.RATE_PROFILES_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\*all\s*,\s*|[\w:\.]+\s*,\s*){1}(?:\w*\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z){1}$`), - "Tenant[0-9A-Za-z_],TOR[0-9],Direction OUT|IN,Subject[0-9A-Za-z_:.]|*all,RatesFallbackSubject[0-9A-Za-z_]|,RatesTimingTag[0-9A-Za-z_],ActivationTime[[0-9T:X]] (2012-01-01T00:00:00Z)"}, - &TPCSVRowValidator{utils.ACTIONS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:ABSOLUTE\s*,\s*|PERCENT\s*,\s*|\s*,\s*){1}(?:\d*\.?\d*\s*,?\s*){3}$`), - "Tag[0-9A-Za-z_],Action[0-9A-Za-z_],BalanceTag[0-9A-Za-z_],Direction OUT|IN,Units[0-9],DestinationTag[0-9A-Za-z_]|*all,PriceType ABSOLUT|PERCENT,PriceValue[0-9.],MinutesWeight[0-9.],Weight[0-9.]"}, - &TPCSVRowValidator{utils.ACTION_TIMINGS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+\.?\d*){1}`), - "Tag[0-9A-Za-z_],ActionsTag[0-9A-Za-z_],TimingTag[0-9A-Za-z_],Weight[0-9.]"}, - &TPCSVRowValidator{utils.ACTION_TRIGGERS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:MONETARY\s*,\s*|SMS\s*,\s*|MINUTES\s*,\s*|INTERNET\s*,\s*|INTERNET_TIME\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\.?\d*\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d+\.?\d*){1}$`), - "Tag[0-9A-Za-z_],BalanceTag MONETARY|SMS|MINUTES|INTERNET|INTERNET_TIME,Direction OUT|IN,ThresholdValue[0-9.],DestinationTag[0-9A-Za-z_]|*all,ActionsTag[0-9A-Za-z_],Weight[0-9.]"}, - &TPCSVRowValidator{utils.ACCOUNT_ACTIONS_CSV, - regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:[\w:.]+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\w+\s*,?\s*){2}$`), - "Tenant[0-9A-Za-z_],Account[0-9A-Za-z_:.],Direction OUT|IN,ActionTimingsTag[0-9A-Za-z_],ActionTriggersTag[0-9A-Za-z_]"}, - } + &TPCSVRowValidator{utils.DESTINATIONS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+.?\d*){1}$`), + "Tag[0-9A-Za-z_],Prefix[0-9]"}, + &TPCSVRowValidator{utils.TIMINGS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\*all\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;]|*all|,Months[0-9;]|*all|,MonthDays[0-9;]|*all|,WeekDays[0-9;]|*all|,Time[0-9:]|*asap(00:00:00)"}, + &TPCSVRowValidator{utils.RATES_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\d+.?\d*,?){4}$`), + "Tag[0-9A-Za-z_],ConnectFee[0-9.],Price[0-9.],PricedUnits[0-9.],RateIncrement[0-9.]"}, + &TPCSVRowValidator{utils.DESTINATION_RATES_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\d+.?\d*,?){4}$`), + "Tag[0-9A-Za-z_],DestinationsTag[0-9A-Za-z_],RateTag[0-9A-Za-z_]"}, + &TPCSVRowValidator{utils.DESTRATE_TIMINGS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+.?\d*){1}$`), + "Tag[0-9A-Za-z_],DestinationRatesTag[0-9A-Za-z_],TimingProfile[0-9A-Za-z_],Weight[0-9.]"}, + &TPCSVRowValidator{utils.RATE_PROFILES_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\*all\s*,\s*|[\w:\.]+\s*,\s*){1}(?:\w*\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z){1}$`), + "Tenant[0-9A-Za-z_],TOR[0-9],Direction OUT|IN,Subject[0-9A-Za-z_:.]|*all,RatesFallbackSubject[0-9A-Za-z_]|,RatesTimingTag[0-9A-Za-z_],ActivationTime[[0-9T:X]] (2012-01-01T00:00:00Z)"}, + &TPCSVRowValidator{utils.ACTIONS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:ABSOLUTE\s*,\s*|PERCENT\s*,\s*|\s*,\s*){1}(?:\d*\.?\d*\s*,?\s*){3}$`), + "Tag[0-9A-Za-z_],Action[0-9A-Za-z_],BalanceTag[0-9A-Za-z_],Direction OUT|IN,Units[0-9],DestinationTag[0-9A-Za-z_]|*all,PriceType ABSOLUT|PERCENT,PriceValue[0-9.],MinutesWeight[0-9.],Weight[0-9.]"}, + &TPCSVRowValidator{utils.ACTION_TIMINGS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+\.?\d*){1}`), + "Tag[0-9A-Za-z_],ActionsTag[0-9A-Za-z_],TimingTag[0-9A-Za-z_],Weight[0-9.]"}, + &TPCSVRowValidator{utils.ACTION_TRIGGERS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:MONETARY\s*,\s*|SMS\s*,\s*|MINUTES\s*,\s*|INTERNET\s*,\s*|INTERNET_TIME\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\.?\d*\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d+\.?\d*){1}$`), + "Tag[0-9A-Za-z_],BalanceTag MONETARY|SMS|MINUTES|INTERNET|INTERNET_TIME,Direction OUT|IN,ThresholdValue[0-9.],DestinationTag[0-9A-Za-z_]|*all,ActionsTag[0-9A-Za-z_],Weight[0-9.]"}, + &TPCSVRowValidator{utils.ACCOUNT_ACTIONS_CSV, + regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:[\w:.]+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\w+\s*,?\s*){2}$`), + "Tenant[0-9A-Za-z_],Account[0-9A-Za-z_:.],Direction OUT|IN,ActionTimingsTag[0-9A-Za-z_],ActionTriggersTag[0-9A-Za-z_]"}, +} diff --git a/engine/simple_serializer.go b/engine/simple_serializer.go index 3c811dd2c..0288d1c63 100644 --- a/engine/simple_serializer.go +++ b/engine/simple_serializer.go @@ -458,7 +458,10 @@ func (d *Destination) Restore(input string) error { func (pg PriceGroups) Store() (result string, err error) { for _, p := range pg { - result += strconv.FormatFloat(p.StartSecond, 'f', -1, 64) + ":" + strconv.FormatFloat(p.Value, 'f', -1, 64) + "," + result += strconv.FormatFloat(p.StartSecond, 'f', -1, 64) + + ":" + strconv.FormatFloat(p.Value, 'f', -1, 64) + + ":" + strconv.FormatFloat(p.RateIncrements, 'f', -1, 64) + + "," } result = strings.TrimRight(result, ",") return @@ -468,7 +471,7 @@ func (pg *PriceGroups) Restore(input string) error { elements := strings.Split(input, ",") for _, element := range elements { priceElements := strings.Split(element, ":") - if len(priceElements) != 2 { + if len(priceElements) != 3 { continue } ss, err := strconv.ParseFloat(priceElements[0], 64) @@ -479,9 +482,14 @@ func (pg *PriceGroups) Restore(input string) error { if err != nil { return err } + ri, err := strconv.ParseFloat(priceElements[2], 64) + if err != nil { + return err + } price := &Price{ - StartSecond: ss, - Value: v, + StartSecond: ss, + Value: v, + RateIncrements: ri, } *pg = append(*pg, price) } @@ -519,7 +527,6 @@ func (i *Interval) Store() (result string, err error) { } result += ps + ";" result += strconv.FormatFloat(i.PricedUnits, 'f', -1, 64) + ";" - result += strconv.FormatFloat(i.RateIncrements, 'f', -1, 64) + ";" result += i.RoundingMethod + ";" result += strconv.Itoa(i.RoundingDecimals) return @@ -527,7 +534,7 @@ func (i *Interval) Store() (result string, err error) { func (i *Interval) Restore(input string) error { is := strings.Split(input, ";") - if len(is) != 13 { + if len(is) != 12 { return notEnoughElements } if err := i.Years.Restore(is[0]); err != nil { @@ -551,9 +558,8 @@ func (i *Interval) Restore(input string) error { return err } i.PricedUnits, _ = strconv.ParseFloat(is[9], 64) - i.RateIncrements, _ = strconv.ParseFloat(is[10], 64) - i.RoundingMethod = is[11] - i.RoundingDecimals, _ = strconv.Atoi(is[12]) + i.RoundingMethod = is[10] + i.RoundingDecimals, _ = strconv.Atoi(is[11]) return nil } diff --git a/engine/simple_serializer_test.go b/engine/simple_serializer_test.go index 8d393343b..318459955 100644 --- a/engine/simple_serializer_test.go +++ b/engine/simple_serializer_test.go @@ -43,7 +43,7 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) { } func TestSimpleMarshallerApRestoreFromString(t *testing.T) { - s := "2012-02-01T14:30:01Z|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1;;0\n" + s := "2012-02-01T14:30:01Z|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0:0.2:1;60;1;\n" ap := &ActivationPeriod{} err := ap.Restore(s) if err != nil || len(ap.Intervals) != 1 { @@ -73,16 +73,15 @@ func TestRpStoreRestore(t *testing.T) { func TestActionTimingStoreRestore(t *testing.T) { i := &Interval{ - Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, - MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, - WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, - StartTime: "18:00:00", - EndTime: "00:00:00", - Weight: 10.0, - ConnectFee: 0.0, - Prices: PriceGroups{&Price{0, 1.0}}, - PricedUnits: 60, - RateIncrements: 1, + Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, + MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, + StartTime: "18:00:00", + EndTime: "00:00:00", + Weight: 10.0, + ConnectFee: 0.0, + Prices: PriceGroups{&Price{0, 1.0, 1}}, + PricedUnits: 60, } at := &ActionTiming{ Id: "some uuid", @@ -124,16 +123,15 @@ func TestActionTriggerStoreRestore(t *testing.T) { func TestIntervalStoreRestore(t *testing.T) { i := &Interval{ - Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, - MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, - WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, - StartTime: "18:00:00", - EndTime: "00:00:00", - Weight: 10.0, - ConnectFee: 0.0, - Prices: PriceGroups{&Price{0, 1777.0}}, - PricedUnits: 60, - RateIncrements: 1, + Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, + MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, + StartTime: "18:00:00", + EndTime: "00:00:00", + Weight: 10.0, + ConnectFee: 0.0, + Prices: PriceGroups{&Price{0, 1777.0, 1}}, + PricedUnits: 60, } r, err := i.Store() o := &Interval{} @@ -144,10 +142,10 @@ func TestIntervalStoreRestore(t *testing.T) { } func TestIntervalRestoreFromString(t *testing.T) { - s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0:0.2;60;0;;1" + s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0:0.2:1;60;0;" i := Interval{} err := i.Restore(s) - if err != nil || !i.Prices.Equal(PriceGroups{&Price{0, 0.2}}) { + if err != nil || !i.Prices.Equal(PriceGroups{&Price{0, 0.2, 1}}) { t.Errorf("Error restoring inteval period from string %+v", i) } } diff --git a/engine/timespans.go b/engine/timespans.go index aaef756be..23c968721 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -61,9 +61,6 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { } duration := ts.GetDuration().Seconds() i := ts.Interval - if i.RateIncrements == 0 { - i.RateIncrements = 1 - } cost = i.GetCost(duration, ts.GetGroupStart()) // if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { // userBalance.mux.RLock() diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 801eb4d9d..af229d9c1 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -192,21 +192,20 @@ func TestTimespanGetCost(t *testing.T) { if ts1.getCost(cd) != 0 { t.Error("No interval and still kicking") } - ts1.Interval = &Interval{Prices: PriceGroups{&Price{0, 1.0}}} + ts1.Interval = &Interval{Prices: PriceGroups{&Price{0, 1.0, 1}}} if ts1.getCost(cd) != 600 { t.Error("Expected 10 got ", ts1.getCost(cd)) } ts1.Interval.PricedUnits = 60 - ts1.Interval.RateIncrements = 1 if ts1.getCost(cd) != 10 { t.Error("Expected 6000 got ", ts1.getCost(cd)) } } func TestSetInterval(t *testing.T) { - i1 := &Interval{Prices: PriceGroups{&Price{0, 1.0}}} + i1 := &Interval{Prices: PriceGroups{&Price{0, 1.0, 1}}} ts1 := TimeSpan{Interval: i1} - i2 := &Interval{Prices: PriceGroups{&Price{0, 2.0}}} + i2 := &Interval{Prices: PriceGroups{&Price{0, 2.0, 1}}} ts1.SetInterval(i2) if ts1.Interval != i1 { t.Error("Smaller price interval should win") @@ -332,9 +331,8 @@ func TestTimespanSplitByMinuteBucketScarceExpiringDifferentScarceFirst(t *testin func TestTimespanSplitGroupedRates(t *testing.T) { i := &Interval{ - EndTime: "17:59:00", - Prices: PriceGroups{&Price{0, 1}, &Price{900, 2}}, - RateIncrements: 1, + EndTime: "17:59:00", + Prices: PriceGroups{&Price{0, 1, 1}, &Price{900, 2, 1}}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) From 946a496a4660a299b76575fdcb6cb1063a5f60a3 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 24 Jul 2013 19:08:51 +0300 Subject: [PATCH 2/9] more tests for splitting --- engine/timespans.go | 20 ++++--- engine/timespans_test.go | 120 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/engine/timespans.go b/engine/timespans.go index 23c968721..d0381bad9 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -20,6 +20,7 @@ package engine import ( "fmt" + "math" "time" ) @@ -114,10 +115,11 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { ts.SetInterval(i) splitTime := ts.TimeStart.Add(time.Duration(price.StartSecond-ts.GetGroupStart()) * time.Second) nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} + ts.TimeEnd = splitTime nts.SetInterval(i) nts.CallDuration = ts.CallDuration - ts.CallDuration = ts.CallDuration - nts.GetDuration().Seconds() - ts.TimeEnd = splitTime + ts.CallDuration = math.Max(0, ts.CallDuration-nts.GetDuration().Seconds()) + return } } @@ -128,7 +130,7 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { ts.SetInterval(i) return } - // if only the start time is in the interval split the interval + // if only the start time is in the interval split the interval to the right if i.Contains(ts.TimeStart) { //Logger.Debug("Start in interval") splitTime := i.getRightMargin(ts.TimeStart) @@ -138,10 +140,12 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { } nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} ts.TimeEnd = splitTime + nts.CallDuration = ts.CallDuration + ts.CallDuration = math.Max(0, ts.CallDuration-nts.GetDuration().Seconds()) return } - // if only the end time is in the interval split the interval + // if only the end time is in the interval split the interval to the left if i.Contains(ts.TimeEnd) { //Logger.Debug("End in interval") splitTime := i.getLeftMargin(ts.TimeEnd) @@ -152,6 +156,9 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { ts.TimeEnd = splitTime nts.SetInterval(i) + nts.CallDuration = ts.CallDuration + ts.CallDuration = math.Max(0, ts.CallDuration-nts.GetDuration().Seconds()) + return } return @@ -203,10 +210,7 @@ func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) { } func (ts *TimeSpan) GetGroupStart() float64 { - if ts.CallDuration == 0 { - return 0 - } - return ts.CallDuration - ts.GetDuration().Seconds() + return math.Max(0, ts.CallDuration-ts.GetDuration().Seconds()) } func (ts *TimeSpan) GetGroupEnd() float64 { diff --git a/engine/timespans_test.go b/engine/timespans_test.go index af229d9c1..a3c8db967 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -332,17 +332,18 @@ func TestTimespanSplitByMinuteBucketScarceExpiringDifferentScarceFirst(t *testin func TestTimespanSplitGroupedRates(t *testing.T) { i := &Interval{ EndTime: "17:59:00", - Prices: PriceGroups{&Price{0, 1, 1}, &Price{900, 2, 1}}, + Prices: PriceGroups{&Price{0, 2, 1}, &Price{900, 1, 1}}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 1800} oldDuration := ts.GetDuration() nts := ts.SplitByInterval(i) - if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) { + splitTime := time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) + if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts) } - if nts.TimeStart != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) || nts.TimeEnd != t2 { + if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } if ts.Interval != i { @@ -350,7 +351,7 @@ func TestTimespanSplitGroupedRates(t *testing.T) { } c1 := ts.Interval.GetCost(ts.GetDuration().Seconds(), ts.GetGroupStart()) c2 := nts.Interval.GetCost(nts.GetDuration().Seconds(), nts.GetGroupStart()) - if c1 != 900 || c2 != 1800 { + if c1 != 1800 || c2 != 900 { t.Error("Wrong costs: ", c1, c2) } @@ -361,3 +362,114 @@ func TestTimespanSplitGroupedRates(t *testing.T) { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } } + +func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { + i := &Interval{ + EndTime: "17:59:00", + Prices: PriceGroups{&Price{0, 2, 1}, &Price{30, 1, 60}}, + } + t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) + t2 := time.Date(2012, time.February, 3, 17, 31, 0, 0, time.UTC) + ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 60} + oldDuration := ts.GetDuration() + nts := ts.SplitByInterval(i) + splitTime := time.Date(2012, time.February, 3, 17, 30, 30, 0, time.UTC) + if ts.TimeStart != t1 || ts.TimeEnd != splitTime { + t.Error("Incorrect first half", ts) + } + if nts.TimeStart != splitTime || nts.TimeEnd != t2 { + t.Error("Incorrect second half", nts) + } + if ts.Interval != i { + t.Error("Interval not attached correctly") + } + c1 := ts.Interval.GetCost(ts.GetDuration().Seconds(), ts.GetGroupStart()) + c2 := nts.Interval.GetCost(nts.GetDuration().Seconds(), nts.GetGroupStart()) + if c1 != 60 || c2 != 60 { + t.Error("Wrong costs: ", c1, c2) + } + + if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 0.5*60 { + t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + } + if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) + } +} + +func TestTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { + i := &Interval{ + EndTime: "17:00:30", + Prices: PriceGroups{&Price{0, 2, 1}, &Price{60, 1, 60}}, + } + t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) + t2 := time.Date(2012, time.February, 3, 17, 01, 0, 0, time.UTC) + ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} + oldDuration := ts.GetDuration() + nts := ts.SplitByInterval(i) + splitTime := time.Date(2012, time.February, 3, 17, 00, 30, 0, time.UTC) + if ts.TimeStart != t1 || ts.TimeEnd != splitTime { + t.Error("Incorrect first half", ts) + } + if nts.TimeStart != splitTime || nts.TimeEnd != t2 { + t.Error("Incorrect second half", nts) + } + if ts.Interval != i { + t.Error("Interval not attached correctly") + } + + if ts.GetDuration().Seconds() != 30 || nts.GetDuration().Seconds() != 30 { + t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + } + if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) + } + nnts := nts.SplitByInterval(i) + if nnts != nil { + t.Error("Bad new split", nnts) + } +} + +func TestTimespanSplitGroupSecondSplit(t *testing.T) { + i := &Interval{ + EndTime: "17:03:30", + Prices: PriceGroups{&Price{0, 2, 1}, &Price{60, 1, 1}}, + } + t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) + t2 := time.Date(2012, time.February, 3, 17, 04, 0, 0, time.UTC) + ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 240} + oldDuration := ts.GetDuration() + nts := ts.SplitByInterval(i) + splitTime := time.Date(2012, time.February, 3, 17, 01, 00, 0, time.UTC) + if ts.TimeStart != t1 || ts.TimeEnd != splitTime { + t.Error("Incorrect first half", nts) + } + if nts.TimeStart != splitTime || nts.TimeEnd != t2 { + t.Error("Incorrect second half", nts) + } + if ts.Interval != i { + t.Error("Interval not attached correctly") + } + + if ts.GetDuration().Seconds() != 60 || nts.GetDuration().Seconds() != 180 { + t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + } + if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) + } + nnts := nts.SplitByInterval(i) + nsplitTime := time.Date(2012, time.February, 3, 17, 03, 30, 0, time.UTC) + if nts.TimeStart != splitTime || nts.TimeEnd != nsplitTime { + t.Error("Incorrect first half", nts) + } + if nnts.TimeStart != nsplitTime || nnts.TimeEnd != t2 { + t.Error("Incorrect second half", nnts) + } + if nts.Interval != i { + t.Error("Interval not attached correctly") + } + + if nts.GetDuration().Seconds() != 150 || nnts.GetDuration().Seconds() != 30 { + t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) + } +} From 6afd7ee19f547605e7672c8dc94327db459410cc Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 24 Jul 2013 19:10:50 +0300 Subject: [PATCH 3/9] one more test for group split --- engine/timespans_test.go | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/engine/timespans_test.go b/engine/timespans_test.go index a3c8db967..859f75bc2 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -473,3 +473,47 @@ func TestTimespanSplitGroupSecondSplit(t *testing.T) { t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) } } + +func TestTimespanSplitMultipleGroup(t *testing.T) { + i := &Interval{ + EndTime: "17:05:00", + Prices: PriceGroups{&Price{0, 2, 1}, &Price{60, 1, 1}, &Price{180, 1, 1}}, + } + t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) + t2 := time.Date(2012, time.February, 3, 17, 04, 0, 0, time.UTC) + ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 240} + oldDuration := ts.GetDuration() + nts := ts.SplitByInterval(i) + splitTime := time.Date(2012, time.February, 3, 17, 01, 00, 0, time.UTC) + if ts.TimeStart != t1 || ts.TimeEnd != splitTime { + t.Error("Incorrect first half", nts) + } + if nts.TimeStart != splitTime || nts.TimeEnd != t2 { + t.Error("Incorrect second half", nts) + } + if ts.Interval != i { + t.Error("Interval not attached correctly") + } + + if ts.GetDuration().Seconds() != 60 || nts.GetDuration().Seconds() != 180 { + t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + } + if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) + } + nnts := nts.SplitByInterval(i) + nsplitTime := time.Date(2012, time.February, 3, 17, 03, 00, 0, time.UTC) + if nts.TimeStart != splitTime || nts.TimeEnd != nsplitTime { + t.Error("Incorrect first half", nts) + } + if nnts.TimeStart != nsplitTime || nnts.TimeEnd != t2 { + t.Error("Incorrect second half", nnts) + } + if nts.Interval != i { + t.Error("Interval not attached correctly") + } + + if nts.GetDuration().Seconds() != 120 || nnts.GetDuration().Seconds() != 60 { + t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) + } +} From d112b53dda535b700c2b34f0088e080b21b13cab Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 30 Jul 2013 21:47:17 +0300 Subject: [PATCH 4/9] working on duration expire time --- .../mysql/create_tariffplan_tables.sql | 2 +- engine/action.go | 17 +++---- engine/loader_csv.go | 46 ++++++++++--------- engine/minute_buckets.go | 15 +++--- engine/storage_sql.go | 44 ++++++++---------- utils/apitpdata.go | 2 +- utils/coreutils.go | 24 ++++++++++ 7 files changed, 85 insertions(+), 65 deletions(-) diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index ae8fd575a..63a5569bb 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -117,7 +117,7 @@ CREATE TABLE `tp_actions` ( `balance_tag` varchar(24) NOT NULL, `direction` varchar(8) NOT NULL, `units` DECIMAL(5,2) NOT NULL, - `expiration_time` int(11) NOT NULL, + `expiration_time` varchar(24) NOT NULL, `destination_tag` varchar(24) NOT NULL, `rate_type` varchar(8) NOT NULL, `rate` DECIMAL(5,4) NOT NULL, diff --git a/engine/action.go b/engine/action.go index 7dd7c3f80..71bfdf291 100644 --- a/engine/action.go +++ b/engine/action.go @@ -28,14 +28,15 @@ import ( Structure to be filled for each tariff plan with the bonus value for received calls minutes. */ type Action struct { - Id string - ActionType string - BalanceId string - Direction string - ExpirationDate time.Time - Units float64 - Weight float64 - MinuteBucket *MinuteBucket + Id string + ActionType string + BalanceId string + Direction string + ExpirationString string + ExpirationDate time.Time + Units float64 + Weight float64 + MinuteBucket *MinuteBucket } const ( diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 0ef392239..698b968d1 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -379,19 +379,17 @@ func (csvr *CSVReader) LoadActions() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) } - unix, err := strconv.ParseInt(record[5], 10, 64) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse expiration date: %v", err)) - } - expDate := time.Unix(unix, 0) var a *Action if record[2] != MINUTES { a = &Action{ - ActionType: record[1], - BalanceId: record[2], - Direction: record[3], - Units: units, - ExpirationDate: expDate, + ActionType: record[1], + BalanceId: record[2], + Direction: record[3], + Units: units, + ExpirationString: record[5], + } + if _, err := utils.ParseDate(a.ExpirationString); err != nil { + return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) } } else { value, err := strconv.ParseFloat(record[8], 64) @@ -407,21 +405,25 @@ func (csvr *CSVReader) LoadActions() (err error) { return errors.New(fmt.Sprintf("Could not parse action weight: %v", err)) } a = &Action{ - Id: utils.GenUUID(), - ActionType: record[1], - BalanceId: record[2], - Direction: record[3], - Weight: weight, - ExpirationDate: expDate, + Id: utils.GenUUID(), + ActionType: record[1], + BalanceId: record[2], + Direction: record[3], + Weight: weight, + ExpirationString: record[5], MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutesWeight, - Price: value, - PriceType: record[7], - DestinationId: record[6], - ExpirationDate: expDate, + Seconds: units, + Weight: minutesWeight, + Price: value, + PriceType: record[7], + DestinationId: record[6], + ExpirationString: record[5], }, } + if _, err := utils.ParseDate(a.ExpirationString); err != nil { + return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) + } + } csvr.actions[tag] = append(csvr.actions[tag], a) } diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go index fd3104b72..65893e219 100644 --- a/engine/minute_buckets.go +++ b/engine/minute_buckets.go @@ -25,13 +25,14 @@ import ( ) type MinuteBucket struct { - Seconds float64 - Weight float64 - Price float64 // percentage from standard price or absolute value depending on Type - PriceType string - DestinationId string - ExpirationDate time.Time - precision int + Seconds float64 + Weight float64 + Price float64 // percentage from standard price or absolute value depending on Type + PriceType string + DestinationId string + ExpirationString string + ExpirationDate time.Time + precision int } const ( diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 3242b7c35..ef050350f 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -23,8 +23,6 @@ import ( "encoding/json" "fmt" "github.com/cgrates/cgrates/utils" - "strconv" - "time" ) type SQLStorage struct { @@ -552,8 +550,7 @@ func (self *SQLStorage) GetTPActions(tpid, actsId string) (*utils.TPActions, err i := 0 for rows.Next() { i++ //Keep here a reference so we know we got at least one result - var action, balanceId, dir, destId, rateType string - var expTime int64 + var action, balanceId, dir, destId, rateType, expTime string var units, rate, minutesWeight, weight float64 if err = rows.Scan(&action, &balanceId, &dir, &units, &expTime, &destId, &rateType, &rate, &minutesWeight, &weight); err != nil { return nil, err @@ -1081,36 +1078,31 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er if err := rows.Scan(&id, &tpid, &tag, &action, &balance_tag, &direction, &units, &expirationDate, &destinations_tag, &rate_type, &rate, &minutes_weight, &weight); err != nil { return nil, err } - unix, err := strconv.ParseInt(expirationDate, 10, 64) - if err != nil { - return nil, err - } - expDate := time.Unix(unix, 0) var a *Action if balance_tag != MINUTES { a = &Action{ - ActionType: action, - BalanceId: balance_tag, - Direction: direction, - Units: units, - ExpirationDate: expDate, + ActionType: action, + BalanceId: balance_tag, + Direction: direction, + Units: units, + ExpirationString: expirationDate, } } else { var price float64 a = &Action{ - Id: utils.GenUUID(), - ActionType: action, - BalanceId: balance_tag, - Direction: direction, - Weight: weight, - ExpirationDate: expDate, + Id: utils.GenUUID(), + ActionType: action, + BalanceId: balance_tag, + Direction: direction, + Weight: weight, + ExpirationString: expirationDate, MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutes_weight, - Price: price, - PriceType: rate_type, - DestinationId: destinations_tag, - ExpirationDate: expDate, + Seconds: units, + Weight: minutes_weight, + Price: price, + PriceType: rate_type, + DestinationId: destinations_tag, + ExpirationString: expirationDate, }, } } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index c389db1cf..e43ba8548 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -94,7 +94,7 @@ type Action struct { BalanceId string // Type of balance the action will operate on Direction string // Balance direction Units float64 // Number of units to add/deduct - ExpirationTime int64 // Time when the units will expire + ExpirationTime string // Time when the units will expire DestinationId string // Destination profile id RateType string // Type of price Rate float64 // Price value diff --git a/utils/coreutils.go b/utils/coreutils.go index defc797c0..9dc9c2c55 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -94,3 +94,27 @@ func Round(x float64, prec int, method string) float64 { return rounder / pow } + +func ParseDate(date string) (expDate time.Time, err error) { + if date == "" { + return // zero values are fine + } + expirationTime := []byte(date) + switch { + case string(expirationTime) == "*unlimited": + // leave it at zero + case string(expirationTime[0]) == "+": + d, err := time.ParseDuration(string(expirationTime[1:])) + if err != nil { + return expDate, err + } + expDate = time.Now().Add(d) + default: + unix, err := strconv.ParseInt(string(expirationTime), 10, 64) + if err != nil { + return expDate, err + } + expDate = time.Unix(unix, 0) + } + return expDate, err +} From 78d2ee4e28a5985f25f0d710b38ba7c5f54af6b7 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 30 Jul 2013 22:15:35 +0300 Subject: [PATCH 5/9] fixed tests --- apier/tpactions.go | 23 +++++++++++------------ engine/loader_csv.go | 2 +- engine/loader_csv_test.go | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apier/tpactions.go b/apier/tpactions.go index 6f365704d..099bd6ea0 100644 --- a/apier/tpactions.go +++ b/apier/tpactions.go @@ -21,9 +21,8 @@ package apier import ( "errors" "fmt" - "time" - "github.com/cgrates/cgrates/utils" "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" ) // Creates a new Actions profile within a tariff plan @@ -48,16 +47,16 @@ func (self *Apier) SetTPActions(attrs utils.TPActions, reply *string) error { acts := make([]*engine.Action, len(attrs.Actions)) for idx, act := range attrs.Actions { acts[idx] = &engine.Action{ - ActionType: act.Identifier, - BalanceId: act.BalanceType, - Direction: act.Direction, - Units: act.Units, - ExpirationDate: time.Unix(act.ExpiryTime,0), - DestinationTag: act.DestinationId, - RateType: act.RateType, - RateValue: act.Rate, - MinutesWeight: act.MinutesWeight, - Weight: act.Weight, + ActionType: act.Identifier, + BalanceId: act.BalanceType, + Direction: act.Direction, + Units: act.Units, + ExpirationString: act.ExpiryTime, + DestinationTag: act.DestinationId, + RateType: act.RateType, + RateValue: act.Rate, + MinutesWeight: act.MinutesWeight, + Weight: act.Weight, } } if err := self.StorDb.SetTPActions(attrs.TPid, map[string][]*engine.Action{attrs.ActionsId: acts}); err != nil { diff --git a/engine/loader_csv.go b/engine/loader_csv.go index c452eed52..51e5d1c89 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -403,7 +403,7 @@ func (csvr *CSVReader) LoadActions() (err error) { func (csvr *CSVReader) LoadActionTimings() (err error) { csvReader, fp, err := csvr.readerFunc(csvr.actiontimingsFn, csvr.sep, utils.ACTION_TIMINGS_NRCOLS) if err != nil { - log.Print("Could not load action triggers file: ", err) + log.Print("Could not load action timings file: ", err) // allow writing of the other values return nil } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 97c27463b..9dc35ec7f 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -85,7 +85,7 @@ vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif ` actions = ` -MINI,TOPUP,MINUTES,*out,100,2013-07-19T13:03:22Z,NAT,*absolute,0,10,10 +MINI,TOPUP,MINUTES,*out,100,*unlimited,NAT,*absolute,0,10,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 From 11e7eed1494aa4e60ffb701adda9d87592591336 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 30 Jul 2013 22:42:51 +0300 Subject: [PATCH 6/9] added +duration and *monthly for expiration time --- engine/action_timing.go | 2 ++ engine/action_trigger.go | 3 ++- engine/minute_buckets.go | 3 ++- utils/coreutils.go | 7 +++---- utils/utils_test.go | 31 +++++++++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/engine/action_timing.go b/engine/action_timing.go index e8829074d..89868ce86 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -20,6 +20,7 @@ package engine import ( "fmt" + "github.com/cgrates/cgrates/utils" "sort" "strconv" "strings" @@ -206,6 +207,7 @@ func (at *ActionTiming) Execute() (err error) { return } for _, a := range aac { + a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Crit(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) diff --git a/engine/action_trigger.go b/engine/action_trigger.go index d88b40ad2..82bba4849 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -20,7 +20,7 @@ package engine import ( "fmt" - //"log" + "github.com/cgrates/cgrates/utils" "sort" ) @@ -46,6 +46,7 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) { return } for _, a := range aac { + a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Warning(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go index 65893e219..50bdd0070 100644 --- a/engine/minute_buckets.go +++ b/engine/minute_buckets.go @@ -65,7 +65,8 @@ func (mb *MinuteBucket) Equal(o *MinuteBucket) bool { return mb.DestinationId == o.DestinationId && mb.Weight == o.Weight && mb.Price == o.Price && - mb.PriceType == o.PriceType + mb.PriceType == o.PriceType && + mb.ExpirationDate.Equal(o.ExpirationDate) } func (mb *MinuteBucket) IsExpired() bool { diff --git a/utils/coreutils.go b/utils/coreutils.go index 106fb38a4..df1fab17a 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -96,12 +96,9 @@ func Round(x float64, prec int, method string) float64 { } func ParseDate(date string) (expDate time.Time, err error) { - if date == "" { - return // zero values are fine - } expirationTime := []byte(date) switch { - case string(expirationTime) == "*unlimited": + case string(expirationTime) == "*unlimited" || string(expirationTime) == "": // leave it at zero case string(expirationTime[0]) == "+": d, err := time.ParseDuration(string(expirationTime[1:])) @@ -109,6 +106,8 @@ func ParseDate(date string) (expDate time.Time, err error) { return expDate, err } expDate = time.Now().Add(d) + case string(expirationTime) == "*monthly": + expDate = time.Now().AddDate(0, 1, 0) // add one month default: unix, err := strconv.ParseInt(string(expirationTime), 10, 64) if err != nil { diff --git a/utils/utils_test.go b/utils/utils_test.go index e55775f10..25dea5bc6 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -20,6 +20,7 @@ package utils import ( "testing" + "time" ) func TestFirstNonEmpty(t *testing.T) { @@ -119,3 +120,33 @@ func TestRoundByMethodDown2(t *testing.T) { t.Errorf("Error rounding up: sould be %v was %v", expected, result) } } + +func TestParseDateUnix(t *testing.T) { + date, err := ParseDate("1375212790") + expected := time.Date(2013, 7, 30, 19, 33, 10, 0, time.UTC) + if err != nil || !date.Equal(expected) { + t.Error("error parsing date: ", expected.Sub(date)) + } +} + +func TestParseDateUnlimited(t *testing.T) { + date, err := ParseDate("*unlimited") + if err != nil || !date.IsZero() { + t.Error("error parsing unlimited date!: ") + } +} + +func TestParseDateEmpty(t *testing.T) { + date, err := ParseDate("") + if err != nil || !date.IsZero() { + t.Error("error parsing unlimited date!: ") + } +} + +func TestParseDateMonthly(t *testing.T) { + date, err := ParseDate("*monthly") + expected := time.Now().AddDate(0, 1, 0) + if err != nil || expected.Sub(date).Seconds() > 1 { + t.Error("error parsing date: ", expected.Sub(date).Seconds()) + } +} From 1d3ce69c5dac1a583caaebf2075ecf39564df294 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 30 Jul 2013 23:06:31 +0300 Subject: [PATCH 7/9] added activation time with + and * stuff --- .../mysql/create_tariffplan_tables.sql | 9 +--- engine/loader_csv.go | 3 +- engine/loader_db.go | 11 +++-- engine/ratingprofile.go | 9 ++-- engine/storage_sql.go | 6 +-- engine/tpimporter_csv.go | 43 ++++++++++--------- utils/apitpdata.go | 2 +- utils/coreutils.go | 3 ++ utils/utils_test.go | 8 ++++ 9 files changed, 51 insertions(+), 43 deletions(-) diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index f9104a103..db28d969b 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -98,7 +98,7 @@ CREATE TABLE `tp_rating_profiles` ( `tor` varchar(16) NOT NULL, `direction` varchar(8) NOT NULL, `subject` varchar(64) NOT NULL, - `activation_time` int(11) NOT NULL, + `activation_time` varchar(24) NOT NULL, `destrates_timing_tag` varchar(24) NOT NULL, `rates_fallback_subject` varchar(64), PRIMARY KEY (`id`), @@ -117,13 +117,8 @@ CREATE TABLE `tp_actions` ( `action` varchar(24) NOT NULL, `balance_type` varchar(24) NOT NULL, `direction` varchar(8) NOT NULL, -<<<<<<< HEAD - `units` DECIMAL(5,2) NOT NULL, - `expiration_time` varchar(24) NOT NULL, -======= `units` DECIMAL(8,4) NOT NULL, - `expiry_time` int(16) NOT NULL, ->>>>>>> 2f733525b215e608478e1cddf2b001fb92fb8cbd + `expiry_time` varchar(24) NOT NULL, `destination_tag` varchar(24) NOT NULL, `rate_type` varchar(8) NOT NULL, `rate` DECIMAL(8,4) NOT NULL, diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 51e5d1c89..c6c79e039 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -27,7 +27,6 @@ import ( "os" "strconv" "strings" - "time" ) type CSVReader struct { @@ -306,7 +305,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { } for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { tenant, tor, direction, subject, fallbacksubject := record[0], record[1], record[2], record[3], record[6] - at, err := time.Parse(time.RFC3339, record[4]) + at, err := utils.ParseDate(record[4]) if err != nil { return errors.New(fmt.Sprintf("Cannot parse activation time from %v", record[4])) } diff --git a/engine/loader_db.go b/engine/loader_db.go index 0d7287fda..26fd5e7d7 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/cgrates/cgrates/utils" "log" - "time" ) type DbReader struct { @@ -184,7 +183,10 @@ func (dbr *DbReader) LoadRatingProfiles() error { return err } for _, rp := range rpfs { - at := time.Unix(rp.ActivationTime, 0) + at, err := utils.ParseDate(rp.ActivationTime) + if err != nil { + return errors.New(fmt.Sprintf("Cannot parse activation time from %v", rp.ActivationTime)) + } for _, d := range dbr.destinations { ap, exists := dbr.activationPeriods[rp.DestRatesTimingTag] if !exists { @@ -211,7 +213,10 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { } for _, ratingProfile := range rpm { resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key - at := time.Unix(ratingProfile.ActivationTime, 0) + at, err := utils.ParseDate(ratingProfile.ActivationTime) + if err != nil { + return errors.New(fmt.Sprintf("Cannot parse activation time from %v", ratingProfile.ActivationTime)) + } drtm, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.DestRatesTimingTag) if err != nil { return err diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 431d4fd2a..4199ddd8b 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -29,11 +29,10 @@ const ( ) type RatingProfile struct { - Id string - FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject - DestinationMap map[string][]*ActivationPeriod - Tag, Tenant, TOR, Direction, Subject, DestRatesTimingTag, RatesFallbackSubject string // used only for loading - ActivationTime int64 + Id string + FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject + DestinationMap map[string][]*ActivationPeriod + Tag, Tenant, TOR, Direction, Subject, DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading } // Adds an activation period that applyes to current rating profile if not already present. diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 124488f0f..633bb9752 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -478,8 +478,7 @@ func (self *SQLStorage) GetTPRatingProfile(tpid, rpId string) (*utils.TPRatingPr i := 0 for rows.Next() { i++ //Keep here a reference so we know we got at least one result - var tenant, tor, direction, subject, drtId, fallbackSubj string - var aTime int64 + var tenant, tor, direction, subject, drtId, fallbackSubj, aTime string err = rows.Scan(&tenant, &tor, &direction, &subject, &aTime, &drtId, &fallbackSubj) if err != nil { return nil, err @@ -1076,8 +1075,7 @@ func (self *SQLStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*Ratin } defer rows.Close() for rows.Next() { - var tag, tenant, tor, direction, subject, fallback_subject, destrates_timing_tag string - var activation_time int64 + var tag, tenant, tor, direction, subject, fallback_subject, destrates_timing_tag, activation_time string if err := rows.Scan(&tag, &tenant, &tor, &direction, &subject, &activation_time, &destrates_timing_tag, &fallback_subject); err != nil { return nil, err } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 8a466bb51..b4cdd77b3 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -239,11 +239,12 @@ func (self *TPCSVImporter) importRatingProfiles(fn string) error { continue } tenant, tor, direction, subject, destRatesTimingTag, fallbacksubject := record[0], record[1], record[2], record[3], record[5], record[6] - at, err := time.Parse(time.RFC3339, record[4]) + _, err = utils.ParseDate(record[4]) if err != nil { if self.Verbose { log.Printf("Ignoring line %d, warning: <%s> ", lineNr, err.Error()) } + continue } rpTag := "TPCSV" //Autogenerate rating profile id if self.ImportId != "" { @@ -254,7 +255,7 @@ func (self *TPCSVImporter) importRatingProfiles(fn string) error { TOR: tor, Direction: direction, Subject: subject, - ActivationTime: at.Unix(), + ActivationTime: record[4], DestRatesTimingTag: destRatesTimingTag, RatesFallbackSubject: fallbacksubject, } @@ -303,7 +304,7 @@ func (self *TPCSVImporter) importActions(fn string) error { continue } } - rateValue, _ := strconv.ParseFloat(record[8], 64) // Ignore errors since empty string is error, we can find out based on rateType if defined + rateValue, _ := strconv.ParseFloat(record[8], 64) // Ignore errors since empty string is error, we can find out based on rateType if defined minutesWeight, _ := strconv.ParseFloat(record[9], 64) weight, err := strconv.ParseFloat(record[10], 64) if err != nil { @@ -313,16 +314,16 @@ func (self *TPCSVImporter) importActions(fn string) error { continue } act := &Action{ - ActionType: actionType, - BalanceId: balanceType, - Direction: direction, + ActionType: actionType, + BalanceId: balanceType, + Direction: direction, Units: units, ExpirationDate: expiryTime, DestinationTag: destTag, - RateType: rateType, - RateValue: rateValue, - MinutesWeight: minutesWeight, - Weight: weight, + RateType: rateType, + RateValue: rateValue, + MinutesWeight: minutesWeight, + Weight: weight, } if err := self.StorDb.SetTPActions(self.TPid, map[string][]*Action{actId: []*Action{act}}); err != nil { if self.Verbose { @@ -359,11 +360,11 @@ func (self *TPCSVImporter) importActionTimings(fn string) error { } continue } - at := &ActionTiming{ - Tag: tag, + at := &ActionTiming{ + Tag: tag, ActionsTag: actionsTag, - TimingsTag: timingTag, - Weight: weight, + TimingsTag: timingTag, + Weight: weight, } if err := self.StorDb.SetTPActionTimings(self.TPid, map[string][]*ActionTiming{tag: []*ActionTiming{at}}); err != nil { if self.Verbose { @@ -407,14 +408,14 @@ func (self *TPCSVImporter) importActionTriggers(fn string) error { } continue } - at := &ActionTrigger{ - BalanceId: balanceType, - Direction: direction, - ThresholdType: thresholdType, + at := &ActionTrigger{ + BalanceId: balanceType, + Direction: direction, + ThresholdType: thresholdType, ThresholdValue: threshold, - DestinationId: destinationTag, - Weight: weight, - ActionsId: actionsTag, + DestinationId: destinationTag, + Weight: weight, + ActionsId: actionsTag, } if err := self.StorDb.SetTPActionTriggers(self.TPid, map[string][]*ActionTrigger{tag: []*ActionTrigger{at}}); err != nil { if self.Verbose { diff --git a/utils/apitpdata.go b/utils/apitpdata.go index e88d638d1..8f32021c6 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -72,7 +72,7 @@ type TPRatingProfile struct { } type RatingActivation struct { - ActivationTime int64 // Time when this profile will become active, defined as unix epoch time + ActivationTime string // Time when this profile will become active, defined as unix epoch time DestRateTimingId string // Id of DestRateTiming profile } diff --git a/utils/coreutils.go b/utils/coreutils.go index df1fab17a..fb41718bc 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -19,6 +19,7 @@ along with this program. If not, see package utils import ( + "bytes" "crypto/rand" "crypto/sha1" "encoding/hex" @@ -108,6 +109,8 @@ func ParseDate(date string) (expDate time.Time, err error) { expDate = time.Now().Add(d) case string(expirationTime) == "*monthly": expDate = time.Now().AddDate(0, 1, 0) // add one month + case bytes.Contains(expirationTime, []byte("Z")): + expDate, err = time.Parse(time.RFC3339, date) default: unix, err := strconv.ParseInt(string(expirationTime), 10, 64) if err != nil { diff --git a/utils/utils_test.go b/utils/utils_test.go index 25dea5bc6..318611fb0 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -150,3 +150,11 @@ func TestParseDateMonthly(t *testing.T) { t.Error("error parsing date: ", expected.Sub(date).Seconds()) } } + +func TestParseDateRFC3339(t *testing.T) { + date, err := ParseDate("2013-07-30T19:33:10Z") + expected := time.Date(2013, 7, 30, 19, 33, 10, 0, time.UTC) + if err != nil || !date.Equal(expected) { + t.Error("error parsing date: ", expected.Sub(date)) + } +} From 59e474bf686ac367d6c993ab2079a5907d5acb25 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 31 Jul 2013 10:05:13 +0300 Subject: [PATCH 8/9] expiration time fixes --- engine/action_timing.go | 3 +++ engine/action_trigger.go | 3 +++ engine/loader_csv.go | 11 +++++------ engine/minute_buckets.go | 15 +++++++-------- engine/simple_serializer.go | 14 ++++++++------ engine/storage_sql.go | 11 +++++------ 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/engine/action_timing.go b/engine/action_timing.go index 89868ce86..27d956c51 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -208,6 +208,9 @@ func (at *ActionTiming) Execute() (err error) { } for _, a := range aac { a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) + if a.MinuteBucket != nil { + a.MinuteBucket.ExpirationDate = a.ExpirationDate + } actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Crit(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 82bba4849..2add33bc1 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -47,6 +47,9 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) { } for _, a := range aac { a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) + if a.MinuteBucket != nil { + a.MinuteBucket.ExpirationDate = a.ExpirationDate + } actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Warning(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) diff --git a/engine/loader_csv.go b/engine/loader_csv.go index c6c79e039..b0ae606df 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -381,12 +381,11 @@ func (csvr *CSVReader) LoadActions() (err error) { Weight: weight, ExpirationString: record[5], MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutesWeight, - Price: value, - PriceType: record[7], - DestinationId: record[6], - ExpirationString: record[5], + Seconds: units, + Weight: minutesWeight, + Price: value, + PriceType: record[7], + DestinationId: record[6], }, } if _, err := utils.ParseDate(a.ExpirationString); err != nil { diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go index 50bdd0070..ae34d0fa7 100644 --- a/engine/minute_buckets.go +++ b/engine/minute_buckets.go @@ -25,14 +25,13 @@ import ( ) type MinuteBucket struct { - Seconds float64 - Weight float64 - Price float64 // percentage from standard price or absolute value depending on Type - PriceType string - DestinationId string - ExpirationString string - ExpirationDate time.Time - precision int + Seconds float64 + Weight float64 + Price float64 // percentage from standard price or absolute value depending on Type + PriceType string + DestinationId string + ExpirationDate time.Time + precision int } const ( diff --git a/engine/simple_serializer.go b/engine/simple_serializer.go index 0288d1c63..9188ecb91 100644 --- a/engine/simple_serializer.go +++ b/engine/simple_serializer.go @@ -112,6 +112,7 @@ func (a *Action) Store() (result string, err error) { result += a.ActionType + "|" result += a.BalanceId + "|" result += a.Direction + "|" + result += a.ExpirationString + "|" result += a.ExpirationDate.Format(time.RFC3339) + "|" result += strconv.FormatFloat(a.Units, 'f', -1, 64) + "|" result += strconv.FormatFloat(a.Weight, 'f', -1, 64) @@ -128,22 +129,23 @@ func (a *Action) Store() (result string, err error) { func (a *Action) Restore(input string) (err error) { elements := strings.Split(input, "|") - if len(elements) < 7 { + if len(elements) < 8 { return notEnoughElements } a.Id = elements[0] a.ActionType = elements[1] a.BalanceId = elements[2] a.Direction = elements[3] - a.ExpirationDate, err = time.Parse(time.RFC3339, elements[4]) + a.ExpirationString = elements[4] + a.ExpirationDate, err = time.Parse(time.RFC3339, elements[5]) if err != nil { return err } - a.Units, _ = strconv.ParseFloat(elements[5], 64) - a.Weight, _ = strconv.ParseFloat(elements[6], 64) - if len(elements) == 8 { + a.Units, _ = strconv.ParseFloat(elements[6], 64) + a.Weight, _ = strconv.ParseFloat(elements[7], 64) + if len(elements) == 9 { a.MinuteBucket = &MinuteBucket{} - if err := a.MinuteBucket.Restore(elements[7]); err != nil { + if err := a.MinuteBucket.Restore(elements[8]); err != nil { return err } } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 633bb9752..99caf6da3 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -1130,12 +1130,11 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er Weight: weight, ExpirationString: expirationDate, MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutes_weight, - Price: price, - PriceType: rate_type, - DestinationId: destinations_tag, - ExpirationString: expirationDate, + Seconds: units, + Weight: minutes_weight, + Price: price, + PriceType: rate_type, + DestinationId: destinations_tag, }, } } From 606d053844deae703b1c8e6057e32235b1152c58 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 31 Jul 2013 14:45:46 +0300 Subject: [PATCH 9/9] better parse date --- utils/coreutils.go | 15 +++++++-------- utils/utils_test.go | 8 ++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/utils/coreutils.go b/utils/coreutils.go index fb41718bc..4c5da1650 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -19,13 +19,13 @@ along with this program. If not, see package utils import ( - "bytes" "crypto/rand" "crypto/sha1" "encoding/hex" "fmt" "math" "strconv" + "strings" "time" ) @@ -97,22 +97,21 @@ func Round(x float64, prec int, method string) float64 { } func ParseDate(date string) (expDate time.Time, err error) { - expirationTime := []byte(date) switch { - case string(expirationTime) == "*unlimited" || string(expirationTime) == "": + case date == "*unlimited" || date == "": // leave it at zero - case string(expirationTime[0]) == "+": - d, err := time.ParseDuration(string(expirationTime[1:])) + case string(date[0]) == "+": + d, err := time.ParseDuration(date[1:]) if err != nil { return expDate, err } expDate = time.Now().Add(d) - case string(expirationTime) == "*monthly": + case date == "*monthly": expDate = time.Now().AddDate(0, 1, 0) // add one month - case bytes.Contains(expirationTime, []byte("Z")): + case strings.Contains(date, "Z"): expDate, err = time.Parse(time.RFC3339, date) default: - unix, err := strconv.ParseInt(string(expirationTime), 10, 64) + unix, err := strconv.ParseInt(date, 10, 64) if err != nil { return expDate, err } diff --git a/utils/utils_test.go b/utils/utils_test.go index 318611fb0..20efa7c47 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -143,6 +143,14 @@ func TestParseDateEmpty(t *testing.T) { } } +func TestParseDatePlus(t *testing.T) { + date, err := ParseDate("+20s") + expected := time.Now() + if err != nil || date.Sub(expected).Seconds() > 20 || date.Sub(expected).Seconds() < 19 { + t.Error("error parsing date: ", date.Sub(expected).Seconds()) + } +} + func TestParseDateMonthly(t *testing.T) { date, err := ParseDate("*monthly") expected := time.Now().AddDate(0, 1, 0)