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/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index dffcc8c23..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`), @@ -118,7 +118,7 @@ CREATE TABLE `tp_actions` ( `balance_type` varchar(24) NOT NULL, `direction` varchar(8) NOT NULL, `units` DECIMAL(8,4) NOT NULL, - `expiry_time` int(16) NOT NULL, + `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/action.go b/engine/action.go index 65e38c3d1..c667326ac 100644 --- a/engine/action.go +++ b/engine/action.go @@ -28,16 +28,17 @@ 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 - DestinationTag, RateType string // From here for import/load purposes only - RateValue, MinutesWeight float64 + Id string + ActionType string + BalanceId string + Direction string + ExpirationString string + ExpirationDate time.Time + Units float64 + Weight float64 + MinuteBucket *MinuteBucket + DestinationTag, RateType string // From here for import/load purposes only + RateValue, MinutesWeight float64 } const ( diff --git a/engine/action_timing.go b/engine/action_timing.go index e8829074d..27d956c51 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,10 @@ func (at *ActionTiming) Execute() (err error) { return } 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 d88b40ad2..2add33bc1 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,10 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) { return } 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/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_csv.go b/engine/loader_csv.go index ec4213b4e..b0ae606df 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])) } @@ -349,21 +348,17 @@ func (csvr *CSVReader) LoadActions() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) } - var expiryTime time.Time // Empty initialized time represents never expire - if record[5] != "*unlimited" { // ToDo: Expand here for other meta tags or go way of adding time for expiry - expiryTime, err = time.Parse(time.RFC3339, record[5]) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse expiry time: %v", err)) - } - } var a *Action if record[2] != MINUTES { a = &Action{ - ActionType: record[1], - BalanceId: record[2], - Direction: record[3], - Units: units, - ExpirationDate: expiryTime, //ToDo: Fix ExpirationDate as string to have ability of storing + reported on run time + 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) @@ -379,21 +374,24 @@ 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: expiryTime, + 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: expiryTime, + Seconds: units, + Weight: minutesWeight, + Price: value, + PriceType: record[7], + DestinationId: record[6], }, } + 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) } @@ -403,7 +401,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 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/loader_helpers.go b/engine/loader_helpers.go index 07977005e..dd50978d6 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -155,16 +155,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 } diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go index fd3104b72..ae34d0fa7 100644 --- a/engine/minute_buckets.go +++ b/engine/minute_buckets.go @@ -64,7 +64,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/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/simple_serializer.go b/engine/simple_serializer.go index 3c811dd2c..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 } } @@ -458,7 +460,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 +473,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 +484,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 +529,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 +536,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 +560,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/storage_sql.go b/engine/storage_sql.go index db05c6fa7..99caf6da3 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 { @@ -480,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 @@ -562,7 +559,7 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err expTime = act.ExpirationDate.Unix() } qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,%d,'%s','%s',%f,%f,%f)", - tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Units, expTime, + tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Units, expTime, act.DestinationTag, act.RateType, act.RateValue, act.MinutesWeight, act.Weight) i++ } @@ -583,8 +580,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 @@ -1079,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 } @@ -1116,36 +1111,30 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er if err := rows.Scan(&id, &tpid, &tag, &action, &balance_type, &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_type != MINUTES { a = &Action{ - ActionType: action, - BalanceId: balance_type, - Direction: direction, - Units: units, - ExpirationDate: expDate, + ActionType: action, + BalanceId: balance_type, + Direction: direction, + Units: units, + ExpirationString: expirationDate, } } else { var price float64 a = &Action{ - Id: utils.GenUUID(), - ActionType: action, - BalanceId: balance_type, - Direction: direction, - Weight: weight, - ExpirationDate: expDate, + Id: utils.GenUUID(), + ActionType: action, + BalanceId: balance_type, + 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, }, } } diff --git a/engine/timespans.go b/engine/timespans.go index aaef756be..d0381bad9 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -20,6 +20,7 @@ package engine import ( "fmt" + "math" "time" ) @@ -61,9 +62,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() @@ -117,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 } } @@ -131,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) @@ -141,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) @@ -155,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 @@ -206,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 801eb4d9d..859f75bc2 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,19 +331,19 @@ 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, 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 { @@ -352,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) } @@ -363,3 +362,158 @@ 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()) + } +} + +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()) + } +} 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 fc784e15d..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 } @@ -91,16 +91,16 @@ type TPActions struct { } type Action struct { - Identifier string // Identifier mapped in the code - BalanceType string // Type of balance the action will operate on - Direction string // Balance direction - Units float64 // Number of units to add/deduct - ExpiryTime int64 // Time when the units will expire - DestinationId string // Destination profile id - RateType string // Type of rate <*absolute|*percent> - Rate float64 // Price value - MinutesWeight float64 // Minutes weight - Weight float64 // Action's weight + Identifier string // Identifier mapped in the code + BalanceType string // Type of balance the action will operate on + Direction string // Balance direction + Units float64 // Number of units to add/deduct + ExpiryTime string // Time when the units will expire + DestinationId string // Destination profile id + RateType string // Type of rate <*absolute|*percent> + Rate float64 // Price value + MinutesWeight float64 // Minutes weight + Weight float64 // Action's weight } type ApiTPActionTimings struct { @@ -123,7 +123,7 @@ type ApiTPActionTriggers struct { } type ApiActionTrigger struct { - BalanceType string // Type of balance this trigger monitors + BalanceType string // Type of balance this trigger monitors Direction string // Traffic direction ThresholdType string // This threshold type ThresholdValue float64 // Threshold diff --git a/utils/coreutils.go b/utils/coreutils.go index 51a588610..4c5da1650 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -25,6 +25,7 @@ import ( "fmt" "math" "strconv" + "strings" "time" ) @@ -94,3 +95,27 @@ func Round(x float64, prec int, method string) float64 { return rounder / pow } + +func ParseDate(date string) (expDate time.Time, err error) { + switch { + case date == "*unlimited" || date == "": + // leave it at zero + case string(date[0]) == "+": + d, err := time.ParseDuration(date[1:]) + if err != nil { + return expDate, err + } + expDate = time.Now().Add(d) + case date == "*monthly": + expDate = time.Now().AddDate(0, 1, 0) // add one month + case strings.Contains(date, "Z"): + expDate, err = time.Parse(time.RFC3339, date) + default: + unix, err := strconv.ParseInt(date, 10, 64) + if err != nil { + return expDate, err + } + expDate = time.Unix(unix, 0) + } + return expDate, err +} diff --git a/utils/utils_test.go b/utils/utils_test.go index e55775f10..20efa7c47 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,49 @@ 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 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) + if err != nil || expected.Sub(date).Seconds() > 1 { + 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)) + } +}