mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-19 22:28:45 +05:00
Merge branch 'master' of https://github.com/cgrates/cgrates
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user