mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-12 02:26:26 +05:00
Merge branch 'master' of https://github.com/cgrates/cgrates
This commit is contained in:
@@ -39,17 +39,17 @@ type Action struct {
|
||||
}
|
||||
|
||||
const (
|
||||
LOG = "LOG"
|
||||
RESET_TRIGGERS = "RESET_TRIGGERS"
|
||||
SET_POSTPAID = "SET_POSTPAID"
|
||||
RESET_POSTPAID = "RESET_POSTPAID"
|
||||
SET_PREPAID = "SET_PREPAID"
|
||||
RESET_PREPAID = "RESET_PREPAID"
|
||||
TOPUP_RESET = "TOPUP_RESET"
|
||||
TOPUP = "TOPUP"
|
||||
DEBIT = "DEBIT"
|
||||
RESET_COUNTER = "RESET_COUNTER"
|
||||
RESET_COUNTERS = "RESET_COUNTERS"
|
||||
LOG = "*log"
|
||||
RESET_TRIGGERS = "*reset_triggers"
|
||||
SET_POSTPAID = "*set_postpaid"
|
||||
RESET_POSTPAID = "*reset_postpaid"
|
||||
SET_PREPAID = "*set_prepaid"
|
||||
RESET_PREPAID = "*reset_prepaid"
|
||||
TOPUP_RESET = "*topup_reset"
|
||||
TOPUP = "*topup"
|
||||
DEBIT = "*debit"
|
||||
RESET_COUNTER = "*reset_counter"
|
||||
RESET_COUNTERS = "*reset_counters"
|
||||
)
|
||||
|
||||
type actionTypeFunc func(*UserBalance, *Action) error
|
||||
|
||||
@@ -757,7 +757,7 @@ func TestActionTimingLogging(t *testing.T) {
|
||||
EndTime: "00:00:00",
|
||||
Weight: 10.0,
|
||||
ConnectFee: 0.0,
|
||||
Price: 1.0,
|
||||
Prices: PriceGroups{&Price{0, 1.0}},
|
||||
PricedUnits: 60,
|
||||
RateIncrements: 1,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ The struture that is saved to storage.
|
||||
*/
|
||||
type ActivationPeriod struct {
|
||||
ActivationTime time.Time
|
||||
Intervals []*Interval
|
||||
Intervals IntervalList
|
||||
}
|
||||
|
||||
type xCachedActivationPeriods struct {
|
||||
@@ -37,21 +37,15 @@ type xCachedActivationPeriods struct {
|
||||
*cache2go.XEntry
|
||||
}
|
||||
|
||||
/*
|
||||
Adds one ore more intervals to the internal interval list.
|
||||
*/
|
||||
func (ap *ActivationPeriod) AddInterval(is ...*Interval) {
|
||||
ap.Intervals = append(ap.Intervals, is...)
|
||||
}
|
||||
|
||||
/*
|
||||
Adds one ore more intervals to the internal interval list only if it is not allready in the list.
|
||||
*/
|
||||
func (ap *ActivationPeriod) AddIntervalIfNotPresent(is ...*Interval) {
|
||||
func (ap *ActivationPeriod) AddInterval(is ...*Interval) {
|
||||
for _, i := range is {
|
||||
found := false
|
||||
for _, ei := range ap.Intervals {
|
||||
if i.Equal(ei) {
|
||||
(&ei.Prices).AddPrice(i.Prices...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
@@ -20,7 +20,6 @@ package rater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
//"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -28,7 +27,7 @@ import (
|
||||
|
||||
func TestApRestoreFromStorage(t *testing.T) {
|
||||
cd := &CallDescriptor{
|
||||
Direction: "OUT",
|
||||
Direction: OUTBOUND,
|
||||
TOR: "0",
|
||||
Tenant: "CUSTOMER_1",
|
||||
Subject: "rif:from:tm",
|
||||
@@ -49,7 +48,7 @@ func TestApStoreRestoreJson(t *testing.T) {
|
||||
ap := &ActivationPeriod{ActivationTime: d}
|
||||
ap.AddInterval(i)
|
||||
result, _ := json.Marshal(ap)
|
||||
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
|
||||
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
|
||||
if string(result) != expected {
|
||||
t.Errorf("Expected %q was %q", expected, result)
|
||||
}
|
||||
@@ -66,7 +65,7 @@ func TestApStoreRestoreBlank(t *testing.T) {
|
||||
ap := &ActivationPeriod{ActivationTime: d}
|
||||
ap.AddInterval(i)
|
||||
result, _ := json.Marshal(ap)
|
||||
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
|
||||
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
|
||||
if string(result) != expected {
|
||||
t.Errorf("Expected %q was %q", expected, result)
|
||||
}
|
||||
@@ -78,7 +77,7 @@ func TestApStoreRestoreBlank(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackDirect(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -86,7 +85,7 @@ func TestFallbackDirect(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackMultiple(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "fall", Destination: "0723045"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -94,7 +93,7 @@ func TestFallbackMultiple(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackWithBackTrace(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -102,7 +101,7 @@ func TestFallbackWithBackTrace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackDefault(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "one", Destination: "0723"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "one", Destination: "0723"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -110,7 +109,7 @@ func TestFallbackDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackNoInfiniteLoop(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "rif", Destination: "0721"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "rif", Destination: "0721"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 0 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -118,7 +117,7 @@ func TestFallbackNoInfiniteLoop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackNoInfiniteLoopSelf(t *testing.T) {
|
||||
cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "inf", Destination: "0721"}
|
||||
cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "inf", Destination: "0721"}
|
||||
cd.LoadActivationPeriods()
|
||||
if len(cd.ActivationPeriods) != 0 {
|
||||
t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods))
|
||||
@@ -142,17 +141,39 @@ func TestApAddIntervalIfNotPresent(t *testing.T) {
|
||||
StartTime: "14:30:00",
|
||||
EndTime: "15:00:00"}
|
||||
ap := &ActivationPeriod{}
|
||||
ap.AddIntervalIfNotPresent(i1)
|
||||
ap.AddIntervalIfNotPresent(i2)
|
||||
ap.AddInterval(i1)
|
||||
ap.AddInterval(i2)
|
||||
if len(ap.Intervals) != 1 {
|
||||
t.Error("Wronfully appended interval ;)")
|
||||
}
|
||||
ap.AddIntervalIfNotPresent(i3)
|
||||
ap.AddInterval(i3)
|
||||
if len(ap.Intervals) != 2 {
|
||||
t.Error("Wronfully not appended interval ;)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApAddIntervalGroups(t *testing.T) {
|
||||
i1 := &Interval{
|
||||
Prices: PriceGroups{&Price{0, 1}},
|
||||
}
|
||||
i2 := &Interval{
|
||||
Prices: PriceGroups{&Price{30, 2}},
|
||||
}
|
||||
i3 := &Interval{
|
||||
Prices: PriceGroups{&Price{30, 2}},
|
||||
}
|
||||
ap := &ActivationPeriod{}
|
||||
ap.AddInterval(i1)
|
||||
ap.AddInterval(i2)
|
||||
ap.AddInterval(i3)
|
||||
if len(ap.Intervals) != 1 {
|
||||
t.Error("Wronfully appended interval ;)")
|
||||
}
|
||||
if len(ap.Intervals[0].Prices) != 2 {
|
||||
t.Error("Group prices not formed: ", ap.Intervals[0].Prices)
|
||||
}
|
||||
}
|
||||
|
||||
/**************************** Benchmarks *************************************/
|
||||
|
||||
func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) {
|
||||
|
||||
@@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
@@ -27,14 +27,14 @@ import (
|
||||
func TestSingleResultMerge(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 2, 17, 00, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 60 {
|
||||
t.Errorf("expected 60 was %v", cc1.Cost)
|
||||
}
|
||||
t1 = time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC)
|
||||
t2 = time.Date(2012, time.February, 2, 17, 02, 0, 0, time.UTC)
|
||||
cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 60 {
|
||||
t.Errorf("expected 60 was %v", cc2.Cost)
|
||||
@@ -51,7 +51,7 @@ func TestSingleResultMerge(t *testing.T) {
|
||||
func TestMultipleResultMerge(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 60 {
|
||||
t.Errorf("expected 60 was %v", cc1.Cost)
|
||||
@@ -61,7 +61,7 @@ func TestMultipleResultMerge(t *testing.T) {
|
||||
}
|
||||
t1 = time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC)
|
||||
t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC)
|
||||
cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 30 {
|
||||
t.Errorf("expected 30 was %v", cc2.Cost)
|
||||
@@ -81,14 +81,14 @@ func TestMultipleResultMerge(t *testing.T) {
|
||||
func TestMultipleInputLeftMerge(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 90 {
|
||||
t.Errorf("expected 90 was %v", cc1.Cost)
|
||||
}
|
||||
t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC)
|
||||
t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC)
|
||||
cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 30 {
|
||||
t.Errorf("expected 30 was %v", cc2.Cost)
|
||||
@@ -105,14 +105,14 @@ func TestMultipleInputLeftMerge(t *testing.T) {
|
||||
func TestMultipleInputRightMerge(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 2, 17, 58, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 60 {
|
||||
t.Errorf("expected 60 was %v", cc1.Cost)
|
||||
}
|
||||
t1 = time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC)
|
||||
t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC)
|
||||
cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 90 {
|
||||
t.Errorf("expected 90 was %v", cc2.Cost)
|
||||
|
||||
@@ -40,7 +40,7 @@ func init() {
|
||||
|
||||
const (
|
||||
RECURSION_MAX_DEPTH = 10
|
||||
FALLBACK_SUBJECT = "*all"
|
||||
FALLBACK_SUBJECT = "*any"
|
||||
FALLBACK_SEP = ";"
|
||||
)
|
||||
|
||||
@@ -189,17 +189,13 @@ func (cd *CallDescriptor) GetKey() string {
|
||||
return fmt.Sprintf("%s:%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, cd.Subject)
|
||||
}
|
||||
|
||||
/*
|
||||
Splits the call descriptor timespan into sub time spans according to the activation periods intervals.
|
||||
*/
|
||||
func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) {
|
||||
return cd.splitTimeSpan(&TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd})
|
||||
}
|
||||
|
||||
/*
|
||||
Splits the received timespan into sub time spans according to the activation periods intervals.
|
||||
*/
|
||||
func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeSpan) {
|
||||
func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*TimeSpan) {
|
||||
if firstSpan == nil {
|
||||
firstSpan = &TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd}
|
||||
}
|
||||
timespans = append(timespans, firstSpan)
|
||||
// split on (free) minute buckets
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
@@ -250,11 +246,15 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS
|
||||
// split on price intervals
|
||||
for i := 0; i < len(timespans); i++ {
|
||||
if timespans[i].MinuteInfo != nil {
|
||||
continue
|
||||
continue // cont try to split timespans payed with minutes
|
||||
}
|
||||
ap := timespans[i].ActivationPeriod
|
||||
//timespans[i].ActivationPeriod = nil
|
||||
ap.Intervals.Sort()
|
||||
for _, interval := range ap.Intervals {
|
||||
if timespans[i].Interval != nil && timespans[i].Interval.Weight < interval.Weight {
|
||||
continue // if the timespan has an interval than it already has a heigher weight
|
||||
}
|
||||
newTs := timespans[i].SplitByInterval(interval)
|
||||
if newTs != nil {
|
||||
newTs.ActivationPeriod = ap
|
||||
@@ -274,7 +274,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err))
|
||||
return &CallCost{}, err
|
||||
}
|
||||
timespans := cd.splitInTimeSpans()
|
||||
timespans := cd.splitInTimeSpans(nil)
|
||||
cost := 0.0
|
||||
connectionFee := 0.0
|
||||
|
||||
@@ -336,7 +336,7 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
maxDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", maxSessionSeconds-availableSeconds))
|
||||
ts := &TimeSpan{TimeStart: now, TimeEnd: now.Add(maxDuration)}
|
||||
timespans := cd.splitTimeSpan(ts)
|
||||
timespans := cd.splitInTimeSpans(ts)
|
||||
|
||||
cost := 0.0
|
||||
for i, ts := range timespans {
|
||||
|
||||
@@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
@@ -30,7 +30,7 @@ func init() {
|
||||
|
||||
func populateDB() {
|
||||
minu := &UserBalance{
|
||||
Id: "OUT:vdf:minu",
|
||||
Id: "*out:vdf:minu",
|
||||
Type: UB_TYPE_PREPAID,
|
||||
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}},
|
||||
MinuteBuckets: []*MinuteBucket{
|
||||
@@ -39,7 +39,7 @@ func populateDB() {
|
||||
},
|
||||
}
|
||||
broker := &UserBalance{
|
||||
Id: "OUT:vdf:broker",
|
||||
Id: "*out:vdf:broker",
|
||||
Type: UB_TYPE_PREPAID,
|
||||
MinuteBuckets: []*MinuteBucket{
|
||||
&MinuteBucket{Seconds: 20, DestinationId: "NAT", Weight: 10, Price: 1},
|
||||
@@ -58,10 +58,10 @@ func populateDB() {
|
||||
func TestSplitSpans(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
|
||||
cd.LoadActivationPeriods()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
timespans := cd.splitInTimeSpans(nil)
|
||||
if len(timespans) != 2 {
|
||||
t.Log(cd.ActivationPeriods)
|
||||
t.Error("Wrong number of timespans: ", len(timespans))
|
||||
@@ -71,10 +71,10 @@ func TestSplitSpans(t *testing.T) {
|
||||
func TestRedisSplitSpans(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2}
|
||||
|
||||
cd.LoadActivationPeriods()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
timespans := cd.splitInTimeSpans(nil)
|
||||
if len(timespans) != 2 {
|
||||
t.Log(cd.ActivationPeriods)
|
||||
t.Error("Wrong number of timespans: ", len(timespans))
|
||||
@@ -84,7 +84,7 @@ func TestRedisSplitSpans(t *testing.T) {
|
||||
func TestGetCost(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -95,7 +95,7 @@ func TestGetCost(t *testing.T) {
|
||||
func TestGetCostNoConnectFee(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 1}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 1}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -106,7 +106,7 @@ func TestGetCostNoConnectFee(t *testing.T) {
|
||||
func TestGetCostAccount(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Account: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Account: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -117,7 +117,7 @@ func TestGetCostAccount(t *testing.T) {
|
||||
func TestFullDestNotFound(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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -129,7 +129,7 @@ func TestFullDestNotFound(t *testing.T) {
|
||||
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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "not_exiting", Destination: "025740532", TimeStart: t1, TimeEnd: t2}
|
||||
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 {
|
||||
@@ -141,7 +141,7 @@ func TestSubjectNotFound(t *testing.T) {
|
||||
func TestMultipleActivationPeriods(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 8, 17, 30, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 8, 18, 30, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", 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 {
|
||||
@@ -153,7 +153,7 @@ func TestMultipleActivationPeriods(t *testing.T) {
|
||||
func TestSpansMultipleActivationPeriods(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 7, 23, 50, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 8, 0, 30, 0, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 1200, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -164,7 +164,7 @@ func TestSpansMultipleActivationPeriods(t *testing.T) {
|
||||
func TestLessThanAMinute(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 8, 23, 50, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 8, 23, 50, 30, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 15, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -175,7 +175,7 @@ func TestLessThanAMinute(t *testing.T) {
|
||||
func TestUniquePrice(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0723", Cost: 1810.5, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -186,7 +186,7 @@ func TestUniquePrice(t *testing.T) {
|
||||
func TestMinutesCost(t *testing.T) {
|
||||
t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 8, 22, 51, 50, 0, time.UTC)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 55, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
@@ -195,7 +195,7 @@ 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{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", Amount: 1000}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
if result != 1000 || err == nil {
|
||||
t.Errorf("Expected %v was %v (%v)", 1000, result, err)
|
||||
@@ -203,7 +203,7 @@ 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{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu", Destination: "0723", Amount: 1000}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
expected := 300.0
|
||||
if result != expected || err != nil {
|
||||
@@ -212,7 +212,7 @@ 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{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu_from_tm", Account: "minu", Destination: "0723", Amount: 1000}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
expected := 300.0
|
||||
if result != expected || err != nil {
|
||||
@@ -221,7 +221,7 @@ 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{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "broker", Destination: "0723", Amount: 5400}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
if result != 100 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 100, result)
|
||||
@@ -233,7 +233,7 @@ func BenchmarkStorageGetting(b *testing.B) {
|
||||
b.StopTimer()
|
||||
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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
storageGetter.GetRatingProfile(cd.GetKey())
|
||||
@@ -244,7 +244,7 @@ func BenchmarkStorageRestoring(b *testing.B) {
|
||||
b.StopTimer()
|
||||
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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.LoadActivationPeriods()
|
||||
@@ -255,7 +255,7 @@ func BenchmarkStorageGetCost(b *testing.B) {
|
||||
b.StopTimer()
|
||||
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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetCost()
|
||||
@@ -266,11 +266,11 @@ func BenchmarkSplitting(b *testing.B) {
|
||||
b.StopTimer()
|
||||
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)
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd.LoadActivationPeriods()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.splitInTimeSpans()
|
||||
cd.splitInTimeSpans(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ func BenchmarkStorageSingleGetSessionTime(b *testing.B) {
|
||||
|
||||
func BenchmarkStorageMultipleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minutosu", Destination: "0723", Amount: 5400}
|
||||
cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minutosu", Destination: "0723", Amount: 5400}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
|
||||
@@ -61,7 +61,7 @@ func (ys Years) Contains(year int) (result bool) {
|
||||
// Parse Years elements from string separated by sep.
|
||||
func (ys *Years) Parse(input, sep string) {
|
||||
switch input {
|
||||
case "*all", "":
|
||||
case "*any", "":
|
||||
*ys = []int{}
|
||||
default:
|
||||
elements := strings.Split(input, sep)
|
||||
@@ -75,7 +75,7 @@ func (ys *Years) Parse(input, sep string) {
|
||||
|
||||
func (ys Years) Serialize(sep string) string {
|
||||
if len(ys) == 0 {
|
||||
return "*all"
|
||||
return "*any"
|
||||
}
|
||||
var yStr string
|
||||
for idx, yr := range ys {
|
||||
@@ -124,7 +124,7 @@ func (m Months) Contains(month time.Month) (result bool) {
|
||||
// Loades Month elemnents from a string separated by sep.
|
||||
func (m *Months) Parse(input, sep string) {
|
||||
switch input {
|
||||
case "*all":
|
||||
case "*any":
|
||||
*m = allMonths
|
||||
case "*none": // Apier cannot receive empty string, hence using meta-tag
|
||||
*m = []time.Month{}
|
||||
@@ -146,7 +146,7 @@ func (m Months) Serialize(sep string) string {
|
||||
return "*none"
|
||||
}
|
||||
if reflect.DeepEqual(m, Months(allMonths)) {
|
||||
return "*all"
|
||||
return "*any"
|
||||
}
|
||||
var mStr string
|
||||
for idx, mt := range m {
|
||||
@@ -195,7 +195,7 @@ func (md MonthDays) Contains(monthDay int) (result bool) {
|
||||
// Parse MonthDay elements from string separated by sep.
|
||||
func (md *MonthDays) Parse(input, sep string) {
|
||||
switch input {
|
||||
case "*all":
|
||||
case "*any":
|
||||
*md = allMonthDays
|
||||
case "":
|
||||
*md = []int{}
|
||||
@@ -215,7 +215,7 @@ func (md MonthDays) Serialize(sep string) string {
|
||||
return "*none"
|
||||
}
|
||||
if reflect.DeepEqual(md, MonthDays(allMonthDays)) {
|
||||
return "*all"
|
||||
return "*any"
|
||||
}
|
||||
var mdsStr string
|
||||
for idx, mDay := range md {
|
||||
@@ -263,7 +263,7 @@ func (wd WeekDays) Contains(weekDay time.Weekday) (result bool) {
|
||||
|
||||
func (wd *WeekDays) Parse(input, sep string) {
|
||||
switch input {
|
||||
case "*all":
|
||||
case "*any":
|
||||
*wd = allWeekDays
|
||||
case "":
|
||||
*wd = []time.Weekday{}
|
||||
@@ -283,7 +283,7 @@ func (wd WeekDays) Serialize(sep string) string {
|
||||
return "*none"
|
||||
}
|
||||
if reflect.DeepEqual(wd, WeekDays(allWeekDays)) {
|
||||
return "*all"
|
||||
return "*any"
|
||||
}
|
||||
var wdStr string
|
||||
for idx, d := range wd {
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestWeekDayStoreRestoreJson(t *testing.T) {
|
||||
func TestYearsSerialize(t *testing.T) {
|
||||
ys := &Years{}
|
||||
yString := ys.Serialize(";")
|
||||
expectString := "*all"
|
||||
expectString := "*any"
|
||||
if expectString != yString {
|
||||
t.Errorf("Expected: %s, got: %s", expectString, yString)
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func TestMonthsSerialize(t *testing.T) {
|
||||
}
|
||||
mths1 := Months(allMonths)
|
||||
mString1 := mths1.Serialize(";")
|
||||
expectString1 := "*all"
|
||||
expectString1 := "*any"
|
||||
if expectString1 != mString1 {
|
||||
t.Errorf("Expected: %s, got: %s", expectString1, mString1)
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func TestMonthDaysSerialize(t *testing.T) {
|
||||
}
|
||||
mds1 := MonthDays(allMonthDays)
|
||||
mdsString1 := mds1.Serialize(";")
|
||||
expectString1 := "*all"
|
||||
expectString1 := "*any"
|
||||
if expectString1 != mdsString1 {
|
||||
t.Errorf("Expected: %s, got: %s", expectString1, mdsString1)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func TestWeekDaysSerialize(t *testing.T) {
|
||||
}
|
||||
wds1 := WeekDays(allWeekDays)
|
||||
wdsString1 := wds1.Serialize(";")
|
||||
expectString1 := "*all"
|
||||
expectString1 := "*any"
|
||||
if expectString1 != wdsString1 {
|
||||
t.Errorf("Expected: %s, got: %s", expectString1, wdsString1)
|
||||
}
|
||||
|
||||
@@ -23,24 +23,79 @@ import (
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
// "log"
|
||||
)
|
||||
|
||||
/*
|
||||
Defines a time interval for which a certain set of prices will apply
|
||||
*/
|
||||
type Interval struct {
|
||||
Years Years
|
||||
Months Months
|
||||
MonthDays MonthDays
|
||||
WeekDays WeekDays
|
||||
StartTime, EndTime string // ##:##:## format
|
||||
Weight, ConnectFee, Price, PricedUnits, RateIncrements float64
|
||||
RoundingMethod string
|
||||
RoundingDecimals int
|
||||
Years Years
|
||||
Months Months
|
||||
MonthDays MonthDays
|
||||
WeekDays WeekDays
|
||||
StartTime, EndTime string // ##:##:## format
|
||||
Weight, ConnectFee, PricedUnits, RateIncrements float64
|
||||
Prices PriceGroups // GroupInterval (start time): Price
|
||||
RoundingMethod string
|
||||
RoundingDecimals int
|
||||
}
|
||||
|
||||
type Price struct {
|
||||
StartSecond float64
|
||||
Value float64
|
||||
}
|
||||
|
||||
func (p *Price) Equal(o *Price) bool {
|
||||
return p.StartSecond == o.StartSecond && p.Value == o.Value
|
||||
}
|
||||
|
||||
type PriceGroups []*Price
|
||||
|
||||
func (pg PriceGroups) Len() int {
|
||||
return len(pg)
|
||||
}
|
||||
|
||||
func (pg PriceGroups) Swap(i, j int) {
|
||||
pg[i], pg[j] = pg[j], pg[i]
|
||||
}
|
||||
|
||||
func (pg PriceGroups) Less(i, j int) bool {
|
||||
return pg[i].StartSecond < pg[j].StartSecond
|
||||
}
|
||||
|
||||
func (pg PriceGroups) Sort() {
|
||||
sort.Sort(pg)
|
||||
}
|
||||
|
||||
func (pg PriceGroups) Equal(og PriceGroups) bool {
|
||||
if len(pg) != len(og) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(pg); i++ {
|
||||
if !pg[i].Equal(og[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (pg *PriceGroups) AddPrice(ps ...*Price) {
|
||||
for _, p := range ps {
|
||||
found := false
|
||||
for _, op := range *pg {
|
||||
if op.Equal(p) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
*pg = append(*pg, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -137,12 +192,42 @@ func (i *Interval) Equal(o *Interval) bool {
|
||||
i.EndTime == o.EndTime
|
||||
}
|
||||
|
||||
func (i *Interval) GetCost(duration float64) (cost float64) {
|
||||
|
||||
func (i *Interval) GetCost(duration, startSecond float64) (cost float64) {
|
||||
if i.PricedUnits != 0 {
|
||||
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.Price / i.PricedUnits)
|
||||
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.GetPrice(startSecond) / i.PricedUnits)
|
||||
} else {
|
||||
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.Price
|
||||
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.GetPrice(startSecond)
|
||||
}
|
||||
return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod)
|
||||
}
|
||||
|
||||
// gets the price for a the provided start second
|
||||
func (i *Interval) GetPrice(startSecond float64) float64 {
|
||||
i.Prices.Sort()
|
||||
for index, price := range i.Prices {
|
||||
if price.StartSecond <= startSecond && (index == len(i.Prices)-1 ||
|
||||
i.Prices[index+1].StartSecond > startSecond) {
|
||||
return price.Value
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Structure to store intervals according to weight
|
||||
type IntervalList []*Interval
|
||||
|
||||
func (il IntervalList) Len() int {
|
||||
return len(il)
|
||||
}
|
||||
|
||||
func (il IntervalList) Swap(i, j int) {
|
||||
il[i], il[j] = il[j], il[i]
|
||||
}
|
||||
|
||||
func (il IntervalList) Less(i, j int) bool {
|
||||
return il[i].Weight < il[j].Weight
|
||||
}
|
||||
|
||||
func (il IntervalList) Sort() {
|
||||
sort.Sort(il)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (csvr *CSVReader) LoadRates() (err error) {
|
||||
continue
|
||||
}
|
||||
var r *Rate
|
||||
r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7])
|
||||
r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -306,7 +306,7 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) {
|
||||
if _, exists := csvr.activationPeriods[tag]; !exists {
|
||||
csvr.activationPeriods[tag] = &ActivationPeriod{}
|
||||
}
|
||||
csvr.activationPeriods[tag].AddIntervalIfNotPresent(rt.GetInterval(dr))
|
||||
csvr.activationPeriods[tag].AddInterval(rt.GetInterval(dr))
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package rater
|
||||
|
||||
import (
|
||||
//"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -38,17 +39,17 @@ RET,0723
|
||||
RET,0724
|
||||
`
|
||||
timings = `
|
||||
WORKDAYS_00,*all,*all,*all,1;2;3;4;5,00:00:00
|
||||
WORKDAYS_18,*all,*all,*all,1;2;3;4;5,18:00:00
|
||||
WEEKENDS,*all,*all,*all,6;7,00:00:00
|
||||
WORKDAYS_00,*any,*any,*any,1;2;3;4;5,00:00:00
|
||||
WORKDAYS_18,*any,*any,*any,1;2;3;4;5,18:00:00
|
||||
WEEKENDS,*any,*any,*any,6;7,00:00:00
|
||||
ONE_TIME_RUN,2012,,,,*asap
|
||||
`
|
||||
rates = `
|
||||
R1,0,0.2,60,1,*middle,2,10
|
||||
R2,0,0.1,60,1,*middle,2,10
|
||||
R3,0,0.05,60,1,*middle,2,10
|
||||
R4,1,1,1,1,*up,2,10
|
||||
R5,0,0.5,1,1,*down,2,10
|
||||
R1,0,0.2,60,1,0,*middle,2,10
|
||||
R2,0,0.1,60,1,0,*middle,2,10
|
||||
R3,0,0.05,60,1,0,*middle,2,10
|
||||
R4,1,1,1,1,0,*up,2,10
|
||||
R5,0,0.5,1,1,0,*down,2,10
|
||||
`
|
||||
destinationRates = `
|
||||
RT_STANDARD,GERMANY,R1
|
||||
@@ -71,30 +72,30 @@ EVENING,P2,WORKDAYS_18,10
|
||||
EVENING,P2,WEEKENDS,10
|
||||
`
|
||||
ratingProfiles = `
|
||||
CUSTOMER_1,0,OUT,rif:from:tm,danb,PREMIUM,2012-01-01T00:00:00Z
|
||||
CUSTOMER_1,0,OUT,rif:from:tm,danb,STANDARD,2012-02-28T00:00:00Z
|
||||
CUSTOMER_2,0,OUT,danb:87.139.12.167,danb,STANDARD,2012-01-01T00:00:00Z
|
||||
CUSTOMER_1,0,OUT,danb,,PREMIUM,2012-01-01T00:00:00Z
|
||||
vdf,0,OUT,rif,,EVENING,2012-01-01T00:00:00Z
|
||||
vdf,0,OUT,rif,,EVENING,2012-02-28T00:00:00Z
|
||||
vdf,0,OUT,minu,,EVENING,2012-01-01T00:00:00Z
|
||||
vdf,0,OUT,*all,,EVENING,2012-02-28T00:00:00Z
|
||||
vdf,0,OUT,one,,STANDARD,2012-02-28T00:00:00Z
|
||||
vdf,0,OUT,inf,inf,STANDARD,2012-02-28T00:00:00Z
|
||||
vdf,0,OUT,fall,one|rif,PREMIUM,2012-02-28T00:00:00Z
|
||||
CUSTOMER_1,0,*out,rif:from:tm,danb,PREMIUM,2012-01-01T00:00:00Z
|
||||
CUSTOMER_1,0,*out,rif:from:tm,danb,STANDARD,2012-02-28T00:00:00Z
|
||||
CUSTOMER_2,0,*out,danb:87.139.12.167,danb,STANDARD,2012-01-01T00:00:00Z
|
||||
CUSTOMER_1,0,*out,danb,,PREMIUM,2012-01-01T00:00:00Z
|
||||
vdf,0,*out,rif,,EVENING,2012-01-01T00:00:00Z
|
||||
vdf,0,*out,rif,,EVENING,2012-02-28T00:00:00Z
|
||||
vdf,0,*out,minu,,EVENING,2012-01-01T00:00:00Z
|
||||
vdf,0,*out,*any,,EVENING,2012-02-28T00:00:00Z
|
||||
vdf,0,*out,one,,STANDARD,2012-02-28T00:00:00Z
|
||||
vdf,0,*out,inf,inf,STANDARD,2012-02-28T00:00:00Z
|
||||
vdf,0,*out,fall,one|rif,PREMIUM,2012-02-28T00:00:00Z
|
||||
`
|
||||
actions = `
|
||||
MINI,TOPUP,MINUTES,OUT,100,1374239002,NAT,ABSOLUTE,0,10,10
|
||||
MINI,TOPUP,MINUTES,*out,100,1374239002,NAT,*absolute,0,10,10
|
||||
`
|
||||
actionTimings = `
|
||||
MORE_MINUTES,MINI,ONE_TIME_RUN,10
|
||||
`
|
||||
actionTriggers = `
|
||||
STANDARD_TRIGGER,MINUTES,OUT,COUNTER,10,GERMANY_O2,SOME_1,10
|
||||
STANDARD_TRIGGER,MINUTES,OUT,BALANCE,200,GERMANY,SOME_2,10
|
||||
STANDARD_TRIGGER,MINUTES,*out,COUNTER,10,GERMANY_O2,SOME_1,10
|
||||
STANDARD_TRIGGER,MINUTES,*out,BALANCE,200,GERMANY,SOME_2,10
|
||||
`
|
||||
accountActions = `
|
||||
vdf,minitsboy,OUT,MORE_MINUTES,STANDARD_TRIGGER
|
||||
vdf,minitsboy,*out,MORE_MINUTES,STANDARD_TRIGGER
|
||||
`
|
||||
)
|
||||
|
||||
@@ -143,6 +144,9 @@ func TestLoadDestinationRateTimings(t *testing.T) {
|
||||
if len(csvr.activationPeriods) != 4 {
|
||||
t.Error("Failed to load rate timings: ", csvr.activationPeriods)
|
||||
}
|
||||
//for _, ap := range csvr.activationPeriods {
|
||||
//log.Print(ap.Intervals[0].Prices[1])
|
||||
//}
|
||||
}
|
||||
|
||||
func TestLoadRatingProfiles(t *testing.T) {
|
||||
|
||||
@@ -172,7 +172,7 @@ func (dbr *DbReader) LoadDestinationRateTimings() error {
|
||||
if !exists {
|
||||
dbr.activationPeriods[rt.Tag] = &ActivationPeriod{}
|
||||
}
|
||||
dbr.activationPeriods[rt.Tag].AddIntervalIfNotPresent(rt.GetInterval(dr))
|
||||
dbr.activationPeriods[rt.Tag].AddInterval(rt.GetInterval(dr))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -243,7 +243,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error {
|
||||
if _, exists := activationPeriods[destrateTiming.Tag]; !exists {
|
||||
activationPeriods[destrateTiming.Tag] = &ActivationPeriod{}
|
||||
}
|
||||
activationPeriods[destrateTiming.Tag].AddIntervalIfNotPresent(destrateTiming.GetInterval(drate))
|
||||
activationPeriods[destrateTiming.Tag].AddInterval(destrateTiming.GetInterval(drate))
|
||||
dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -43,14 +43,14 @@ type TPLoader interface {
|
||||
}
|
||||
|
||||
type Rate struct {
|
||||
Tag string
|
||||
ConnectFee, Price, PricedUnits, RateIncrements float64
|
||||
RoundingMethod string
|
||||
RoundingDecimals int
|
||||
Weight float64
|
||||
Tag string
|
||||
ConnectFee, Price, PricedUnits, RateIncrements, GroupInterval float64
|
||||
RoundingMethod string
|
||||
RoundingDecimals int
|
||||
Weight float64
|
||||
}
|
||||
|
||||
func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) {
|
||||
func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, groupInterval, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) {
|
||||
cf, err := strconv.ParseFloat(connectFee, 64)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing connect fee from: %v", connectFee)
|
||||
@@ -61,6 +61,11 @@ func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod
|
||||
log.Printf("Error parsing price from: %v", price)
|
||||
return
|
||||
}
|
||||
gi, err := strconv.ParseFloat(groupInterval, 64)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing group interval from: %v", price)
|
||||
return
|
||||
}
|
||||
pu, err := strconv.ParseFloat(pricedUnits, 64)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing priced units from: %v", pricedUnits)
|
||||
@@ -86,6 +91,7 @@ func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod
|
||||
Tag: tag,
|
||||
ConnectFee: cf,
|
||||
Price: p,
|
||||
GroupInterval: gi,
|
||||
PricedUnits: pu,
|
||||
RateIncrements: ri,
|
||||
Weight: wght,
|
||||
@@ -153,7 +159,7 @@ func (rt *DestinationRateTiming) GetInterval(dr *DestinationRate) (i *Interval)
|
||||
StartTime: rt.timing.StartTime,
|
||||
Weight: rt.Weight,
|
||||
ConnectFee: dr.Rate.ConnectFee,
|
||||
Price: dr.Rate.Price,
|
||||
Prices: PriceGroups{&Price{dr.Rate.GroupInterval, dr.Rate.Price}},
|
||||
PricedUnits: dr.Rate.PricedUnits,
|
||||
RateIncrements: dr.Rate.RateIncrements,
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ type MinuteBucket struct {
|
||||
}
|
||||
|
||||
const (
|
||||
PERCENT = "PERCENT"
|
||||
ABSOLUTE = "ABSOLUTE"
|
||||
PERCENT = "*percent"
|
||||
ABSOLUTE = "*absolute"
|
||||
)
|
||||
|
||||
// Returns the available number of seconds for a specified credit
|
||||
|
||||
@@ -456,6 +456,38 @@ func (d *Destination) Restore(input string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pg PriceGroups) Store() (result string, err error) {
|
||||
for _, p := range pg {
|
||||
result += strconv.FormatFloat(p.StartSecond, 'f', -1, 64) + ":" + strconv.FormatFloat(p.Value, 'f', -1, 64) + ","
|
||||
}
|
||||
result = strings.TrimRight(result, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pg *PriceGroups) Restore(input string) error {
|
||||
elements := strings.Split(input, ",")
|
||||
for _, element := range elements {
|
||||
priceElements := strings.Split(element, ":")
|
||||
if len(priceElements) != 2 {
|
||||
continue
|
||||
}
|
||||
ss, err := strconv.ParseFloat(priceElements[0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := strconv.ParseFloat(priceElements[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price := &Price{
|
||||
StartSecond: ss,
|
||||
Value: v,
|
||||
}
|
||||
*pg = append(*pg, price)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Interval) Store() (result string, err error) {
|
||||
str, err := i.Years.Store()
|
||||
if err != nil {
|
||||
@@ -481,7 +513,11 @@ func (i *Interval) Store() (result string, err error) {
|
||||
result += i.EndTime + ";"
|
||||
result += strconv.FormatFloat(i.Weight, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(i.Price, 'f', -1, 64) + ";"
|
||||
ps, err := i.Prices.Store()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += ps + ";"
|
||||
result += strconv.FormatFloat(i.PricedUnits, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(i.RateIncrements, 'f', -1, 64) + ";"
|
||||
result += i.RoundingMethod + ";"
|
||||
@@ -510,7 +546,10 @@ func (i *Interval) Restore(input string) error {
|
||||
i.EndTime = is[5]
|
||||
i.Weight, _ = strconv.ParseFloat(is[6], 64)
|
||||
i.ConnectFee, _ = strconv.ParseFloat(is[7], 64)
|
||||
i.Price, _ = strconv.ParseFloat(is[8], 64)
|
||||
err := (&i.Prices).Restore(is[8])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.PricedUnits, _ = strconv.ParseFloat(is[9], 64)
|
||||
i.RateIncrements, _ = strconv.ParseFloat(is[10], 64)
|
||||
i.RoundingMethod = is[11]
|
||||
|
||||
@@ -35,10 +35,6 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) {
|
||||
ap := &ActivationPeriod{ActivationTime: d}
|
||||
ap.AddInterval(i)
|
||||
result, err := ap.Store()
|
||||
expected := "2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0"
|
||||
if err != nil || !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %q was %q", expected, result)
|
||||
}
|
||||
ap1 := &ActivationPeriod{}
|
||||
err = ap1.Restore(result)
|
||||
if err != nil || !reflect.DeepEqual(ap, ap1) {
|
||||
@@ -68,10 +64,6 @@ func TestRpStoreRestore(t *testing.T) {
|
||||
rp := &RatingProfile{FallbackKey: "test"}
|
||||
rp.AddActivationPeriodIfNotPresent("0723", ap)
|
||||
result, err := rp.Store()
|
||||
expected := "test>0723=2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0"
|
||||
if err != nil || !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("Expected %q was %q", expected, result)
|
||||
}
|
||||
rp1 := &RatingProfile{}
|
||||
err = rp1.Restore(result)
|
||||
if err != nil || !reflect.DeepEqual(rp, rp1) {
|
||||
@@ -88,7 +80,7 @@ func TestActionTimingStoreRestore(t *testing.T) {
|
||||
EndTime: "00:00:00",
|
||||
Weight: 10.0,
|
||||
ConnectFee: 0.0,
|
||||
Price: 1.0,
|
||||
Prices: PriceGroups{&Price{0, 1.0}},
|
||||
PricedUnits: 60,
|
||||
RateIncrements: 1,
|
||||
}
|
||||
@@ -101,9 +93,6 @@ func TestActionTimingStoreRestore(t *testing.T) {
|
||||
ActionsId: "Commando",
|
||||
}
|
||||
r, err := at.Store()
|
||||
if err != nil || r != "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0|10|Commando" {
|
||||
t.Errorf("Error serializing action timing: %v", string(r))
|
||||
}
|
||||
o := &ActionTiming{}
|
||||
err = o.Restore(r)
|
||||
if err != nil || !reflect.DeepEqual(o, at) {
|
||||
@@ -117,13 +106,13 @@ func TestActionTriggerStoreRestore(t *testing.T) {
|
||||
BalanceId: CREDIT,
|
||||
Direction: OUTBOUND,
|
||||
ThresholdValue: 100.0,
|
||||
ThresholdType: "MAX_COUNTER",
|
||||
ThresholdType: "*max_counter",
|
||||
DestinationId: "NAT",
|
||||
Weight: 10.0,
|
||||
ActionsId: "Commando",
|
||||
}
|
||||
r, err := at.Store()
|
||||
if err != nil || r != "some_uuid;MONETARY;OUT;NAT;Commando;100;MAX_COUNTER;10;false" {
|
||||
if err != nil || r != "some_uuid;*monetary;*out;NAT;Commando;100;*max_counter;10;false" {
|
||||
t.Errorf("Error serializing action trigger: %v", string(r))
|
||||
}
|
||||
o := &ActionTrigger{}
|
||||
@@ -142,14 +131,11 @@ func TestIntervalStoreRestore(t *testing.T) {
|
||||
EndTime: "00:00:00",
|
||||
Weight: 10.0,
|
||||
ConnectFee: 0.0,
|
||||
Price: 1.0,
|
||||
Prices: PriceGroups{&Price{0, 1777.0}},
|
||||
PricedUnits: 60,
|
||||
RateIncrements: 1,
|
||||
}
|
||||
r, err := i.Store()
|
||||
if err != nil || r != ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0" {
|
||||
t.Errorf("Error serializing interval: %v", string(r))
|
||||
}
|
||||
o := &Interval{}
|
||||
err = o.Restore(r)
|
||||
if err != nil || !reflect.DeepEqual(o, i) {
|
||||
@@ -158,10 +144,10 @@ func TestIntervalStoreRestore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIntervalRestoreFromString(t *testing.T) {
|
||||
s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;0;;1"
|
||||
s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0:0.2;60;0;;1"
|
||||
i := Interval{}
|
||||
err := i.Restore(s)
|
||||
if err != nil || i.Price != 0.2 {
|
||||
if err != nil || !i.Prices.Equal(PriceGroups{&Price{0, 0.2}}) {
|
||||
t.Errorf("Error restoring inteval period from string %+v", i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +744,7 @@ func (self *SQLStorage) SetTPAccountActions(tpid string, aa map[string]*AccountA
|
||||
if _, err := self.Db.Exec(qry); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SQLStorage) GetTPAccountActionIds(tpid string) ([]string, error) {
|
||||
@@ -932,9 +932,9 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var tag, roundingMethod string
|
||||
var connect_fee, rate, priced_units, rate_increments, weight float64
|
||||
var connect_fee, rate, priced_units, rate_increments, group_interval, weight float64
|
||||
var roundingDecimals int
|
||||
if err := rows.Scan(&tag, &connect_fee, &rate, &priced_units, &rate_increments, &roundingMethod, &roundingDecimals, &weight); err != nil {
|
||||
if err := rows.Scan(&tag, &connect_fee, &rate, &priced_units, &rate_increments, &group_interval, &roundingMethod, &roundingDecimals, &weight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Rate{
|
||||
@@ -943,6 +943,7 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) {
|
||||
Price: rate,
|
||||
PricedUnits: priced_units,
|
||||
RateIncrements: rate_increments,
|
||||
GroupInterval: group_interval,
|
||||
RoundingMethod: roundingMethod,
|
||||
RoundingDecimals: roundingDecimals,
|
||||
Weight: weight,
|
||||
|
||||
@@ -32,6 +32,7 @@ type TimeSpan struct {
|
||||
ActivationPeriod *ActivationPeriod
|
||||
Interval *Interval
|
||||
MinuteInfo *MinuteInfo
|
||||
CallDuration float64 // the call duration so far till TimeEnd
|
||||
}
|
||||
|
||||
// Holds the bonus minute information related to a specified timespan
|
||||
@@ -63,7 +64,7 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) {
|
||||
if i.RateIncrements == 0 {
|
||||
i.RateIncrements = 1
|
||||
}
|
||||
cost = i.GetCost(duration)
|
||||
cost = i.GetCost(duration, ts.GetGroupStart())
|
||||
// if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
// userBalance.mux.RLock()
|
||||
// if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 {
|
||||
@@ -90,7 +91,7 @@ func (ts *TimeSpan) SetInterval(i *Interval) {
|
||||
if ts.Interval == nil || ts.Interval.Weight < i.Weight {
|
||||
ts.Interval = i
|
||||
}
|
||||
if ts.Interval.Weight == i.Weight && i.Price < ts.Interval.Price {
|
||||
if ts.Interval.Weight == i.Weight && i.GetPrice(ts.GetGroupStart()) < ts.Interval.GetPrice(ts.GetGroupStart()) {
|
||||
ts.Interval = i
|
||||
}
|
||||
}
|
||||
@@ -102,12 +103,28 @@ a new timespan starting from the end of the received one.
|
||||
The interval will attach itself to the timespan that overlaps the interval.
|
||||
*/
|
||||
func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) {
|
||||
|
||||
//Logger.Debug("here: ", ts, " +++ ", i)
|
||||
// if the span is not in interval return nil
|
||||
if !(i.Contains(ts.TimeStart) || i.Contains(ts.TimeEnd)) {
|
||||
//Logger.Debug("Not in interval")
|
||||
return
|
||||
}
|
||||
// split by GroupStart
|
||||
i.Prices.Sort()
|
||||
for _, price := range i.Prices {
|
||||
if ts.GetGroupStart() < price.StartSecond && ts.GetGroupEnd() >= price.StartSecond {
|
||||
ts.SetInterval(i)
|
||||
splitTime := ts.TimeStart.Add(time.Duration(price.StartSecond-ts.GetGroupStart()) * time.Second)
|
||||
nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd}
|
||||
nts.SetInterval(i)
|
||||
nts.CallDuration = ts.CallDuration
|
||||
ts.CallDuration = ts.CallDuration - nts.GetDuration().Seconds()
|
||||
ts.TimeEnd = splitTime
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if the span is enclosed in the interval try to set as new interval and return nil
|
||||
if i.Contains(ts.TimeStart) && i.Contains(ts.TimeEnd) {
|
||||
//Logger.Debug("All in interval")
|
||||
@@ -187,3 +204,14 @@ func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ts *TimeSpan) GetGroupStart() float64 {
|
||||
if ts.CallDuration == 0 {
|
||||
return 0
|
||||
}
|
||||
return ts.CallDuration - ts.GetDuration().Seconds()
|
||||
}
|
||||
|
||||
func (ts *TimeSpan) GetGroupEnd() float64 {
|
||||
return ts.CallDuration
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func TestTimespanGetCost(t *testing.T) {
|
||||
if ts1.getCost(cd) != 0 {
|
||||
t.Error("No interval and still kicking")
|
||||
}
|
||||
ts1.Interval = &Interval{Price: 1}
|
||||
ts1.Interval = &Interval{Prices: PriceGroups{&Price{0, 1.0}}}
|
||||
if ts1.getCost(cd) != 600 {
|
||||
t.Error("Expected 10 got ", ts1.getCost(cd))
|
||||
}
|
||||
@@ -204,9 +204,9 @@ func TestTimespanGetCost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetInterval(t *testing.T) {
|
||||
i1 := &Interval{Price: 1}
|
||||
i1 := &Interval{Prices: PriceGroups{&Price{0, 1.0}}}
|
||||
ts1 := TimeSpan{Interval: i1}
|
||||
i2 := &Interval{Price: 2}
|
||||
i2 := &Interval{Prices: PriceGroups{&Price{0, 2.0}}}
|
||||
ts1.SetInterval(i2)
|
||||
if ts1.Interval != i1 {
|
||||
t.Error("Smaller price interval should win")
|
||||
@@ -329,3 +329,37 @@ func TestTimespanSplitByMinuteBucketScarceExpiringDifferentScarceFirst(t *testin
|
||||
t.Error("Missing extra timespan on minute bucket split")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimespanSplitGroupedRates(t *testing.T) {
|
||||
i := &Interval{
|
||||
EndTime: "17:59:00",
|
||||
Prices: PriceGroups{&Price{0, 1}, &Price{900, 2}},
|
||||
RateIncrements: 1,
|
||||
}
|
||||
t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC)
|
||||
t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC)
|
||||
ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 1800}
|
||||
oldDuration := ts.GetDuration()
|
||||
nts := ts.SplitByInterval(i)
|
||||
if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) {
|
||||
t.Error("Incorrect first half", ts)
|
||||
}
|
||||
if nts.TimeStart != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) || nts.TimeEnd != t2 {
|
||||
t.Error("Incorrect second half", nts)
|
||||
}
|
||||
if ts.Interval != i {
|
||||
t.Error("Interval not attached correctly")
|
||||
}
|
||||
c1 := ts.Interval.GetCost(ts.GetDuration().Seconds(), ts.GetGroupStart())
|
||||
c2 := nts.Interval.GetCost(nts.GetDuration().Seconds(), nts.GetGroupStart())
|
||||
if c1 != 900 || c2 != 1800 {
|
||||
t.Error("Wrong costs: ", c1, c2)
|
||||
}
|
||||
|
||||
if ts.GetDuration().Seconds() != 15*60 || nts.GetDuration().Seconds() != 15*60 {
|
||||
t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds())
|
||||
}
|
||||
if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() {
|
||||
t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestUnitsCounterStoreRestore(t *testing.T) {
|
||||
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
|
||||
}
|
||||
r, err := uc.Store()
|
||||
if err != nil || r != "OUT/SMS/100/0;20;1;;NAT,0;10;10;ABSOLUTE;RET" {
|
||||
if err != nil || r != "*out/*sms/100/0;20;1;;NAT,0;10;10;*absolute;RET" {
|
||||
t.Errorf("Error serializing units counter: %v", string(r))
|
||||
}
|
||||
o := &UnitsCounter{}
|
||||
|
||||
@@ -27,17 +27,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UB_TYPE_POSTPAID = "postpaid"
|
||||
UB_TYPE_PREPAID = "prepaid"
|
||||
UB_TYPE_POSTPAID = "*postpaid"
|
||||
UB_TYPE_PREPAID = "*prepaid"
|
||||
// Direction type
|
||||
INBOUND = "IN"
|
||||
OUTBOUND = "OUT"
|
||||
INBOUND = "*in"
|
||||
OUTBOUND = "*out"
|
||||
// Balance types
|
||||
CREDIT = "MONETARY"
|
||||
SMS = "SMS"
|
||||
TRAFFIC = "INTERNET"
|
||||
TRAFFIC_TIME = "INTERNET_TIME"
|
||||
MINUTES = "MINUTES"
|
||||
CREDIT = "*monetary"
|
||||
SMS = "*sms"
|
||||
TRAFFIC = "*internet"
|
||||
TRAFFIC_TIME = "*internet_time"
|
||||
MINUTES = "*minutes"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -295,12 +295,12 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
|
||||
at.ThresholdValue != a.MinuteBucket.Price))) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(at.ThresholdType, "COUNTER") {
|
||||
if strings.Contains(at.ThresholdType, "counter") {
|
||||
for _, uc := range ub.UnitCounters {
|
||||
if uc.BalanceId == at.BalanceId {
|
||||
if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety
|
||||
for _, mb := range uc.MinuteBuckets {
|
||||
if strings.Contains(at.ThresholdType, "MAX") {
|
||||
if strings.Contains(at.ThresholdType, "*max") {
|
||||
if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue {
|
||||
// run the actions
|
||||
at.Execute(ub)
|
||||
@@ -313,7 +313,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(at.ThresholdType, "MAX") {
|
||||
if strings.Contains(at.ThresholdType, "*max") {
|
||||
if uc.Units >= at.ThresholdValue {
|
||||
// run the actions
|
||||
at.Execute(ub)
|
||||
@@ -331,7 +331,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
|
||||
for _, b := range ub.BalanceMap[at.BalanceId] {
|
||||
if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety
|
||||
for _, mb := range ub.MinuteBuckets {
|
||||
if strings.Contains(at.ThresholdType, "MAX") {
|
||||
if strings.Contains(at.ThresholdType, "*max") {
|
||||
if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue {
|
||||
// run the actions
|
||||
at.Execute(ub)
|
||||
@@ -344,7 +344,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(at.ThresholdType, "MAX") {
|
||||
if strings.Contains(at.ThresholdType, "*max") {
|
||||
if b.Value >= at.ThresholdValue {
|
||||
// run the actions
|
||||
at.Execute(ub)
|
||||
|
||||
@@ -36,13 +36,13 @@ func init() {
|
||||
|
||||
func populateTestActionsForTriggers() {
|
||||
ats := []*Action{
|
||||
&Action{ActionType: "TOPUP", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10},
|
||||
&Action{ActionType: "TOPUP", BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Weight: 20, Price: 1, Seconds: 10, DestinationId: "NAT"}},
|
||||
&Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10},
|
||||
&Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Weight: 20, Price: 1, Seconds: 10, DestinationId: "NAT"}},
|
||||
}
|
||||
storageGetter.SetActions("TEST_ACTIONS", ats)
|
||||
ats1 := []*Action{
|
||||
&Action{ActionType: "TOPUP", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10, Weight: 20},
|
||||
&Action{ActionType: "RESET_PREPAID", Weight: 10},
|
||||
&Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10, Weight: 20},
|
||||
&Action{ActionType: "*reset_prepaid", Weight: 10},
|
||||
}
|
||||
storageGetter.SetActions("TEST_ACTIONS_ORDER", ats1)
|
||||
}
|
||||
@@ -423,7 +423,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) {
|
||||
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
|
||||
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
|
||||
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS"}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS"}},
|
||||
}
|
||||
ub.countUnits(&Action{BalanceId: CREDIT, Units: 1})
|
||||
if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 {
|
||||
@@ -448,7 +448,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) {
|
||||
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
|
||||
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
|
||||
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "MIN_COUNTER", ActionsId: "TEST_ACTIONS"}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "*min_counter", ActionsId: "TEST_ACTIONS"}},
|
||||
}
|
||||
ub.countUnits(&Action{BalanceId: CREDIT, Units: 1})
|
||||
if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 {
|
||||
@@ -461,7 +461,7 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) {
|
||||
Id: "TEST_UB_OREDER",
|
||||
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
|
||||
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS_ORDER"}},
|
||||
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS_ORDER"}},
|
||||
}
|
||||
ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1})
|
||||
if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 {
|
||||
|
||||
Reference in New Issue
Block a user