From 3466fa103bcb5ec63228179be8c563cd47be3d1a Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 21 Oct 2013 19:18:56 +0300 Subject: [PATCH] refactored rating plans structure --- engine/callcost.go | 2 +- engine/calldesc.go | 51 ++-- engine/calldesc_test.go | 55 +++- engine/loader_csv.go | 66 ++--- engine/loader_csv_test.go | 471 ++++++++++------------------------- engine/loader_db.go | 82 +++--- engine/loader_helpers.go | 8 +- engine/ratingplan.go | 27 +- engine/ratingplan_test.go | 114 +++++---- engine/ratingprofile.go | 120 ++++++--- engine/ratingprofile_test.go | 27 +- engine/storage_interface.go | 3 + engine/storage_map.go | 19 ++ engine/storage_mongo.go | 19 ++ engine/storage_radix.go | 22 ++ engine/storage_redigo.go | 19 ++ engine/storage_redis.go | 19 ++ engine/storage_test.go | 30 +-- engine/timespans.go | 6 +- engine/timespans_test.go | 6 +- engine/userbalance_test.go | 158 ++++-------- 21 files changed, 633 insertions(+), 691 deletions(-) diff --git a/engine/callcost.go b/engine/callcost.go index 973c4d900..bb3c019da 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -47,7 +47,7 @@ func (cc *CallCost) Merge(other *CallCost) { } ts := cc.Timespans[len(cc.Timespans)-1] otherTs := other.Timespans[0] - if reflect.DeepEqual(ts.ratingPlan, otherTs.ratingPlan) && + if reflect.DeepEqual(ts.ratingInfo, otherTs.ratingInfo) && reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { // extend the last timespan with ts.TimeEnd = ts.TimeEnd.Add(otherTs.GetDuration()) diff --git a/engine/calldesc.go b/engine/calldesc.go index f2de51b64..23c85a91c 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -107,14 +107,14 @@ type CallDescriptor struct { CallDuration time.Duration // the call duration so far (till TimeEnd) Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject - RatingPlans []*RatingPlan + RatingInfos []*RatingInfo Increments Increments userBalance *UserBalance } -// Adds an activation period that applyes to current call descriptor. -func (cd *CallDescriptor) AddRatingPlan(aps ...*RatingPlan) { - cd.RatingPlans = append(cd.RatingPlans, aps...) +// Adds a rating plan that applyes to current call descriptor. +func (cd *CallDescriptor) AddRatingInfo(ris ...*RatingInfo) { + cd.RatingInfos = append(cd.RatingInfos, ris...) } // Returns the key used to retrive the user balance involved in this call @@ -137,39 +137,41 @@ func (cd *CallDescriptor) getUserBalance() (ub *UserBalance, err error) { /* Restores the activation periods for the specified prefix from storage. */ -func (cd *CallDescriptor) LoadRatingPlans() (destPrefix, matchedSubject string, err error) { +func (cd *CallDescriptor) LoadRatingPlans() (destPrefixes []string, matchedSubject string, err error) { matchedSubject = cd.GetKey() - if val, err := cache2go.GetXCached(cd.GetKey() + cd.Destination); err == nil { + /*if val, err := cache2go.GetXCached(cd.GetKey() + cd.Destination); err == nil { xaps := val.(xCachedRatingPlans) cd.RatingPlans = xaps.aps return xaps.destPrefix, matchedSubject, nil - } - destPrefix, matchedSubject, values, err := cd.getRatingPlansForPrefix(cd.GetKey(), 1) + }*/ + destPrefixes, matchedSubject, values, err := cd.getRatingPlansForPrefix(cd.GetKey(), 1) if err != nil { fallbackKey := fmt.Sprintf("%s:%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, FALLBACK_SUBJECT) // use the default subject - destPrefix, matchedSubject, values, err = cd.getRatingPlansForPrefix(fallbackKey, 1) + destPrefixes, matchedSubject, values, err = cd.getRatingPlansForPrefix(fallbackKey, 1) } //load the rating plans if err == nil && len(values) > 0 { - xaps := xCachedRatingPlans{destPrefix, values, new(cache2go.XEntry)} - xaps.XCache(cd.GetKey()+cd.Destination, debitPeriod+5*time.Second, xaps) - cd.RatingPlans = values + /* + xaps := xCachedRatingPlans{destPrefix, values, new(cache2go.XEntry)} + xaps.XCache(cd.GetKey()+cd.Destination, debitPeriod+5*time.Second, xaps) + */ + cd.RatingInfos = values } return } -func (cd *CallDescriptor) getRatingPlansForPrefix(key string, recursionDepth int) (foundPrefix, matchedSubject string, aps []*RatingPlan, err error) { +func (cd *CallDescriptor) getRatingPlansForPrefix(key string, recursionDepth int) (foundPrefixes []string, matchedSubject string, ris []*RatingInfo, err error) { matchedSubject = key if recursionDepth > RECURSION_MAX_DEPTH { err = errors.New("Max fallback recursion depth reached!" + key) return } rp, err := storageGetter.GetRatingProfile(key) - if err != nil { - return "", "", nil, err + if err != nil || rp == nil { + return nil, "", nil, err } - foundPrefix, aps, err = rp.GetRatingPlansForPrefix(cd.Destination) + foundPrefixes, ris, err = rp.GetRatingPlansForPrefix(cd) if err != nil { if rp.FallbackKey != "" { recursionDepth++ @@ -200,15 +202,16 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti firstSpan = &TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd, CallDuration: cd.CallDuration} } timespans = append(timespans, firstSpan) - if len(cd.RatingPlans) == 0 { + if len(cd.RatingInfos) == 0 { return } - firstSpan.ratingPlan = cd.RatingPlans[0] - // split on activation periods + + firstSpan.ratingInfo = cd.RatingInfos[0] + // split on rating plans afterStart, afterEnd := false, false //optimization for multiple activation periods - for _, rp := range cd.RatingPlans { + for _, rp := range cd.RatingInfos { if !afterStart && !afterEnd && rp.ActivationTime.Before(cd.TimeStart) { - firstSpan.ratingPlan = rp + firstSpan.ratingInfo = rp } else { afterStart = true for i := 0; i < len(timespans); i++ { @@ -227,7 +230,7 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti for i := 0; i < len(timespans); i++ { //log.Printf("==============%v==================", i) //log.Printf("TS: %+v", timespans[i]) - rp := timespans[i].ratingPlan + rp := timespans[i].ratingInfo Logger.Debug(fmt.Sprintf("rp: %+v", rp)) //timespans[i].RatingPlan = nil rp.RateIntervals.Sort() @@ -238,7 +241,7 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } newTs := timespans[i].SplitByRateInterval(interval) if newTs != nil { - newTs.ratingPlan = rp + newTs.ratingInfo = rp // insert the new timespan index := i + 1 timespans = append(timespans, nil) @@ -329,7 +332,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { Tenant: cd.Tenant, Subject: matchedSubject[startIndex:], Account: cd.Account, - Destination: destPrefix, + Destination: strings.Join(destPrefix, ";"), Cost: cost, ConnectFee: connectionFee, Timespans: timespans} diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index acc27e1bd..8c04c6d08 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -71,7 +71,7 @@ func TestSplitSpans(t *testing.T) { cd.LoadRatingPlans() timespans := cd.splitInTimeSpans(nil) if len(timespans) != 2 { - t.Log(cd.RatingPlans) + t.Log(cd.RatingInfos) t.Error("Wrong number of timespans: ", len(timespans)) } } @@ -82,9 +82,10 @@ func TestSplitSpansRoundToIncrements(t *testing.T) { cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "test", Subject: "trp", Destination: "0256", TimeStart: t1, TimeEnd: t2, CallDuration: 132 * time.Second} cd.LoadRatingPlans() + t.Logf("%+v", cd) timespans := cd.splitInTimeSpans(nil) if len(timespans) != 2 { - t.Log(cd.RatingPlans) + t.Log(cd.RatingInfos) t.Error("Wrong number of timespans: ", len(timespans)) } var d time.Duration @@ -161,7 +162,7 @@ func TestGetCostRateGroups(t *testing.T) { t.Error("Error getting cost: ", err) } if result.Cost != 132 { - t.Error("Error calculating cost: ", result.Timespans[0]) + t.Error("Error calculating cost: ", result.Timespans) } } @@ -194,19 +195,19 @@ func TestFullDestNotFound(t *testing.T) { result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { - t.Log(cd.RatingPlans) + t.Log(cd.RatingInfos) t.Errorf("Expected %v was %v", expected, result) } } func TestSubjectNotFound(t *testing.T) { - t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) - t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) + t1 := time.Date(2013, time.February, 1, 17, 30, 0, 0, time.UTC) + t2 := time.Date(2013, time.February, 1, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "not_exiting", Destination: "025740532", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { - t.Log(cd.RatingPlans) + t.Logf("%+v", result.Timespans[0].RateInterval) t.Errorf("Expected %v was %v", expected, result) } } @@ -267,7 +268,14 @@ func TestMinutesCost(t *testing.T) { } func TestMaxSessionTimeNoUserBalance(t *testing.T) { - cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "rif", + Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionTime(time.Now()) if result != 1000 || err == nil { t.Errorf("Expected %v was %v (%v)", 1000, result, err) @@ -275,7 +283,15 @@ func TestMaxSessionTimeNoUserBalance(t *testing.T) { } func TestMaxSessionTimeWithUserBalance(t *testing.T) { - cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "minu", + Destination: "0723", + Amount: 1000} result, err := cd.GetMaxSessionTime(time.Now()) expected := 300.0 if result != expected || err != nil { @@ -284,7 +300,16 @@ func TestMaxSessionTimeWithUserBalance(t *testing.T) { } func TestMaxSessionTimeWithUserBalanceAccount(t *testing.T) { - cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu_from_tm", Account: "minu", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "minu_from_tm", + Account: "minu", + Destination: "0723", + Amount: 1000} result, err := cd.GetMaxSessionTime(time.Now()) expected := 300.0 if result != expected || err != nil { @@ -293,7 +318,15 @@ func TestMaxSessionTimeWithUserBalanceAccount(t *testing.T) { } func TestMaxSessionTimeNoCredit(t *testing.T) { - cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "broker", Destination: "0723", Amount: 5400} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "broker", + Destination: "0723", + Amount: 5400} result, err := cd.GetMaxSessionTime(time.Now()) if result != 100 || err != nil { t.Errorf("Expected %v was %v", 100, result) diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 69f205367..40b4b3ab8 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 - destinationRateTimings map[string][]*DestinationRateTiming - 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 + ratingPlans map[string]*RatingPlan + ratingProfiles map[string]*RatingProfile // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string @@ -58,7 +58,7 @@ func NewFileCSVReader(storage DataStorage, sep rune, destinationsFn, timingsFn, c.rates = make(map[string][]*LoadRate) c.destinationRates = make(map[string][]*DestinationRate) c.timings = make(map[string]*Timing) - c.destinationRateTimings = make(map[string][]*DestinationRateTiming) + c.ratingPlans = make(map[string]*RatingPlan) c.ratingProfiles = make(map[string]*RatingProfile) c.readerFunc = openFileCSVReader c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, @@ -115,6 +115,18 @@ func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) { log.Print(d.Id, " : ", d.Prefixes) } } + if verbose { + log.Print("Rating plans") + } + for _, rp := range csvr.ratingPlans { + err = storage.SetRatingPlan(rp) + if err != nil { + return err + } + if verbose { + log.Print(rp.Id) + } + } if verbose { log.Print("Rating profiles") } @@ -298,8 +310,15 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1])) } - drt := NewDestinationRateTiming(drs, t, record[3]) - csvr.destinationRateTimings[tag] = append(csvr.destinationRateTimings[tag], drt) + drt := NewDestinationRateTiming(t, record[3]) + plan, exists := csvr.ratingPlans[tag] + if !exists { + plan = &RatingPlan{Id: tag} + csvr.ratingPlans[tag] = plan + } + for _, dr := range drs { + plan.AddRateInterval(dr.DestinationsTag, drt.GetRateInterval(dr)) + } } return } @@ -326,21 +345,11 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { rp = &RatingProfile{Id: key} csvr.ratingProfiles[key] = rp } - drts, exists := csvr.destinationRateTimings[record[5]] + _, exists := csvr.ratingPlans[record[5]] if !exists { return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", record[5])) } - - for _, drt := range drts { - //log.Print("TAG: ", record[5]) - for _, dr := range drt.destinationRates { - plan := &RatingPlan{ActivationTime: at} - //log.Printf("RI: %+v", drt.GetRateInterval(dr)) - plan.AddRateInterval(drt.GetRateInterval(dr)) - rp.AddRatingPlanIfNotPresent(dr.DestinationsTag, plan) - } - } - + rp.RatingPlanActivations = append(rp.RatingPlanActivations, &RatingPlanActivation{at, record[5]}) if fallbacksubject != "" { for _, fbs := range strings.Split(fallbacksubject, ";") { newKey := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fbs) @@ -351,6 +360,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { } rp.FallbackKey = strings.TrimRight(rp.FallbackKey, ";") } + csvr.ratingProfiles[rp.Id] = rp } return } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index b3045b581..f18aa4a01 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -434,180 +434,163 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.destinationRateTimings) != 5 { - t.Error("Failed to load rate timings: ", csvr.destinationRateTimings) + if len(csvr.ratingPlans) != 5 { + t.Error("Failed to load rate timings: ", csvr.ratingPlans) } - 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, + rplan := csvr.ratingPlans["STANDARD"] + expected := &RatingPlan{ + Id: "STANDARD", + DestinationRates: map[string]RateIntervalList{ + "GERMANY": 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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.2, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, }, - &DestinationRate{ - Tag: "RT_STANDARD", - DestinationsTag: "GERMANY_O2", - rates: []*LoadRate{ - &LoadRate{ - Tag: "R2", - ConnectFee: 0, - Price: 0.1, - RateUnit: time.Minute, - RateIncrement: time.Second, + &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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, }, - &DestinationRate{ - Tag: "RT_STANDARD", - DestinationsTag: "GERMANY_PREMIUM", - rates: []*LoadRate{ - &LoadRate{ - Tag: "R2", - ConnectFee: 0, - Price: 0.1, - RateUnit: time.Minute, - RateIncrement: time.Second, + &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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, }, }, - 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, + "GERMANY_O2": 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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + 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, + &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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.05, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + 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.05, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, }, }, - 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, + "GERMANY_PREMIUM": 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, - RoundingMethod: "*middle", - RoundingDecimals: 2, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, }, }, + 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, - 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 destination rate timing: %+v", rplan) - } - rplan = csvr.destinationRateTimings["TDRT"] - expected = []*DestinationRateTiming{ - &DestinationRateTiming{ - destinationRates: csvr.destinationRates["T1"], - Weight: 10, - timing: csvr.timings["WORKDAYS_00"], - }, - &DestinationRateTiming{ - destinationRates: csvr.destinationRates["T2"], - Weight: 10, - timing: csvr.timings["WORKDAYS_00"], - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading destination rate timing: %+v", rplan[0]) + t.Errorf("Error loading destination rate timing: %+v", rplan.DestinationRates["GERMANY_PREMIUM"][0].Rates[0]) } } @@ -619,206 +602,14 @@ func TestLoadRatingProfiles(t *testing.T) { expected := &RatingProfile{ Id: "*out:test:0:trp", FallbackKey: "*out:test:0:rif;*out:test:0:danb", - DestinationMap: map[string][]*RatingPlan{ - "NAT": []*RatingPlan{ - &RatingPlan{ - ActivationTime: time.Date(2013, time.October, 1, 0, 0, 0, 0, time.UTC), - RateIntervals: []*RateInterval{ - &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: 1, - RateIncrement: time.Minute, - RateUnit: time.Second, - }, - &Rate{ - GroupIntervalStart: time.Minute, - Value: 1, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 4, - }, - }, - }, - }, - "GERMANY": []*RatingPlan{ - &RatingPlan{ - ActivationTime: time.Date(2013, time.October, 1, 0, 0, 0, 0, time.UTC), - RateIntervals: []*RateInterval{ - &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: 7.77777, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 4, - }, - }, - }, - }, - "GERMANY_O2": []*RatingPlan{ - &RatingPlan{ - ActivationTime: time.Date(2013, time.October, 1, 0, 0, 0, 0, time.UTC), - RateIntervals: []*RateInterval{ - &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: 1, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 4, - }, - }, - }, - }, - "GERMANY_PREMIUM": []*RatingPlan{ - &RatingPlan{ - ActivationTime: time.Date(2013, time.October, 1, 0, 0, 0, 0, time.UTC), - RateIntervals: []*RateInterval{ - &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: 5.55555, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 4, - }, - }, - }, - }, - }, + RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ + ActivationTime: time.Date(2013, 10, 1, 0, 0, 0, 0, time.UTC), + RatingPlanId: "TDRT", + }}, } if !reflect.DeepEqual(rp, expected) { - t.Errorf("Error loading rating profile: %+v", rp.FallbackKey) + t.Errorf("Error loading rating profile: %+v", rp.RatingPlanActivations[0]) } - rp = csvr.ratingProfiles["*out:vdf:0:one"] - expected = &RatingProfile{ - DestinationMap: map[string][]*RatingPlan{ - "GERMANY": []*RatingPlan{ - &RatingPlan{ - ActivationTime: time.Date(2012, time.February, 28, 0, 0, 0, 0, time.UTC), - RateIntervals: []*RateInterval{ - &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, - }, - }, - 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, - }, - }, - 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, - }, - }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, - }, - }, - }, - }, - }, - } - if !reflect.DeepEqual(rp.DestinationMap["GERMANY"], expected.DestinationMap["GERMANY"]) { - t.Errorf("Error loading rating profile: %+v", rp.DestinationMap["GERMANY"][0]) - } - rp = csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] - if len(rp.DestinationMap["GERMANY"]) != 2 { - t.Errorf("Failed to load rating profile %+v", rp.DestinationMap["GERMANY"][0].RateIntervals[0]) - } - } func TestLoadActions(t *testing.T) { diff --git a/engine/loader_db.go b/engine/loader_db.go index 2fb86fd6c..f00ef3c1a 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -26,19 +26,19 @@ import ( ) type DbReader struct { - tpid string - storDb LoadStorage - dataDb DataStorage - 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 + tpid string + storDb LoadStorage + dataDb DataStorage + 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 } func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader { @@ -46,8 +46,9 @@ func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader c.storDb = storDB c.dataDb = storage c.tpid = tpid - c.destinationRateTimings = make(map[string][]*DestinationRateTiming) c.actionsTimings = make(map[string][]*ActionTiming) + c.ratingPlans = make(map[string]*RatingPlan) + c.ratingProfiles = make(map[string]*RatingProfile) return c } @@ -68,6 +69,18 @@ func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) { log.Print(d.Id, " : ", d.Prefixes) } } + if verbose { + log.Print("Rating plans") + } + for _, rp := range dbr.ratingPlans { + err = storage.SetRatingPlan(rp) + if err != nil { + return err + } + if verbose { + log.Print(rp.Id) + } + } if verbose { log.Print("Rating profiles") } @@ -176,8 +189,15 @@ func (dbr *DbReader) LoadDestinationRateTimings() error { if !exists { return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", drt.DestinationRatesTag)) } - drt.destinationRates = drs - dbr.destinationRateTimings[drt.Tag] = append(dbr.destinationRateTimings[drt.Tag], drt) + + plan, exists := dbr.ratingPlans[drt.Tag] + if !exists { + plan = &RatingPlan{Id: drt.Tag} + dbr.ratingPlans[drt.Tag] = plan + } + for _, dr := range drs { + plan.AddRateInterval(dr.DestinationsTag, drt.GetRateInterval(dr)) + } } return nil } @@ -192,24 +212,18 @@ func (dbr *DbReader) LoadRatingProfiles() error { if err != nil { return errors.New(fmt.Sprintf("Cannot parse activation time from %v", rp.ActivationTime)) } - drts, exists := dbr.destinationRateTimings[rp.DestRatesTimingTag] + _, exists := dbr.ratingPlans[rp.DestRatesTimingTag] if !exists { - return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", rp.DestinationMap)) - } - for _, drt := range drts { - for _, dr := range drt.destinationRates { - plan := &RatingPlan{ActivationTime: at} - plan.AddRateInterval(drt.GetRateInterval(dr)) - rp.AddRatingPlanIfNotPresent(dr.DestinationsTag, plan) - } + return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", rp.DestRatesTimingTag)) } + rp.RatingPlanActivations = append(rp.RatingPlanActivations, &RatingPlanActivation{at, rp.DestRatesTimingTag}) dbr.ratingProfiles[rp.Id] = rp } return nil } func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { - activationPeriods := make(map[string]*RatingPlan) + ratingPlans := make(map[string]*RatingPlan) resultRatingProfile := &RatingProfile{} rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag) if err != nil || len(rpm) == 0 { @@ -219,7 +233,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { Logger.Debug(fmt.Sprintf("Rating profile: %v", rpm)) resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key resultRatingProfile.Id = ratingProfile.Id // idem - at, err := utils.ParseDate(ratingProfile.ActivationTime) + _, err := utils.ParseDate(ratingProfile.ActivationTime) if err != nil { return fmt.Errorf("Cannot parse activation time from %v", ratingProfile.ActivationTime) } @@ -247,10 +261,10 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { } Logger.Debug(fmt.Sprintf("Rate: %v", rpm)) drate.rates = rt[drate.RateTag] - if _, exists := activationPeriods[destrateTiming.Tag]; !exists { - activationPeriods[destrateTiming.Tag] = &RatingPlan{} + if _, exists := ratingPlans[destrateTiming.Tag]; !exists { + ratingPlans[destrateTiming.Tag] = &RatingPlan{} } - activationPeriods[destrateTiming.Tag].AddRateInterval(destrateTiming.GetRateInterval(drate)) + ratingPlans[destrateTiming.Tag].AddRateInterval(drate.DestinationsTag, destrateTiming.GetRateInterval(drate)) dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag) if err != nil || len(dm) == 0 { return fmt.Errorf("Could not get destination id %s: %v", drate.DestinationsTag, err) @@ -258,10 +272,10 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { Logger.Debug(fmt.Sprintf("Tag: %s Destinations: %v", drate.DestinationsTag, dm)) for _, destination := range dm { Logger.Debug(fmt.Sprintf("Destination: %v", rpm)) - ap := activationPeriods[ratingProfile.DestRatesTimingTag] - newAP := &RatingPlan{ActivationTime: at} - newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) - resultRatingProfile.AddRatingPlanIfNotPresent(destination.Id, newAP) + //ap := ratingPlans[ratingProfile.DestRatesTimingTag] + //newAP := &RatingPlan{ActivationTime: at} + //newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) + //resultRatingProfile.AddRatingPlanIfNotPresent(destination.Id, newAP) dbr.dataDb.SetDestination(destination) } } diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 4d77a4579..bc204bee0 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -143,22 +143,20 @@ func NewTiming(timingInfo ...string) (rt *Timing) { type DestinationRateTiming struct { Tag string DestinationRatesTag string // intermediary used when loading from db - destinationRates []*DestinationRate Weight float64 TimingTag string // intermediary used when loading from db timing *Timing } -func NewDestinationRateTiming(destinationRates []*DestinationRate, timing *Timing, weight string) (drt *DestinationRateTiming) { +func NewDestinationRateTiming(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 } drt = &DestinationRateTiming{ - destinationRates: destinationRates, - timing: timing, - Weight: w, + timing: timing, + Weight: w, } return } diff --git a/engine/ratingplan.go b/engine/ratingplan.go index e26762a15..2f9f87e3f 100644 --- a/engine/ratingplan.go +++ b/engine/ratingplan.go @@ -18,43 +18,42 @@ along with this program. If not, see package engine -import ( - "github.com/cgrates/cgrates/cache2go" - "time" -) - /* The struture that is saved to storage. */ type RatingPlan struct { - ActivationTime time.Time - RateIntervals RateIntervalList + Id string + DestinationRates map[string]RateIntervalList } -type xCachedRatingPlans struct { - destPrefix string - aps []*RatingPlan +/* +type xCachedRatingPlan struct { + rp *RatingPlan *cache2go.XEntry } +*/ /* Adds one ore more intervals to the internal interval list only if it is not allready in the list. */ -func (rp *RatingPlan) AddRateInterval(ris ...*RateInterval) { +func (rp *RatingPlan) AddRateInterval(dId string, ris ...*RateInterval) { + if rp.DestinationRates == nil { + rp.DestinationRates = make(map[string]RateIntervalList, 1) + } for _, ri := range ris { found := false - for _, eri := range rp.RateIntervals { + for _, eri := range rp.DestinationRates[dId] { if ri.Equal(eri) { found = true break } } if !found { - rp.RateIntervals = append(rp.RateIntervals, ri) + rp.DestinationRates[dId] = append(rp.DestinationRates[dId], ri) } } } func (rp *RatingPlan) Equal(o *RatingPlan) bool { - return rp.ActivationTime == o.ActivationTime + return rp.Id == o.Id } diff --git a/engine/ratingplan_test.go b/engine/ratingplan_test.go index 69074932b..22435a39f 100644 --- a/engine/ratingplan_test.go +++ b/engine/ratingplan_test.go @@ -27,26 +27,27 @@ import ( func TestApRestoreFromStorage(t *testing.T) { cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), Direction: OUTBOUND, TOR: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm", Destination: "49"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 2 { - t.Error("Error restoring activation periods: ", cd.RatingPlans[0]) + if len(cd.RatingInfos) != 2 { + t.Error("Error restoring activation periods: ", cd.RatingInfos) } } func TestApStoreRestoreJson(t *testing.T) { - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) result, _ := json.Marshal(ap) ap1 := &RatingPlan{} json.Unmarshal(result, ap1) @@ -56,10 +57,9 @@ func TestApStoreRestoreJson(t *testing.T) { } func TestApStoreRestoreBlank(t *testing.T) { - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) result, _ := json.Marshal(ap) ap1 := RatingPlan{} json.Unmarshal(result, &ap1) @@ -69,50 +69,78 @@ func TestApStoreRestoreBlank(t *testing.T) { } func TestFallbackDirect(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + TOR: "0", + Direction: OUTBOUND, + Tenant: "CUSTOMER_2", + Subject: "danb:87.139.12.167", + Destination: "41"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 1 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingInfos) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingInfos)) } } func TestFallbackMultiple(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + TOR: "0", + Direction: OUTBOUND, + Tenant: "vdf", + Subject: "fall", + Destination: "0723045"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 2 { - t.Errorf("Error restoring rating plans: %+v", cd.RatingPlans) + if len(cd.RatingInfos) != 2 { + t.Errorf("Error restoring rating plans: %+v", cd.RatingInfos) } } func TestFallbackWithBackTrace(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + TOR: "0", + Direction: OUTBOUND, + Tenant: "CUSTOMER_2", + Subject: "danb:87.139.12.167", + Destination: "4123"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 1 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingInfos) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingInfos)) } } func TestFallbackDefault(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "one", Destination: "0723"} + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC), + TOR: "0", + Direction: OUTBOUND, + Tenant: "vdf", + Subject: "one", + Destination: "0723"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 1 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingInfos) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingInfos)) } } func TestFallbackNoInfiniteLoop(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "rif", Destination: "0721"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 0 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingInfos) != 0 { + t.Error("Error restoring activation periods: ", len(cd.RatingInfos)) } } func TestFallbackNoInfiniteLoopSelf(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "inf", Destination: "0721"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 0 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingInfos) != 0 { + t.Error("Error restoring activation periods: ", len(cd.RatingInfos)) } } @@ -132,15 +160,15 @@ func TestApAddIntervalIfNotPresent(t *testing.T) { WeekDays: []time.Weekday{time.Wednesday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{} - ap.AddRateInterval(i1) - ap.AddRateInterval(i2) - if len(ap.RateIntervals) != 1 { - t.Error("Wronfully appended interval ;)") + rp := &RatingPlan{} + rp.AddRateInterval("NAT", i1) + rp.AddRateInterval("NAT", i2) + if len(rp.DestinationRates) != 1 { + t.Error("Wronfullyrppended interval ;)") } - ap.AddRateInterval(i3) - if len(ap.RateIntervals) != 2 { - t.Error("Wronfully not appended interval ;)") + rp.AddRateInterval("NAT", i3) + if len(rp.DestinationRates["NAT"]) != 2 { + t.Error("Wronfully not appended interval ;)", rp.DestinationRates) } } @@ -155,14 +183,14 @@ func TestApAddRateIntervalGroups(t *testing.T) { Rates: RateGroups{&Rate{30 * time.Second, 2, 1 * time.Second, 1 * time.Second}}, } ap := &RatingPlan{} - ap.AddRateInterval(i1) - ap.AddRateInterval(i2) - ap.AddRateInterval(i3) - if len(ap.RateIntervals) != 1 { + ap.AddRateInterval("NAT", i1) + ap.AddRateInterval("NAT", i2) + ap.AddRateInterval("NAT", i3) + if len(ap.DestinationRates) != 1 { t.Error("Wronfully appended interval ;)") } - if len(ap.RateIntervals[0].Rates) != 1 { - t.Errorf("Group prices not formed: %#v", ap.RateIntervals[0].Rates[0]) + if len(ap.DestinationRates["NAT"][0].Rates) != 1 { + t.Errorf("Group prices not formed: %#v", ap.DestinationRates["NAT"][0].Rates[0]) } } @@ -170,14 +198,13 @@ func TestApAddRateIntervalGroups(t *testing.T) { func BenchmarkRatingPlanStoreRestoreJson(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ap1 := RatingPlan{} b.StartTimer() @@ -189,14 +216,13 @@ func BenchmarkRatingPlanStoreRestoreJson(b *testing.B) { func BenchmarkRatingPlanStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ap1 := &RatingPlan{} b.StartTimer() diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 0a9bbe84d..b512b7927 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -21,52 +21,94 @@ package engine import ( "errors" "fmt" + "github.com/cgrates/cgrates/cache2go" + "sort" + "time" ) type RatingProfile struct { - Id string - FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject - DestinationMap map[string][]*RatingPlan - Tag, Tenant, TOR, Direction, Subject, DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading + Id string + FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject + RatingPlanActivations RatingPlanActivations + Tag, Tenant, TOR, Direction, Subject string // used only for loading + DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading } -// Adds an activation period that applyes to current rating profile if not already present. -func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, plans ...*RatingPlan) { - if rp.DestinationMap == nil { - rp.DestinationMap = make(map[string][]*RatingPlan, 1) +type RatingPlanActivation struct { + ActivationTime time.Time + RatingPlanId string +} + +func (rpa *RatingPlanActivation) GetRatingPlan() (rp *RatingPlan, err error) { + if x, err := cache2go.GetCached(rpa.RatingPlanId); err != nil { + rp, err = storageGetter.GetRatingPlan(rpa.RatingPlanId) + if err == nil && rp != nil { + cache2go.Cache(rpa.RatingPlanId, rp) + } + } else { + rp = x.(*RatingPlan) } - for _, plan := range plans { - found := false - for _, existingPlan := range rp.DestinationMap[destInfo] { - if plan.Equal(existingPlan) { - existingPlan.AddRateInterval(plan.RateIntervals...) - found = true - break + return +} + +func (rpa *RatingPlanActivation) Equal(orpa *RatingPlanActivation) bool { + return rpa.ActivationTime == orpa.ActivationTime && rpa.RatingPlanId == orpa.RatingPlanId +} + +type RatingPlanActivations []*RatingPlanActivation + +func (rpas RatingPlanActivations) Len() int { + return len(rpas) +} + +func (rpas RatingPlanActivations) Swap(i, j int) { + rpas[i], rpas[j] = rpas[j], rpas[i] +} + +func (rpas RatingPlanActivations) Less(i, j int) bool { + return rpas[i].ActivationTime.Before(rpas[j].ActivationTime) +} + +func (rpas RatingPlanActivations) Sort() { + sort.Sort(rpas) +} + +type RatingInfo struct { + ActivationTime time.Time + RateIntervals RateIntervalList +} + +func (rp *RatingProfile) GetRatingPlansForPrefix(cd *CallDescriptor) (foundPrefixes []string, ris []*RatingInfo, err error) { + rp.RatingPlanActivations.Sort() + for _, rpa := range rp.RatingPlanActivations { + if rpa.ActivationTime.Before(cd.TimeEnd) { + rpl, err := rpa.GetRatingPlan() + if err != nil || rpl == nil { + Logger.Err(fmt.Sprintf("Error checking destination: %v", err)) + continue + } + bestPrecision := 0 + var rps RateIntervalList + for dId, rpls := range rpl.DestinationRates { + precision, err := storageGetter.DestinationContainsPrefix(dId, cd.Destination) + if err != nil { + Logger.Err(fmt.Sprintf("Error checking destination: %v", err)) + continue + } + if precision > bestPrecision { + bestPrecision = precision + rps = rpls + } + } + if bestPrecision > 0 { + ris = append(ris, &RatingInfo{rpa.ActivationTime, rps}) + foundPrefixes = append(foundPrefixes, cd.Destination[:bestPrecision]) } } - if !found { - rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], plan) - } } -} - -func (rp *RatingProfile) GetRatingPlansForPrefix(destPrefix string) (foundPrefix string, aps []*RatingPlan, err error) { - bestPrecision := 0 - for dId, v := range rp.DestinationMap { - precision, err := storageGetter.DestinationContainsPrefix(dId, destPrefix) - if err != nil { - Logger.Err(fmt.Sprintf("Error checking destination: %v", err)) - continue - } - if precision > bestPrecision { - bestPrecision = precision - aps = v - } - } - - if bestPrecision > 0 { - return destPrefix[:bestPrecision], aps, nil - } - - return "", nil, errors.New("not found") + if len(ris) > 0 { + return foundPrefixes, ris, nil + } + + return nil, nil, errors.New("not found") } diff --git a/engine/ratingprofile_test.go b/engine/ratingprofile_test.go index cd7afc6cc..6655c2596 100644 --- a/engine/ratingprofile_test.go +++ b/engine/ratingprofile_test.go @@ -20,21 +20,20 @@ package engine import ( "testing" - "time" ) func TestRpAddAPIfNotPresent(t *testing.T) { - ap1 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} - ap2 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} - ap3 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 1, time.UTC)} - rp := &RatingProfile{} - rp.AddRatingPlanIfNotPresent("test", ap1) - rp.AddRatingPlanIfNotPresent("test", ap2) - if len(rp.DestinationMap["test"]) != 1 { - t.Error("Wronfully appended activation period ;)", len(rp.DestinationMap["test"])) - } - rp.AddRatingPlanIfNotPresent("test", ap3) - if len(rp.DestinationMap["test"]) != 2 { - t.Error("Wronfully not appended activation period ;)", len(rp.DestinationMap["test"])) - } + /* ap1 := &RatingPlan{Id: "test1"} + ap2 := &RatingPlan{Id: "test1"} + ap3 := &RatingPlan{Id: "test2"} + rp := &RatingProfile{} + rp.AddRatingPlanIfNotPresent("test", ap1) + rp.AddRatingPlanIfNotPresent("test", ap2) + if len(rp.DestinationMap["test"]) != 1 { + t.Error("Wronfully appended activation period ;)", len(rp.DestinationMap["test"])) + } + rp.AddRatingPlanIfNotPresent("test", ap3) + if len(rp.DestinationMap["test"]) != 2 { + t.Error("Wronfully not appended activation period ;)", len(rp.DestinationMap["test"])) + }*/ } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 03fa0a14f..8cdad4262 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -31,6 +31,7 @@ import ( const ( ACTION_TIMING_PREFIX = "atm_" + RATING_PLAN_PREFIX = "rpl_" RATING_PROFILE_PREFIX = "rpf_" ACTION_PREFIX = "act_" USER_BALANCE_PREFIX = "ubl_" @@ -59,6 +60,8 @@ Interface for storage providers. */ type DataStorage interface { Storage + GetRatingPlan(string) (*RatingPlan, error) + SetRatingPlan(*RatingPlan) error GetRatingProfile(string) (*RatingProfile, error) SetRatingProfile(*RatingProfile) error GetDestination(string) (*Destination, error) diff --git a/engine/storage_map.go b/engine/storage_map.go index 15850583b..e342b27c5 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -43,6 +43,25 @@ func (ms *MapStorage) Flush() error { return nil } +func (ms *MapStorage) GetRatingPlan(key string) (rp *RatingPlan, err error) { + if values, ok := ms.dict[RATING_PLAN_PREFIX+key]; ok { + rp = new(RatingPlan) + + err = ms.ms.Unmarshal(values, rp) + } else { + return nil, errors.New("not found") + } + return +} + +func (ms *MapStorage) SetRatingPlan(rp *RatingPlan) (err error) { + result, err := ms.ms.Marshal(rp) + ms.dict[RATING_PLAN_PREFIX+rp.Id] = result + response := 0 + go historyScribe.Record(&history.Record{RATING_PLAN_PREFIX + rp.Id, rp}, &response) + return +} + func (ms *MapStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) { if values, ok := ms.dict[RATING_PROFILE_PREFIX+key]; ok { rp = new(RatingProfile) diff --git a/engine/storage_mongo.go b/engine/storage_mongo.go index f39fea872..4454a2aba 100644 --- a/engine/storage_mongo.go +++ b/engine/storage_mongo.go @@ -52,6 +52,7 @@ func NewMongoStorage(host, port, db, user, pass string) (Storage, error) { err = ndb.C("actiontimings").EnsureIndex(index) index = mgo.Index{Key: []string{"id"}, Background: true} err = ndb.C("ratingprofiles").EnsureIndex(index) + err = ndb.C("ratingplans").EnsureIndex(index) err = ndb.C("destinations").EnsureIndex(index) err = ndb.C("userbalances").EnsureIndex(index) @@ -67,6 +68,10 @@ func (ms *MongoStorage) Flush() (err error) { if err != nil { return } + err = ms.db.C("ratingplans").DropCollection() + if err != nil { + return + } err = ms.db.C("destinations").DropCollection() if err != nil { return @@ -123,6 +128,20 @@ type LogErrEntry struct { Source string } +func (ms *MongoStorage) GetRatingPlan(key string) (rp *RatingPlan, err error) { + rp = new(RatingPlan) + err = ms.db.C("ratingplans").Find(bson.M{"id": key}).One(&rp) + return +} + +func (ms *MongoStorage) SetRatingPlan(rp *RatingPlan) error { + if historyScribe != nil { + response := 0 + historyScribe.Record(&history.Record{RATING_PLAN_PREFIX + rp.Id, rp}, &response) + } + return ms.db.C("ratingplans").Insert(rp) +} + func (ms *MongoStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) { rp = new(RatingProfile) err = ms.db.C("ratingprofiles").Find(bson.M{"id": key}).One(&rp) diff --git a/engine/storage_radix.go b/engine/storage_radix.go index fb8ccd08f..3bb12beaf 100644 --- a/engine/storage_radix.go +++ b/engine/storage_radix.go @@ -59,6 +59,28 @@ func (rs *RadixStorage) Flush() (err error) { return r.Err } +func (rs *RadixStorage) GetRatingPlan(key string) (rp *RatingPlan, err error) { + if values, err := rs.db.Cmd("get", RATING_PLAN_PREFIX+key).Bytes(); err == nil { + rp = new(RatingPlan) + err = rs.ms.Unmarshal(values, rp) + } else { + return nil, err + } + return +} + +func (rs *RadixStorage) SetRatingPlan(rp *RatingPlan) (err error) { + result, err := rs.ms.Marshal(rp) + if r := rs.db.Cmd("set", RATING_PLAN_PREFIX+rp.Id, string(result)); r.Err != nil { + return r.Err + } + if err == nil && historyScribe != nil { + response := 0 + historyScribe.Record(&history.Record{RATING_PLAN_PREFIX + rp.Id, rp}, &response) + } + return +} + func (rs *RadixStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) { if values, err := rs.db.Cmd("get", RATING_PROFILE_PREFIX+key).Bytes(); err == nil { rp = new(RatingProfile) diff --git a/engine/storage_redigo.go b/engine/storage_redigo.go index 82e0b9d0d..dbae2deb8 100644 --- a/engine/storage_redigo.go +++ b/engine/storage_redigo.go @@ -59,6 +59,25 @@ func (rs *RedigoStorage) Flush() (err error) { return } +func (rs *RedigoStorage) GetRatingPlan(key string) (rp *RatingPlan, err error) { + var values []byte + if values, err = redis.Bytes(rs.db.Do("get", RATING_PLAN_PREFIX+key)); err == nil { + rp = new(RatingPlan) + err = rs.ms.Unmarshal(values, rp) + } + return +} + +func (rs *RedigoStorage) SetRatingPlan(rp *RatingPlan) (err error) { + result, err := rs.ms.Marshal(rp) + _, err = rs.db.Do("set", RATING_PLAN_PREFIX+rp.Id, result) + if err == nil && historyScribe != nil { + response := 0 + historyScribe.Record(&history.Record{RATING_PLAN_PREFIX + rp.Id, rp}, &response) + } + return +} + func (rs *RedigoStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) { var values []byte if values, err = redis.Bytes(rs.db.Do("get", RATING_PROFILE_PREFIX+key)); err == nil { diff --git a/engine/storage_redis.go b/engine/storage_redis.go index ae4447777..42db0f4ae 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -76,6 +76,25 @@ func (rs *RedisStorage) Flush() (err error) { return } +func (rs *RedisStorage) GetRatingPlan(key string) (rp *RatingPlan, err error) { + var values string + if values, err = rs.db.Get(RATING_PLAN_PREFIX + key); err == nil { + rp = new(RatingPlan) + err = rs.ms.Unmarshal([]byte(values), rp) + } + return +} + +func (rs *RedisStorage) SetRatingPlan(rp *RatingPlan) (err error) { + result, err := rs.ms.Marshal(rp) + _, err = rs.db.Set(RATING_PLAN_PREFIX+rp.Id, result) + if err == nil && historyScribe != nil { + response := 0 + historyScribe.Record(&history.Record{RATING_PLAN_PREFIX + rp.Id, rp}, &response) + } + return +} + func (rs *RedisStorage) GetRatingProfile(key string) (rp *RatingProfile, err error) { var values string if values, err = rs.db.Get(RATING_PROFILE_PREFIX + key); err == nil { diff --git a/engine/storage_test.go b/engine/storage_test.go index c917fc11f..5bd7c7634 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -124,14 +124,13 @@ func GetUB() *UserBalance { func BenchmarkMarshallerJSONStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} @@ -148,14 +147,13 @@ func BenchmarkMarshallerJSONStoreRestore(b *testing.B) { func BenchmarkMarshallerBSONStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} @@ -172,14 +170,13 @@ func BenchmarkMarshallerBSONStoreRestore(b *testing.B) { func BenchmarkMarshallerJSONBufStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} @@ -196,14 +193,13 @@ func BenchmarkMarshallerJSONBufStoreRestore(b *testing.B) { func BenchmarkMarshallerGOBStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} @@ -220,14 +216,13 @@ func BenchmarkMarshallerGOBStoreRestore(b *testing.B) { func BenchmarkMarshallerCodecMsgpackStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} @@ -244,14 +239,13 @@ func BenchmarkMarshallerCodecMsgpackStoreRestore(b *testing.B) { func BenchmarkMarshallerBincStoreRestore(b *testing.B) { b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &RatingPlan{ActivationTime: d} - ap.AddRateInterval(i) + ap := &RatingPlan{Id: "test"} + ap.AddRateInterval("NAT", i) ub := GetUB() ap1 := RatingPlan{} diff --git a/engine/timespans.go b/engine/timespans.go index c9722da8a..610717d98 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -30,7 +30,7 @@ A unit in which a call will be split that has a specific price related interval type TimeSpan struct { TimeStart, TimeEnd time.Time Cost float64 - ratingPlan *RatingPlan + ratingInfo *RatingInfo RateInterval *RateInterval CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one @@ -253,11 +253,11 @@ func (ts *TimeSpan) SplitByDuration(duration time.Duration) *TimeSpan { } // Splits the given timespan on activation period's activation time. -func (ts *TimeSpan) SplitByRatingPlan(rp *RatingPlan) (newTs *TimeSpan) { +func (ts *TimeSpan) SplitByRatingPlan(rp *RatingInfo) (newTs *TimeSpan) { if !ts.Contains(rp.ActivationTime) { return nil } - newTs = &TimeSpan{TimeStart: rp.ActivationTime, TimeEnd: ts.TimeEnd, ratingPlan: rp} + newTs = &TimeSpan{TimeStart: rp.ActivationTime, TimeEnd: ts.TimeEnd, ratingInfo: rp} newTs.CallDuration = ts.CallDuration ts.TimeEnd = rp.ActivationTime ts.SetNewCallDuration(newTs) diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 4d3eaba37..f42cf11f7 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -190,9 +190,9 @@ func TestSplitByRatingPlan(t *testing.T) { t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) t3 := time.Date(2012, time.February, 5, 17, 50, 0, 0, time.UTC) ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - ap1 := &RatingPlan{ActivationTime: t1} - ap2 := &RatingPlan{ActivationTime: t2} - ap3 := &RatingPlan{ActivationTime: t3} + ap1 := &RatingInfo{ActivationTime: t1} + ap2 := &RatingInfo{ActivationTime: t2} + ap3 := &RatingInfo{ActivationTime: t3} if ts.SplitByRatingPlan(ap1) != nil { t.Error("Error spliting on left margin") diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 27265389f..628edf704 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -647,8 +647,50 @@ func TestDebitCreditSubjectMoney(t *testing.T) { } func TestDebitCreditSubjectMixed(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 40, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} cc := &CallCost{ + Tenant: "vdf", + TOR: "0", + Direction: OUTBOUND, + Destination: "0723045326", + Timespans: []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 24, 10, 49, 10, 0, time.UTC), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 150, RateSubject: "minu"}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceUuids[0] != "testb" || + cc.Timespans[0].Increments[0].BalanceUuids[1] != "moneya" || + cc.Timespans[0].Increments[0].Duration != time.Second { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 80 { + t.Errorf("Error extracting minutes from balance: %+v, %+v", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) + } + if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != 40*time.Second { + t.Error("Error truncating extra timespans: ", cc.Timespans[0].GetDuration()) + } +} + +/* +func TestDebitCreditSubjectMixedMoreTS(t *testing.T) { + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + cc := &CallCost{ + Tenant: "vdf", + TOR: "0", Direction: OUTBOUND, Destination: "0723045326", Timespans: []*TimeSpan{ @@ -668,12 +710,13 @@ func TestDebitCreditSubjectMixed(t *testing.T) { } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50, RateSubject: "minu"}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } + t.Errorf("%+v %+v", cc.Timespans[0], cc.Timespans[1]) if cc.Timespans[0].Increments[0].BalanceUuids[0] != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) @@ -687,117 +730,6 @@ func TestDebitCreditSubjectMixed(t *testing.T) { t.Error("Error truncating extra timespans: ", cc.Timespans) } } - -/* -func TestDebitMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(&CallCost{Direction: OUTBOUND, Destination: "0723", Cost: 6}, false) - if b2.Value != 94 || err != nil { - t.Errorf("Expected %v was %v", 94, b1.Value) - } -}*/ - -/*func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(105, "0723", false) - if b2.Value != 0 || b1.Value != 5 || err != nil { - t.Log(err) - t.Errorf("Expected %v was %v", 0, b2.Value) - } -} - -func TestDebitAllMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(110, "0723", false) - if b2.Value != 0 || b1.Value != 0 || err != nil { - t.Errorf("Expected %v was %v", 0, b2.Value) - } -} - -func TestDebitMoreMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(115, "0723", false) - if b2.Value != 100 || b1.Value != 10 || err == nil { - t.Errorf("Expected %v was %v", 1000, b2.Value) - } -} - -func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(5, "0723", false) - if b2.Value != 95 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { - t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} - -func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(21, "0723", false) - if b2.Value != 79 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { - t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} - -func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(25, "0723", false) - if b2.Value != 75 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { - t.Log(b2.Value) - t.Log(b1.Value) - t.Log(err) - t.Errorf("Expected %v was %v", -4, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} - -func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(25, "0723", false) - expected := 21.0 - if b2.Value != 100 || b1.Value != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { - t.Log(b2.Value) - t.Log(b1.Value) - t.Log(err) - t.Errorf("Expected %v was %v", expected, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} - -func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(-15, "0723", false) - if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { - t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} - -func TestDebitNegativeMinuteBalance(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitCreditBalance(-15, "0723", false) - if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { - t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) - } -} */ func TestDebitSMSBalance(t *testing.T) {