diff --git a/apier/v1/tpdestinationrates.go b/apier/v1/tpdestinationrates.go index 9b4603457..4d3219a8a 100644 --- a/apier/v1/tpdestinationrates.go +++ b/apier/v1/tpdestinationrates.go @@ -39,7 +39,10 @@ func (self *ApierV1) SetTPDestinationRate(attrs utils.TPDestinationRate, reply * } drs := make([]*engine.DestinationRate, len(attrs.DestinationRates)) for idx, dr := range attrs.DestinationRates { - drs[idx] = &engine.DestinationRate{attrs.DestinationRateId, dr.DestinationId, dr.RateId, nil} + drs[idx] = &engine.DestinationRate{ + Tag: attrs.DestinationRateId, + DestinationsTag: dr.DestinationId, + RateTag: dr.RateId} } if err := self.StorDb.SetTPDestinationRates(attrs.TPid, map[string][]*engine.DestinationRate{attrs.DestinationRateId: drs}); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) diff --git a/apier/v1/tpdestratetimings.go b/apier/v1/tpdestratetimings.go index 64683c38d..91dee75ff 100644 --- a/apier/v1/tpdestratetimings.go +++ b/apier/v1/tpdestratetimings.go @@ -42,7 +42,7 @@ func (self *ApierV1) SetTPDestRateTiming(attrs utils.TPDestRateTiming, reply *st drts[idx] = &engine.DestinationRateTiming{Tag: attrs.DestRateTimingId, DestinationRatesTag: drt.DestRatesId, Weight: drt.Weight, - TimingsTag: drt.TimingId, + TimingTag: drt.TimingId, } } if err := self.StorDb.SetTPDestRateTimings(attrs.TPid, map[string][]*engine.DestinationRateTiming{attrs.DestRateTimingId: drts}); err != nil { diff --git a/engine/loader_csv.go b/engine/loader_csv.go index dd7f0c4c5..b38bd96a4 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -30,19 +30,19 @@ import ( ) type CSVReader struct { - sep rune - storage DataStorage - readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) - actions map[string][]*Action - actionsTimings map[string][]*ActionTiming - actionsTriggers map[string][]*ActionTrigger - accountActions []*UserBalance - destinations []*Destination - timings map[string]*Timing - rates map[string]*LoadRate - destinationRates map[string][]*DestinationRate - ratingPlans map[string]*RatingPlan - ratingProfiles map[string]*RatingProfile + sep rune + storage DataStorage + readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) + actions map[string][]*Action + actionsTimings map[string][]*ActionTiming + actionsTriggers map[string][]*ActionTrigger + accountActions []*UserBalance + destinations []*Destination + timings map[string]*Timing + rates map[string][]*LoadRate + destinationRates map[string][]*DestinationRate + destinationRateTimings map[string][]*DestinationRateTiming + ratingProfiles map[string]*RatingProfile // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string @@ -55,10 +55,10 @@ func NewFileCSVReader(storage DataStorage, sep rune, destinationsFn, timingsFn, c.actions = make(map[string][]*Action) c.actionsTimings = make(map[string][]*ActionTiming) c.actionsTriggers = make(map[string][]*ActionTrigger) - c.rates = make(map[string]*LoadRate) + c.rates = make(map[string][]*LoadRate) c.destinationRates = make(map[string][]*DestinationRate) c.timings = make(map[string]*Timing) - c.ratingPlans = make(map[string]*RatingPlan) + c.destinationRateTimings = make(map[string][]*DestinationRateTiming) c.ratingProfiles = make(map[string]*RatingProfile) c.readerFunc = openFileCSVReader c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, @@ -228,7 +228,14 @@ func (csvr *CSVReader) LoadRates() (err error) { if err != nil { return err } - csvr.rates[tag] = r + // same tag only to create rate groups + existingRates, exists := csvr.rates[tag] + if exists { + if err := existingRates[len(existingRates)-1].ValidNextGroup(r); err != nil { + return err + } + } + csvr.rates[tag] = append(csvr.rates[tag], r) } return } @@ -249,11 +256,20 @@ func (csvr *CSVReader) LoadDestinationRates() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not get rates for tag %v", record[2])) } - //ToDo: Not checking presence of destinations? + destinationExists := false + for _, d := range csvr.destinations { + if d.Id == record[1] { + destinationExists = true + break + } + } + if !destinationExists { + return errors.New(fmt.Sprintf("Could not get destination for tag %v", record[1])) + } dr := &DestinationRate{ Tag: tag, DestinationsTag: record[1], - Rate: r, + rates: r, } csvr.destinationRates[tag] = append(csvr.destinationRates[tag], dr) @@ -277,18 +293,12 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not get timing for tag %v", record[2])) } - - rt := NewDestinationRateTiming(record[1], t, record[3]) drs, exists := csvr.destinationRates[record[1]] if !exists { return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1])) } - for _, dr := range drs { - if _, exists := csvr.ratingPlans[tag]; !exists { - csvr.ratingPlans[tag] = &RatingPlan{} - } - csvr.ratingPlans[tag].AddRateInterval(rt.GetRateInterval(dr)) - } + drt := NewDestinationRateTiming(drs, t, record[3]) + csvr.destinationRateTimings[tag] = append(csvr.destinationRateTimings[tag], drt) } return } @@ -315,19 +325,22 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { rp = &RatingProfile{Id: key} csvr.ratingProfiles[key] = rp } - for _, d := range csvr.destinations { - ap, exists := csvr.ratingPlans[record[5]] - if !exists { - return errors.New(fmt.Sprintf("Could not load ratinTiming for tag: %v", record[5])) - } - newAP := &RatingPlan{ActivationTime: at} - //copy(newAP.Intervals, ap.Intervals) - newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) - rp.AddRatingPlanIfNotPresent(d.Id, newAP) - if fallbacksubject != "" { - rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject) + drts, exists := csvr.destinationRateTimings[record[5]] + if !exists { + return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", record[5])) + } + + for _, drt := range drts { + plan := &RatingPlan{ActivationTime: at} + for _, dr := range drt.destinationRates { + plan.AddRateInterval(drt.GetRateInterval(dr)) + rp.AddRatingPlanIfNotPresent(dr.DestinationsTag, plan) } } + + if fallbacksubject != "" { + rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject) + } } return } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 5c0ff31bf..9fa64ae8a 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -209,7 +209,7 @@ func TestLoadRates(t *testing.T) { if len(csvr.rates) != 5 { t.Error("Failed to load rates: ", csvr.rates) } - rate := csvr.rates["R1"] + rate := csvr.rates["R1"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R1", ConnectFee: 0, @@ -221,9 +221,9 @@ func TestLoadRates(t *testing.T) { RoundingDecimals: 2, Weight: 10, }) { - t.Error("Error loading rate: ", csvr.rates) + t.Error("Error loading rate: ", csvr.rates["R1"][0]) } - rate = csvr.rates["R2"] + rate = csvr.rates["R2"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R2", ConnectFee: 0, @@ -237,7 +237,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R3"] + rate = csvr.rates["R3"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R3", ConnectFee: 0, @@ -251,7 +251,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R4"] + rate = csvr.rates["R4"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R4", ConnectFee: 1, @@ -265,7 +265,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R5"] + rate = csvr.rates["R5"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R5", ConnectFee: 0, @@ -284,24 +284,24 @@ func TestLoadRates(t *testing.T) { func TestLoadDestinationRates(t *testing.T) { if len(csvr.destinationRates) != 5 { - t.Error("Failed to load rates: ", csvr.rates) + t.Error("Failed to load destinationrates: ", csvr.destinationRates) } drs := csvr.destinationRates["RT_STANDARD"] if !reflect.DeepEqual(drs, []*DestinationRate{ &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY", - Rate: csvr.rates["R1"], + rates: csvr.rates["R1"], }, &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY_O2", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY_PREMIUM", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -311,7 +311,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "RT_DEFAULT", DestinationsTag: "ALL", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -321,12 +321,12 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "RT_STD_WEEKEND", DestinationsTag: "GERMANY", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, &DestinationRate{ Tag: "RT_STD_WEEKEND", DestinationsTag: "GERMANY_O2", - Rate: csvr.rates["R3"], + rates: csvr.rates["R3"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -336,7 +336,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "P1", DestinationsTag: "NAT", - Rate: csvr.rates["R4"], + rates: csvr.rates["R4"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -346,7 +346,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "P2", DestinationsTag: "NAT", - Rate: csvr.rates["R5"], + rates: csvr.rates["R5"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -354,228 +354,171 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.ratingPlans) != 4 { - t.Error("Failed to load rate timings: ", csvr.ratingPlans) + if len(csvr.destinationRateTimings) != 4 { + t.Error("Failed to load rate timings: ", csvr.destinationRateTimings) } - rplan := csvr.ratingPlans["STANDARD"] - expected := &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.2, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, + rplan := csvr.destinationRateTimings["STANDARD"] + expected := []*DestinationRateTiming{ + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R1", + ConnectFee: 0, + Price: 0.2, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_PREMIUM", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "18:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, + Weight: 10, + timing: &Timing{ + Id: "WORKDAYS_00", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00"}, + }, + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R3", + ConnectFee: 0, + Price: 0.05, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, + Weight: 10, + timing: &Timing{ + Id: "WORKDAYS_18", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "18:00:00", + }, + }, + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R3", + ConnectFee: 0, + Price: 0.05, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + }, + Weight: 10, + timing: &Timing{ + Id: "WEEKENDS", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{6, 0}, + StartTime: "00:00:00", }, }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["STANDARD"]) - } - rplan = csvr.ratingPlans["PREMIUM"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["PREMIUM"]) - } - rplan = csvr.ratingPlans["DEFAULT"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["DEFAULT"]) - } - rplan = csvr.ratingPlans["EVENING"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 1, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 1, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 2, - }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "18:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.5, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_DOWN, - RoundingDecimals: 2, - }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.5, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_DOWN, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["EVENING"]) + t.Errorf("Error loading destination rate timing: %#v", rplan) } } @@ -586,22 +529,13 @@ func TestLoadRatingProfiles(t *testing.T) { rp := csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] expected := &RatingProfile{} if reflect.DeepEqual(rp, expected) { - t.Error("Error loading rating profile: ", csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"]) + t.Errorf("Error loading rating profile: %#v", rp) } } /* CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb CUSTOMER_1,0,*out,rif:from:tm,2012-02-28T00:00:00Z,STANDARD,danb -CUSTOMER_2,0,*out,danb:87.139.12.167,2012-01-01T00:00:00Z,STANDARD,danb -CUSTOMER_1,0,*out,danb,2012-01-01T00:00:00Z,PREMIUM, -vdf,0,*out,rif,2012-01-01T00:00:00Z,EVENING, -vdf,0,*out,rif,2012-02-28T00:00:00Z,EVENING, -vdf,0,*out,minu,2012-01-01T00:00:00Z,EVENING, -vdf,0,*out,*any,2012-02-28T00:00:00Z,EVENING, -vdf,0,*out,one,2012-02-28T00:00:00Z,STANDARD, -vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf -vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif */ func TestLoadActions(t *testing.T) { diff --git a/engine/loader_db.go b/engine/loader_db.go index 2141e7c46..fd996c84b 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -35,7 +35,7 @@ type DbReader struct { accountActions []*UserBalance destinations []*Destination timings map[string]*Timing - rates map[string]*LoadRate + rates map[string][]*LoadRate destinationRates map[string][]*DestinationRate activationPeriods map[string]*RatingPlan ratingProfiles map[string]*RatingProfile @@ -141,11 +141,11 @@ func (dbr *DbReader) LoadDestinationRates() (err error) { } for _, drs := range dbr.destinationRates { for _, dr := range drs { - rate, exists := dbr.rates[dr.RateTag] + rates, exists := dbr.rates[dr.RateTag] if !exists { return errors.New(fmt.Sprintf("Could not find rate for tag %v", dr.RateTag)) } - dr.Rate = rate + dr.rates = rates } } return nil @@ -157,9 +157,9 @@ func (dbr *DbReader) LoadDestinationRateTimings() error { return err } for _, rt := range rts { - t, exists := dbr.timings[rt.TimingsTag] + t, exists := dbr.timings[rt.TimingTag] if !exists { - return errors.New(fmt.Sprintf("Could not get timing for tag %v", rt.TimingsTag)) + return errors.New(fmt.Sprintf("Could not get timing for tag %v", rt.TimingTag)) } rt.timing = t drs, exists := dbr.destinationRates[rt.DestinationRatesTag] @@ -223,12 +223,12 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { } for _, destrateTiming := range drtm { Logger.Debug(fmt.Sprintf("Destination rate timing: %v", rpm)) - tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag) + tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingTag) Logger.Debug(fmt.Sprintf("Timing: %v", rpm)) if err != nil || len(tm) == 0 { - return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.TimingsTag, err) + return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.TimingTag, err) } - destrateTiming.timing = tm[destrateTiming.TimingsTag] + destrateTiming.timing = tm[destrateTiming.TimingTag] drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag) if err != nil || len(drm) == 0 { return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.DestinationRatesTag, err) @@ -240,7 +240,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { return fmt.Errorf("No Rates profile with id %s: %v", drate.RateTag, err) } Logger.Debug(fmt.Sprintf("Rate: %v", rpm)) - drate.Rate = rt[drate.RateTag] + drate.rates = rt[drate.RateTag] if _, exists := activationPeriods[destrateTiming.Tag]; !exists { activationPeriods[destrateTiming.Tag] = &RatingPlan{} } diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 8d9291f88..e9b96e517 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -24,6 +24,7 @@ import ( "fmt" "github.com/cgrates/cgrates/utils" "log" + "math" "os" "path" "regexp" @@ -106,11 +107,24 @@ func NewLoadRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterv return } +func (present *LoadRate) ValidNextGroup(next *LoadRate) error { + if next.GroupIntervalStart <= present.GroupIntervalStart { + return errors.New(fmt.Sprintf("Next rate group interval start must be heigher than the last one: %#v", next)) + } + if math.Mod(next.GroupIntervalStart.Seconds(), present.RateIncrement.Seconds()) != 0 { + return errors.New(fmt.Sprintf("GroupIntervalStart of %#v must be a multiple of RateIncrement of %#v", next, present)) + } + if present.RoundingMethod != next.RoundingMethod || present.RoundingDecimals != next.RoundingDecimals { + return errors.New(fmt.Sprintf("Rounding stuff must be equal for sam rate tag: %#v, %#v", present, next)) + } + return nil +} + type DestinationRate struct { Tag string DestinationsTag string - RateTag string - Rate *LoadRate + RateTag string // intermediary used when loading from db + rates []*LoadRate } type Timing struct { @@ -135,43 +149,46 @@ func NewTiming(timingInfo ...string) (rt *Timing) { type DestinationRateTiming struct { Tag string - DestinationRatesTag string + DestinationRatesTag string // intermediary used when loading from db + destinationRates []*DestinationRate Weight float64 - TimingsTag string // intermediary used when loading from db + TimingTag string // intermediary used when loading from db timing *Timing } -func NewDestinationRateTiming(destinationRatesTag string, timing *Timing, weight string) (rt *DestinationRateTiming) { +func NewDestinationRateTiming(destinationRates []*DestinationRate, timing *Timing, weight string) (drt *DestinationRateTiming) { w, err := strconv.ParseFloat(weight, 64) if err != nil { log.Printf("Error parsing weight unit from: %v", weight) return } - rt = &DestinationRateTiming{ - DestinationRatesTag: destinationRatesTag, - Weight: w, - timing: timing, + drt = &DestinationRateTiming{ + destinationRates: destinationRates, + timing: timing, + Weight: w, } return } -func (rt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { +func (drt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { i = &RateInterval{ - 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, - RoundingMethod: dr.Rate.RoundingMethod, - RoundingDecimals: dr.Rate.RoundingDecimals, - Rates: RateGroups{&Rate{ - GroupIntervalStart: dr.Rate.GroupIntervalStart, - Value: dr.Rate.Price, - RateIncrement: dr.Rate.RateIncrement, - RateUnit: dr.Rate.RateUnit, - }}, + Years: drt.timing.Years, + Months: drt.timing.Months, + MonthDays: drt.timing.MonthDays, + WeekDays: drt.timing.WeekDays, + StartTime: drt.timing.StartTime, + Weight: drt.Weight, + ConnectFee: dr.rates[0].ConnectFee, + RoundingMethod: dr.rates[0].RoundingMethod, + RoundingDecimals: dr.rates[0].RoundingDecimals, + } + for _, rl := range dr.rates { + i.Rates = append(i.Rates, &Rate{ + GroupIntervalStart: rl.GroupIntervalStart, + Value: rl.Price, + RateIncrement: rl.RateIncrement, + RateUnit: rl.RateUnit, + }) } return } diff --git a/engine/ratingplan.go b/engine/ratingplan.go index 5e14bf74f..0b6211630 100644 --- a/engine/ratingplan.go +++ b/engine/ratingplan.go @@ -40,18 +40,17 @@ type xCachedRatingPlans struct { /* Adds one ore more intervals to the internal interval list only if it is not allready in the list. */ -func (ap *RatingPlan) AddRateInterval(is ...*RateInterval) { - for _, i := range is { +func (ap *RatingPlan) AddRateInterval(ris ...*RateInterval) { + for _, ri := range ris { found := false - for _, ei := range ap.RateIntervals { - if i.Equal(ei) { - (&ei.Rates).AddRate(i.Rates...) + for _, eri := range ap.RateIntervals { + if ri.Equal(eri) { found = true break } } if !found { - ap.RateIntervals = append(ap.RateIntervals, i) + ap.RateIntervals = append(ap.RateIntervals, ri) } } } diff --git a/engine/ratingplan_test.go b/engine/ratingplan_test.go index ef007ba93..e22413b77 100644 --- a/engine/ratingplan_test.go +++ b/engine/ratingplan_test.go @@ -79,8 +79,8 @@ func TestFallbackDirect(t *testing.T) { func TestFallbackMultiple(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 1 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingPlans) != 2 { + t.Errorf("Error restoring rating plans: %#v", cd.RatingPlans) } } @@ -161,8 +161,8 @@ func TestApAddRateIntervalGroups(t *testing.T) { if len(ap.RateIntervals) != 1 { t.Error("Wronfully appended interval ;)") } - if len(ap.RateIntervals[0].Rates) != 2 { - t.Error("Group prices not formed: ", ap.RateIntervals[0].Rates) + if len(ap.RateIntervals[0].Rates) != 1 { + t.Errorf("Group prices not formed: %#v", ap.RateIntervals[0].Rates[0]) } } diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 0c243de0f..a2185f316 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -31,20 +31,21 @@ type RatingProfile struct { } // Adds an activation period that applyes to current rating profile if not already present. -func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, aps ...*RatingPlan) { +func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, plans ...*RatingPlan) { if rp.DestinationMap == nil { rp.DestinationMap = make(map[string][]*RatingPlan, 1) } - for _, ap := range aps { + for _, plan := range plans { found := false - for _, eap := range rp.DestinationMap[destInfo] { - if ap.Equal(eap) { + for _, existingPlan := range rp.DestinationMap[destInfo] { + if plan.Equal(existingPlan) { + existingPlan.AddRateInterval(plan.RateIntervals...) found = true break } } if !found { - rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], ap) + rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], plan) } } } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 0fe0b567a..2f91cdb8a 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -143,7 +143,7 @@ type LoadStorage interface { // loader functions GetTpDestinations(string, string) ([]*Destination, error) GetTpTimings(string, string) (map[string]*Timing, error) - GetTpRates(string, string) (map[string]*LoadRate, error) + GetTpRates(string, string) (map[string][]*LoadRate, error) GetTpDestinationRates(string, string) (map[string][]*DestinationRate, error) GetTpDestinationRateTimings(string, string) ([]*DestinationRateTiming, error) GetTpRatingProfiles(string, string) (map[string]*RatingProfile, error) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 13d13f1a3..c11e43f46 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -363,7 +363,7 @@ func (self *SQLStorage) SetTPDestRateTimings(tpid string, drts map[string][]*Des qry += "," } qry += fmt.Sprintf("('%s','%s','%s','%s',%f)", - tpid, drtId, drt.DestinationRatesTag, drt.TimingsTag, drt.Weight) + tpid, drtId, drt.DestinationRatesTag, drt.TimingTag, drt.Weight) i++ } } @@ -921,8 +921,8 @@ func (self *SQLStorage) GetTpDestinations(tpid, tag string) ([]*Destination, err return dests, nil } -func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*LoadRate, error) { - rts := make(map[string]*LoadRate) +func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string][]*LoadRate, error) { + rts := make(map[string][]*LoadRate) q := fmt.Sprintf("SELECT tag, connect_fee, rate, rate_unit, rate_increment, group_interval_start, rounding_method, rounding_decimals, weight FROM %s WHERE tpid='%s' ", utils.TBL_TP_RATES, tpid) if tag != "" { q += fmt.Sprintf(" AND tag='%s'", tag) @@ -951,7 +951,14 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*LoadRate, erro RoundingDecimals: roundingDecimals, Weight: weight, } - rts[tag] = r + // same tag only to create rate groups + existingRates, exists := rts[tag] + if exists { + if err := existingRates[len(existingRates)-1].ValidNextGroup(r); err != nil { + return nil, err + } + } + rts[tag] = append(rts[tag], r) } return rts, nil } @@ -1027,7 +1034,7 @@ func (self *SQLStorage) GetTpDestinationRateTimings(tpid, tag string) ([]*Destin Tag: tag, DestinationRatesTag: destination_rates_tag, Weight: weight, - TimingsTag: timings_tag, + TimingTag: timings_tag, } rts = append(rts, rt) } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 324c122ba..db8105390 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -208,7 +208,7 @@ func (self *TPCSVImporter) importDestRateTimings(fn string) error { drt := &DestinationRateTiming{Tag: record[0], DestinationRatesTag: record[1], Weight: weight, - TimingsTag: record[2], + TimingTag: record[2], } if err := self.StorDb.SetTPDestRateTimings(self.TPid, map[string][]*DestinationRateTiming{drt.Tag: []*DestinationRateTiming{drt}}); err != nil { if self.Verbose { diff --git a/engine/userbalance.go b/engine/userbalance.go index 1826c5b29..1c0e69fc9 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -287,6 +287,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { if paid { continue } + // TODO: Split if some increments were processed by minutes // debit monetary for _, b := range usefulMoneyBalances { if b.Value == 0 {