refactored rating plans structure

This commit is contained in:
Radu Ioan Fericean
2013-10-21 19:18:56 +03:00
parent d03ff0a5cd
commit 3466fa103b
21 changed files with 633 additions and 691 deletions

View File

@@ -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())

View File

@@ -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}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -18,43 +18,42 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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
}

View File

@@ -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()

View File

@@ -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")
}

View File

@@ -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"]))
}*/
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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{}

View File

@@ -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)

View File

@@ -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")

View File

@@ -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) {