mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
work in progress
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ a.out
|
||||
*workspace
|
||||
docs/_*
|
||||
bin
|
||||
.idea
|
||||
|
||||
@@ -19,46 +19,55 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package timespans
|
||||
|
||||
import (
|
||||
//"log"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
// Amount of a trafic of a certain type (TOR)
|
||||
type TrafficVolume struct {
|
||||
type UnitsCounter struct {
|
||||
Direction string
|
||||
TOR string
|
||||
Units float64
|
||||
Weight float64
|
||||
DestinationId string
|
||||
destination *Destination
|
||||
}
|
||||
|
||||
// Volume discount to be applyed after the Units are reached
|
||||
// in a certain time period.
|
||||
type VolumeDiscount struct {
|
||||
TOR string
|
||||
DestinationsId string
|
||||
Units float64
|
||||
AbsoulteValue float64 // either this or the procentage below
|
||||
DiscountProcentage float64 // use only one
|
||||
Weight float64
|
||||
// Structure to store actions according to weight
|
||||
type countersorter []*UnitsCounter
|
||||
|
||||
func (s countersorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s countersorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s countersorter) Less(j, i int) bool {
|
||||
return s[i].Weight < s[j].Weight
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the destination loading it from the storage if necessary.
|
||||
*/
|
||||
func (vd *VolumeDiscount) getDestination(storage StorageGetter) (dest *Destination) {
|
||||
if vd.destination == nil {
|
||||
vd.destination, _ = storage.GetDestination(vd.DestinationId)
|
||||
func (uc *UnitsCounter) getDestination() (dest *Destination) {
|
||||
if uc.destination == nil {
|
||||
uc.destination, _ = storageGetter.GetDestination(uc.DestinationId)
|
||||
}
|
||||
return vd.destination
|
||||
return uc.destination
|
||||
}
|
||||
|
||||
/*
|
||||
Structure to be filled for each tariff plan with the bonus value for received calls minutes.
|
||||
*/
|
||||
type Bonus struct {
|
||||
type Action struct {
|
||||
Direction string
|
||||
TOR string
|
||||
Units float64
|
||||
balanceMap map[string]float64
|
||||
MinuteBuckets []*MinuteBucket
|
||||
Weight float64
|
||||
DestinationsId string
|
||||
destination *Destination
|
||||
}
|
||||
@@ -66,27 +75,30 @@ type Bonus struct {
|
||||
/*
|
||||
Serializes the tariff plan for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (rcb *Bonus) store() (result string) {
|
||||
result += strconv.FormatFloat(rcb.Credit, 'f', -1, 64) + ","
|
||||
result += strconv.FormatFloat(rcb.SmsCredit, 'f', -1, 64) + ","
|
||||
result += strconv.FormatFloat(rcb.Traffic, 'f', -1, 64)
|
||||
if rcb.MinuteBucket != nil {
|
||||
result += ","
|
||||
result += rcb.MinuteBucket.store()
|
||||
}
|
||||
return
|
||||
func (a *Action) store() (result string) {
|
||||
buf := new(bytes.Buffer)
|
||||
gob.NewEncoder(buf).Encode(a)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
De-serializes the tariff plan for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (rcb *Bonus) restore(input string) {
|
||||
elements := strings.Split(input, ",")
|
||||
rcb.Credit, _ = strconv.ParseFloat(elements[0], 64)
|
||||
rcb.SmsCredit, _ = strconv.ParseFloat(elements[1], 64)
|
||||
rcb.Traffic, _ = strconv.ParseFloat(elements[2], 64)
|
||||
if len(elements) > 3 {
|
||||
rcb.MinuteBucket = &MinuteBucket{}
|
||||
rcb.MinuteBucket.restore(elements[3])
|
||||
}
|
||||
func (a *Action) restore(input string) {
|
||||
gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(a)
|
||||
}
|
||||
|
||||
// Structure to store actions according to weight
|
||||
type actionsorter []*Action
|
||||
|
||||
func (s actionsorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s actionsorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s actionsorter) Less(j, i int) bool {
|
||||
return s[i].Weight < s[j].Weight
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func TestApRestoreKyoto(t *testing.T) {
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf",
|
||||
Subject: "rif",
|
||||
DestinationPrefix: "0257",
|
||||
Destination: "0257",
|
||||
storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 2 {
|
||||
@@ -45,7 +45,7 @@ func TestApRestoreRedis(t *testing.T) {
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf",
|
||||
Subject: "rif",
|
||||
DestinationPrefix: "0257",
|
||||
Destination: "0257",
|
||||
storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 2 {
|
||||
@@ -78,7 +78,7 @@ func TestFallbackDirect(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0745", storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0745", storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", cd.ActivationPeriods)
|
||||
@@ -89,7 +89,7 @@ func TestFallbackWithBackTrace(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0745121", storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0745121", storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", cd.ActivationPeriods)
|
||||
@@ -100,7 +100,7 @@ func TestFallbackDefault(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "00000", storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "00000", storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 1 {
|
||||
t.Error("Error restoring activation periods: ", cd.ActivationPeriods)
|
||||
@@ -111,7 +111,7 @@ func TestFallbackNoInfiniteLoop(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0721", storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0721", storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
if len(cd.ActivationPeriods) != 0 {
|
||||
t.Error("Error restoring activation periods: ", cd.ActivationPeriods)
|
||||
|
||||
@@ -26,14 +26,14 @@ import (
|
||||
The output structure that will be returned with the call cost information.
|
||||
*/
|
||||
type CallCost struct {
|
||||
TOR, Tenant, Subject, DestinationPrefix string
|
||||
TOR, Tenant, Subject, Destination string
|
||||
Cost, ConnectFee float64
|
||||
Timespans []*TimeSpan
|
||||
}
|
||||
|
||||
// Pretty printing for call cost
|
||||
func (cc *CallCost) String() (r string) {
|
||||
r = fmt.Sprintf("%v[%v] : %s -> %s (", cc.Cost, cc.ConnectFee, cc.Subject, cc.DestinationPrefix)
|
||||
r = fmt.Sprintf("%v[%v] : %s -> %s (", cc.Cost, cc.ConnectFee, cc.Subject, cc.Destination)
|
||||
for _, ts := range cc.Timespans {
|
||||
r += fmt.Sprintf(" %v,", ts.GetDuration())
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ func TestSingleResultMerge(t *testing.T) {
|
||||
defer getter.Close()
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 12 {
|
||||
t.Errorf("expected 12 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 12 {
|
||||
t.Errorf("expected 12 was %v", cc2.Cost)
|
||||
@@ -55,14 +55,14 @@ func TestMultipleResultMerge(t *testing.T) {
|
||||
defer getter.Close()
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 12 {
|
||||
t.Errorf("expected 12 was %v", cc1.Cost)
|
||||
}
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 6 {
|
||||
t.Errorf("expected 6 was %v", cc2.Cost)
|
||||
@@ -81,14 +81,14 @@ func TestMultipleInputLeftMerge(t *testing.T) {
|
||||
defer getter.Close()
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 18 {
|
||||
t.Errorf("expected 12 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 6 {
|
||||
t.Errorf("expected 6 was %v", cc2.Cost)
|
||||
@@ -107,14 +107,14 @@ func TestMultipleInputRightMerge(t *testing.T) {
|
||||
defer getter.Close()
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc1, _ := cd.GetCost()
|
||||
if cc1.Cost != 12 {
|
||||
t.Errorf("expected 12 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cc2, _ := cd.GetCost()
|
||||
if cc2.Cost != 18 {
|
||||
t.Errorf("expected 18 was %v", cc2.Cost)
|
||||
|
||||
@@ -53,15 +53,14 @@ func round(val float64, prec int) float64 {
|
||||
The input stucture that contains call information.
|
||||
*/
|
||||
type CallDescriptor struct {
|
||||
TOR string
|
||||
Tenant, Subject, DestinationPrefix string
|
||||
TimeStart, TimeEnd time.Time
|
||||
Amount float64
|
||||
FallbackSubject string // the subject to check for destination if not found on primary subject
|
||||
ActivationPeriods []*ActivationPeriod
|
||||
FallbackKey string
|
||||
storageGetter StorageGetter
|
||||
userBudget *UserBudget
|
||||
TOR string
|
||||
Tenant, Subject, Destination string
|
||||
TimeStart, TimeEnd time.Time
|
||||
Amount float64
|
||||
FallbackSubject string // the subject to check for destination if not found on primary subject
|
||||
ActivationPeriods []*ActivationPeriod
|
||||
FallbackKey string
|
||||
userBalance *UserBalance
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -74,20 +73,20 @@ func (cd *CallDescriptor) AddActivationPeriod(aps ...*ActivationPeriod) {
|
||||
}
|
||||
|
||||
/*
|
||||
Gets and caches the user budget information.
|
||||
Gets and caches the user balance information.
|
||||
*/
|
||||
func (cd *CallDescriptor) getUserBudget() (ub *UserBudget, err error) {
|
||||
if cd.userBudget == nil {
|
||||
cd.userBudget, err = cd.storageGetter.GetUserBudget(cd.Subject)
|
||||
func (cd *CallDescriptor) getUserBalance() (ub *UserBalance, err error) {
|
||||
if cd.userBalance == nil {
|
||||
cd.userBalance, err = storageGetter.GetUserBalance(cd.Subject)
|
||||
}
|
||||
return cd.userBudget, err
|
||||
return cd.userBalance, err
|
||||
}
|
||||
|
||||
/*
|
||||
Exported method to set the storage getter.
|
||||
*/
|
||||
func (cd *CallDescriptor) SetStorageGetter(sg StorageGetter) {
|
||||
cd.storageGetter = sg
|
||||
func SetStorageGetter(sg StorageGetter) {
|
||||
storageGetter = sg
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -96,7 +95,7 @@ Restores the activation periods for the specified prefix from storage.
|
||||
func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error) {
|
||||
cd.ActivationPeriods = make([]*ActivationPeriod, 0)
|
||||
base := fmt.Sprintf("%s:%s:", cd.Tenant, cd.Subject)
|
||||
destPrefix = cd.DestinationPrefix
|
||||
destPrefix = cd.Destination
|
||||
key := base + destPrefix
|
||||
values, err := cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1)
|
||||
if err != nil {
|
||||
@@ -115,7 +114,7 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s
|
||||
err = errors.New("Max fallback recursion depth reached!" + key)
|
||||
return
|
||||
}
|
||||
values, fallbackKey, err := cd.storageGetter.GetActivationPeriodsOrFallback(key)
|
||||
values, fallbackKey, err := storageGetter.GetActivationPeriodsOrFallback(key)
|
||||
if fallbackKey != "" {
|
||||
base = fallbackKey + ":"
|
||||
key = base + destPrefix
|
||||
@@ -123,7 +122,7 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s
|
||||
return cd.getActivationPeriodsOrFallback(key, base, destPrefix, recursionDepth)
|
||||
}
|
||||
//get for a smaller prefix if the orignal one was not found
|
||||
for i := len(cd.DestinationPrefix); err != nil || fallbackKey != ""; {
|
||||
for i := len(cd.Destination); err != nil || fallbackKey != ""; {
|
||||
if fallbackKey != "" {
|
||||
base = fallbackKey + ":"
|
||||
key = base + destPrefix
|
||||
@@ -132,12 +131,12 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s
|
||||
}
|
||||
i--
|
||||
if i >= MinPrefixLength {
|
||||
destPrefix = cd.DestinationPrefix[:i]
|
||||
destPrefix = cd.Destination[:i]
|
||||
key = base + destPrefix
|
||||
} else {
|
||||
break
|
||||
}
|
||||
values, fallbackKey, err = cd.storageGetter.GetActivationPeriodsOrFallback(key)
|
||||
values, fallbackKey, err = storageGetter.GetActivationPeriodsOrFallback(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -147,7 +146,7 @@ Constructs the key for the storage lookup.
|
||||
The prefixLen is limiting the length of the destination prefix.
|
||||
*/
|
||||
func (cd *CallDescriptor) GetKey() string {
|
||||
return fmt.Sprintf("%s:%s:%s", cd.Tenant, cd.Subject, cd.DestinationPrefix)
|
||||
return fmt.Sprintf("%s:%s:%s", cd.Tenant, cd.Subject, cd.Destination)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -163,9 +162,9 @@ Splits the received timespan into sub time spans according to the activation per
|
||||
func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeSpan) {
|
||||
timespans = append(timespans, firstSpan)
|
||||
// split on (free) minute buckets
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
userBudget.mux.RLock()
|
||||
_, bucketList := userBudget.getSecondsForPrefix(cd.storageGetter, cd.DestinationPrefix)
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
userBalance.mux.RLock()
|
||||
_, bucketList := userBalance.getSecondsForPrefix(cd.Destination)
|
||||
for _, mb := range bucketList {
|
||||
for i := 0; i < len(timespans); i++ {
|
||||
if timespans[i].MinuteInfo != nil {
|
||||
@@ -179,7 +178,7 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS
|
||||
}
|
||||
}
|
||||
}
|
||||
userBudget.mux.RUnlock()
|
||||
userBalance.mux.RUnlock()
|
||||
}
|
||||
|
||||
if firstSpan.MinuteInfo != nil {
|
||||
@@ -247,12 +246,12 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
cost += ts.getCost(cd)
|
||||
}
|
||||
cc := &CallCost{TOR: cd.TOR,
|
||||
Tenant: cd.Tenant,
|
||||
Subject: cd.Subject,
|
||||
DestinationPrefix: destPrefix,
|
||||
Cost: cost,
|
||||
ConnectFee: connectionFee,
|
||||
Timespans: timespans}
|
||||
Tenant: cd.Tenant,
|
||||
Subject: cd.Subject,
|
||||
Destination: destPrefix,
|
||||
Cost: cost,
|
||||
ConnectFee: connectionFee,
|
||||
Timespans: timespans}
|
||||
|
||||
return cc, err
|
||||
}
|
||||
@@ -275,7 +274,7 @@ func (cd *CallDescriptor) getPresentSecondCost() (cost float64, err error) {
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the approximate max allowed session for user budget. It will try the max amount received in the call descriptor
|
||||
Returns the approximate max allowed session for user balance. It will try the max amount received in the call descriptor
|
||||
and will decrease it by 10% for nine times. So if the user has little credit it will still allow 10% of the initial amount.
|
||||
If the user has no credit then it will return 0.
|
||||
*/
|
||||
@@ -283,19 +282,19 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) {
|
||||
_, err = cd.SearchStorageForPrefix()
|
||||
now := time.Now()
|
||||
availableCredit, availableSeconds := 0.0, 0.0
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
if userBudget.Type == UB_TYPE_POSTPAID {
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
if userBalance.Type == UB_TYPE_POSTPAID {
|
||||
return -1, nil
|
||||
} else {
|
||||
userBudget.mux.RLock()
|
||||
availableCredit = userBudget.BalanceMap[CREDIT]
|
||||
availableSeconds, _ = userBudget.getSecondsForPrefix(cd.storageGetter, cd.DestinationPrefix)
|
||||
userBudget.mux.RUnlock()
|
||||
userBalance.mux.RLock()
|
||||
availableCredit = userBalance.BalanceMap[CREDIT]
|
||||
availableSeconds, _ = userBalance.getSecondsForPrefix(cd.Destination)
|
||||
userBalance.mux.RUnlock()
|
||||
}
|
||||
} else {
|
||||
return cd.Amount, err
|
||||
}
|
||||
// check for zero budget
|
||||
// check for zero balance
|
||||
if availableCredit == 0 {
|
||||
return availableSeconds, nil
|
||||
}
|
||||
@@ -323,19 +322,19 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) {
|
||||
}
|
||||
|
||||
// Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method)
|
||||
// from user's money budget.
|
||||
// from user's money balance.
|
||||
func (cd *CallDescriptor) Debit() (cc *CallCost, err error) {
|
||||
cc, err = cd.GetCost()
|
||||
if err != nil {
|
||||
log.Printf("error getting cost %v", err)
|
||||
}
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
if cc.Cost != 0 || cc.ConnectFee != 0 {
|
||||
userBudget.debitMoneyBudget(cd.storageGetter, cc.Cost+cc.ConnectFee)
|
||||
userBalance.debitMoneyBalance(cc.Cost + cc.ConnectFee)
|
||||
}
|
||||
for _, ts := range cc.Timespans {
|
||||
if ts.MinuteInfo != nil {
|
||||
userBudget.debitMinutesBudget(cd.storageGetter, ts.MinuteInfo.Quantity, cd.DestinationPrefix)
|
||||
userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,79 +342,57 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) {
|
||||
}
|
||||
|
||||
/*
|
||||
Interface method used to add/substract an amount of cents from user's money budget.
|
||||
Interface method used to add/substract an amount of cents from user's money balance.
|
||||
The amount filed has to be filled in call descriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) DebitCents() (left float64, err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.debitMoneyBudget(cd.storageGetter, cd.Amount), nil
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
return userBalance.debitMoneyBalance(cd.Amount), nil
|
||||
}
|
||||
return 0.0, err
|
||||
}
|
||||
|
||||
/*
|
||||
Interface method used to add/substract an amount of units from user's sms budget.
|
||||
Interface method used to add/substract an amount of units from user's sms balance.
|
||||
The amount filed has to be filled in call descriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) DebitSMS() (left float64, err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.debitSMSBuget(cd.storageGetter, cd.Amount)
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
return userBalance.debitSMSBuget(cd.Amount)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
/*
|
||||
Interface method used to add/substract an amount of seconds from user's minutes budget.
|
||||
Interface method used to add/substract an amount of seconds from user's minutes balance.
|
||||
The amount filed has to be filled in call descriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) DebitSeconds() (err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.debitMinutesBudget(cd.storageGetter, cd.Amount, cd.DestinationPrefix)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Interface method used to add an amount to the accumulated placed call seconds
|
||||
to be used for volume discount.
|
||||
The amount filed has to be filled in call descriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) AddVolumeDiscountSeconds() (err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.addVolumeDiscountSeconds(cd.storageGetter, cd.Amount)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Resets the accumulated volume discount seconds (to zero).
|
||||
*/
|
||||
func (cd *CallDescriptor) ResetVolumeDiscountSeconds() (err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.resetVolumeDiscountSeconds(cd.storageGetter)
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
return userBalance.debitMinutesBalance(cd.Amount, cd.Destination)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the specified amount of seconds to the received call seconds. When the threshold specified
|
||||
in the user's tariff plan is reached then the received call budget is reseted and the bonus
|
||||
in the user's tariff plan is reached then the received call balance is reseted and the bonus
|
||||
specified in the tariff plan is applied.
|
||||
The amount filed has to be filled in call descriptor.
|
||||
*/
|
||||
func (cd *CallDescriptor) AddRecievedCallSeconds() (err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.addReceivedCallSeconds(cd.storageGetter, cd.Amount)
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
return userBalance.addReceivedCallSeconds(INBOUND, cd.TOR, cd.Destination, cd.Amount)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Resets user budgets value to the amounts specified in the tariff plan.
|
||||
Resets user balances value to the amounts specified in the tariff plan.
|
||||
*/
|
||||
func (cd *CallDescriptor) ResetUserBudget() (err error) {
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
return userBudget.resetUserBudget(cd.storageGetter)
|
||||
func (cd *CallDescriptor) ResetUserBalance() (err error) {
|
||||
if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
return userBalance.resetUserBalance()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestKyotoSplitSpans(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
|
||||
cd.SearchStorageForPrefix()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
@@ -56,7 +56,7 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
|
||||
cd.SearchStorageForPrefix()
|
||||
timespans := cd.splitInTimeSpans()
|
||||
@@ -72,15 +72,15 @@ func TestKyotoGetCost(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ = cd.GetCost()
|
||||
expected = &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 540, ConnectFee: 0}
|
||||
expected = &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 540, ConnectFee: 0}
|
||||
}
|
||||
|
||||
func TestRedisGetCost(t *testing.T) {
|
||||
@@ -89,9 +89,9 @@ func TestRedisGetCost(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
@@ -106,9 +106,9 @@ func TestMongoGetCost(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
@@ -120,9 +120,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Log(cd.ActivationPeriods)
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
@@ -135,9 +135,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 330, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 330, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Log(result.Timespans)
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
@@ -150,9 +150,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 360, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 360, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
@@ -164,9 +164,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 0.5, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 0.5, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
@@ -178,9 +178,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723045326", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", Cost: 60.35, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0723", Cost: 60.35, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
@@ -192,7 +192,7 @@ func TestPresentSecodCost(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.getPresentSecondCost()
|
||||
expected := 0.016
|
||||
if result != expected {
|
||||
@@ -206,29 +206,29 @@ 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{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
result, _ := cd.GetCost()
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", Cost: 0.1, ConnectFee: 0}
|
||||
expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 0.1, ConnectFee: 0}
|
||||
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
|
||||
t.Log(result.Timespans[0])
|
||||
t.Errorf("Expected %v was %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxSessionTimeNoUserBudget(t *testing.T) {
|
||||
func TestMaxSessionTimeNoUserBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", storageGetter: getter, Amount: 1000}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", storageGetter: getter, Amount: 1000}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
if result != 1000 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 1000, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxSessionTimeWithUserBudget(t *testing.T) {
|
||||
func TestMaxSessionTimeWithUserBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
if result != 1080 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 1080, result)
|
||||
@@ -238,7 +238,7 @@ func TestMaxSessionTimeWithUserBudget(t *testing.T) {
|
||||
func TestMaxSessionTimeNoCredit(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "broker", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "broker", Destination: "0723", storageGetter: getter, Amount: 5400}
|
||||
result, err := cd.GetMaxSessionTime()
|
||||
if result != 100 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 100, result)
|
||||
@@ -251,10 +251,10 @@ func TestGetCostWithVolumeDiscount(t *testing.T) {
|
||||
vd1 := &VolumeDiscount{100, 10}
|
||||
vd2 := &VolumeDiscount{500, 20}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd1, vd2}}
|
||||
rifsBudget := &UserBudget{Id: "rif", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 105}
|
||||
rifsBalance := &UserBalance{Id: "rif", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 105}
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter, userBudget: rifsBudget}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter, userBalance: rifsBalance}
|
||||
callCost, err := cd.GetCost()
|
||||
if callCost.Cost != 54.0 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 54.0, callCost)
|
||||
@@ -269,7 +269,7 @@ func BenchmarkRedisGetting(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
getter.GetActivationPeriodsOrFallback(cd.GetKey())
|
||||
@@ -283,7 +283,7 @@ func BenchmarkRedisRestoring(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.SearchStorageForPrefix()
|
||||
@@ -297,7 +297,7 @@ func BenchmarkRedisGetCost(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetCost()
|
||||
@@ -311,7 +311,7 @@ func BenchmarkKyotoGetting(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := cd.GetKey()
|
||||
@@ -326,7 +326,7 @@ func BenchmarkKyotoRestoring(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.SearchStorageForPrefix()
|
||||
@@ -340,7 +340,7 @@ func BenchmarkSplitting(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd.SearchStorageForPrefix()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -355,7 +355,7 @@ func BenchmarkKyotoGetCost(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetCost()
|
||||
@@ -369,7 +369,7 @@ func BenchmarkMongoGetting(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
getter.GetActivationPeriodsOrFallback(cd.GetKey())
|
||||
@@ -383,7 +383,7 @@ func BenchmarkMongoGetCost(b *testing.B) {
|
||||
|
||||
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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetCost()
|
||||
@@ -394,7 +394,7 @@ func BenchmarkKyotoSingleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewKyotoStorage("../data/test.kch")
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
@@ -405,7 +405,7 @@ func BenchmarkKyotoMultipleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewKyotoStorage("../data/test.kch")
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
@@ -416,7 +416,7 @@ func BenchmarkRedisSingleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewRedisStorage("", 10)
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
@@ -427,7 +427,7 @@ func BenchmarkRedisMultipleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewRedisStorage("", 10)
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
@@ -438,7 +438,7 @@ func BenchmarkMongoSingleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewMongoStorage("127.0.0.1", "test")
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
@@ -449,7 +449,7 @@ func BenchmarkMongoMultipleGetSessionTime(b *testing.B) {
|
||||
b.StopTimer()
|
||||
getter, _ := NewMongoStorage("127.0.0.1", "test")
|
||||
defer getter.Close()
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400}
|
||||
cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cd.GetMaxSessionTime()
|
||||
|
||||
@@ -27,8 +27,9 @@ import (
|
||||
|
||||
type MinuteBucket struct {
|
||||
Seconds float64
|
||||
Priority int
|
||||
Weight float64
|
||||
Price float64
|
||||
Percentage float64 // percentage from standard price
|
||||
DestinationId string
|
||||
destination *Destination
|
||||
precision int
|
||||
@@ -39,7 +40,7 @@ Serializes the minute bucket for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (mb *MinuteBucket) store() (result string) {
|
||||
result += strconv.Itoa(int(mb.Seconds)) + "|"
|
||||
result += strconv.Itoa(int(mb.Priority)) + "|"
|
||||
result += strconv.FormatFloat(mb.Weight, 'f', -1, 64) + "|"
|
||||
result += strconv.FormatFloat(mb.Price, 'f', -1, 64) + "|"
|
||||
result += mb.DestinationId
|
||||
return
|
||||
@@ -51,7 +52,7 @@ De-serializes the minute bucket for the storage. Used for key-value storages.
|
||||
func (mb *MinuteBucket) restore(input string) {
|
||||
elements := strings.Split(input, "|")
|
||||
mb.Seconds, _ = strconv.ParseFloat(elements[0], 64)
|
||||
mb.Priority, _ = strconv.Atoi(elements[1])
|
||||
mb.Weight, _ = strconv.ParseFloat(elements[1], 64)
|
||||
mb.Price, _ = strconv.ParseFloat(elements[2], 64)
|
||||
mb.DestinationId = elements[3]
|
||||
}
|
||||
@@ -59,9 +60,9 @@ func (mb *MinuteBucket) restore(input string) {
|
||||
/*
|
||||
Returns the destination loading it from the storage if necessary.
|
||||
*/
|
||||
func (mb *MinuteBucket) getDestination(storage StorageGetter) (dest *Destination) {
|
||||
func (mb *MinuteBucket) getDestination() (dest *Destination) {
|
||||
if mb.destination == nil {
|
||||
mb.destination, _ = storage.GetDestination(mb.DestinationId)
|
||||
mb.destination, _ = storageGetter.GetDestination(mb.DestinationId)
|
||||
}
|
||||
return mb.destination
|
||||
}
|
||||
|
||||
@@ -29,6 +29,6 @@ type StorageGetter interface {
|
||||
SetDestination(*Destination) error
|
||||
GetTariffPlan(string) (*TariffPlan, error)
|
||||
SetTariffPlan(*TariffPlan) error
|
||||
GetUserBudget(string) (*UserBudget, error)
|
||||
SetUserBudget(*UserBudget) error
|
||||
GetUserBalance(string) (*UserBalance, error)
|
||||
SetUserBalance(*UserBalance) error
|
||||
}
|
||||
|
||||
@@ -97,14 +97,14 @@ func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) error {
|
||||
return ks.db.Set([]byte(tp.Id), []byte(tp.store()))
|
||||
}
|
||||
|
||||
func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) {
|
||||
func (ks *KyotoStorage) GetUserBalance(key string) (ub *UserBalance, err error) {
|
||||
if values, err := ks.db.Get([]byte(key)); err == nil {
|
||||
ub = &UserBudget{Id: key}
|
||||
ub = &UserBalance{Id: key}
|
||||
ub.restore(string(values))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ks *KyotoStorage) SetUserBudget(ub *UserBudget) error {
|
||||
func (ks *KyotoStorage) SetUserBalance(ub *UserBalance) error {
|
||||
return ks.db.Set([]byte(ub.Id), []byte(ub.store()))
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewMongoStorage(address, db string) (*MongoStorage, error) {
|
||||
index = mgo.Index{Key: []string{"id"}, Unique: true, DropDups: true, Background: true}
|
||||
err = session.DB(db).C("destinations").EnsureIndex(index)
|
||||
err = session.DB(db).C("tariffPlans").EnsureIndex(index)
|
||||
err = session.DB(db).C("userBudget").EnsureIndex(index)
|
||||
err = session.DB(db).C("userBalance").EnsureIndex(index)
|
||||
|
||||
return &MongoStorage{db: session.DB(db), session: session}, nil
|
||||
}
|
||||
@@ -96,14 +96,14 @@ func (ms *MongoStorage) SetTariffPlan(tp *TariffPlan) error {
|
||||
return ndb.Insert(&tp)
|
||||
}
|
||||
|
||||
func (ms *MongoStorage) GetUserBudget(key string) (result *UserBudget, err error) {
|
||||
ndb := ms.db.C("userBudget")
|
||||
result = &UserBudget{}
|
||||
func (ms *MongoStorage) GetUserBalance(key string) (result *UserBalance, err error) {
|
||||
ndb := ms.db.C("userBalance")
|
||||
result = &UserBalance{}
|
||||
err = ndb.Find(bson.M{"id": key}).One(result)
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MongoStorage) SetUserBudget(ub *UserBudget) error {
|
||||
ndb := ms.db.C("userBudget")
|
||||
func (ms *MongoStorage) SetUserBalance(ub *UserBalance) error {
|
||||
ndb := ms.db.C("userBalance")
|
||||
return ndb.Insert(&ub)
|
||||
}
|
||||
|
||||
@@ -99,16 +99,16 @@ func (rs *RedisStorage) SetTariffPlan(tp *TariffPlan) error {
|
||||
return rs.db.Set(tp.Id, tp.store())
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) GetUserBudget(key string) (ub *UserBudget, err error) {
|
||||
func (rs *RedisStorage) GetUserBalance(key string) (ub *UserBalance, err error) {
|
||||
//rs.db.Select(rs.dbNb + 3)
|
||||
if values, err := rs.db.Get(key); err == nil {
|
||||
ub = &UserBudget{Id: key}
|
||||
ub = &UserBalance{Id: key}
|
||||
ub.restore(values.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) SetUserBudget(ub *UserBudget) error {
|
||||
func (rs *RedisStorage) SetUserBalance(ub *UserBalance) error {
|
||||
//rs.db.Select(rs.dbNb + 3)
|
||||
return rs.db.Set(ub.Id, ub.store())
|
||||
}
|
||||
|
||||
@@ -20,80 +20,43 @@ package timespans
|
||||
|
||||
import (
|
||||
// "log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
const (
|
||||
CREDIT = "CREDIT"
|
||||
SMS = "SMS"
|
||||
TRAFFIC = "TRAFFIC"
|
||||
// Direction type
|
||||
INBOUND = "IN"
|
||||
OUTBOUND = "OUT"
|
||||
// Balance types
|
||||
CREDIT = "MONETARY"
|
||||
SMS = "SMS"
|
||||
TRAFFIC = "INTERNET"
|
||||
)
|
||||
|
||||
/*
|
||||
Structure describing a tariff plan's number of bonus items. It is uset to restore
|
||||
these numbers to the user budget every month.
|
||||
these numbers to the user balance every month.
|
||||
*/
|
||||
type TariffPlan struct {
|
||||
Id string
|
||||
balanceMap map[string]float64
|
||||
Bonuses []*Bonus
|
||||
MinuteBuckets []*MinuteBucket
|
||||
VolumeDiscountThresholds []*VolumeDiscount
|
||||
Id string
|
||||
balanceMap map[string]float64
|
||||
Actions []*Action
|
||||
MinuteBuckets []*MinuteBucket
|
||||
}
|
||||
|
||||
/*
|
||||
Serializes the tariff plan for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (tp *TariffPlan) store() (result string) {
|
||||
result += strconv.FormatFloat(tp.SmsCredit, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(tp.Traffic, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(tp.ReceivedCallSecondsLimit, 'f', -1, 64) + ";"
|
||||
if tp.RecivedCallBonus == nil {
|
||||
tp.RecivedCallBonus = &RecivedCallBonus{}
|
||||
}
|
||||
result += tp.RecivedCallBonus.store() + ";"
|
||||
for i, mb := range tp.MinuteBuckets {
|
||||
if i > 0 {
|
||||
result += ","
|
||||
}
|
||||
result += mb.store()
|
||||
}
|
||||
if tp.VolumeDiscountThresholds != nil {
|
||||
result += ";"
|
||||
}
|
||||
for i, vd := range tp.VolumeDiscountThresholds {
|
||||
if i > 0 {
|
||||
result += ","
|
||||
}
|
||||
result += strconv.FormatFloat(vd.Volume, 'f', -1, 64) + "|" + strconv.FormatFloat(vd.Discount, 'f', -1, 64)
|
||||
}
|
||||
result = strings.TrimRight(result, ";")
|
||||
return
|
||||
buf := new(bytes.Buffer)
|
||||
gob.NewEncoder(buf).Encode(tp)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
De-serializes the tariff plan for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (tp *TariffPlan) restore(input string) {
|
||||
elements := strings.Split(input, ";")
|
||||
tp.SmsCredit, _ = strconv.ParseFloat(elements[0], 64)
|
||||
tp.Traffic, _ = strconv.ParseFloat(elements[1], 64)
|
||||
tp.ReceivedCallSecondsLimit, _ = strconv.ParseFloat(elements[2], 64)
|
||||
tp.RecivedCallBonus = &RecivedCallBonus{}
|
||||
tp.RecivedCallBonus.restore(elements[3])
|
||||
for _, mbs := range strings.Split(elements[4], ",") {
|
||||
mb := &MinuteBucket{}
|
||||
mb.restore(mbs)
|
||||
tp.MinuteBuckets = append(tp.MinuteBuckets, mb)
|
||||
}
|
||||
if len(elements) > 5 {
|
||||
for _, vdss := range strings.Split(elements[5], ",") {
|
||||
vd := &VolumeDiscount{}
|
||||
vds := strings.Split(vdss, "|")
|
||||
vd.Volume, _ = strconv.ParseFloat(vds[0], 64)
|
||||
vd.Discount, _ = strconv.ParseFloat(vds[1], 64)
|
||||
tp.VolumeDiscountThresholds = append(tp.VolumeDiscountThresholds, vd)
|
||||
}
|
||||
}
|
||||
gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(tp)
|
||||
}
|
||||
|
||||
@@ -64,13 +64,13 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) {
|
||||
} else {
|
||||
cost = ts.GetDuration().Seconds() * ts.Interval.Price
|
||||
}
|
||||
if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil {
|
||||
userBudget.mux.RLock()
|
||||
if percentageDiscount, err := userBudget.getVolumeDiscount(cd.storageGetter); err == nil && percentageDiscount > 0 {
|
||||
cost *= (100 - percentageDiscount) / 100
|
||||
}
|
||||
userBudget.mux.RUnlock()
|
||||
}
|
||||
// if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
|
||||
// userBalance.mux.RLock()
|
||||
// if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 {
|
||||
// cost *= (100 - percentageDiscount) / 100
|
||||
// }
|
||||
// userBalance.mux.RUnlock()
|
||||
// }
|
||||
ts.Cost = cost
|
||||
return
|
||||
}
|
||||
|
||||
261
timespans/userbalance.go
Normal file
261
timespans/userbalance.go
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012 Radu Ioan Fericean
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package timespans
|
||||
|
||||
import (
|
||||
// "log"
|
||||
"sort"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
UB_TYPE_POSTPAID = "postpaid"
|
||||
UB_TYPE_PREPAID = "prepaid"
|
||||
)
|
||||
|
||||
var (
|
||||
storageGetter StorageGetter
|
||||
)
|
||||
|
||||
/*
|
||||
Structure containing information about user's credit (minutes, cents, sms...).'
|
||||
*/
|
||||
type UserBalance struct {
|
||||
Id string
|
||||
Type string // prepaid-postpaid
|
||||
BalanceMap map[string]float64
|
||||
UnitsCounters []*UnitsCounter
|
||||
TariffPlanId string
|
||||
tariffPlan *TariffPlan
|
||||
MinuteBuckets []*MinuteBucket
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
/*
|
||||
Error type for overflowed debit methods.
|
||||
*/
|
||||
type AmountTooBig byte
|
||||
|
||||
func (a AmountTooBig) Error() string {
|
||||
return "Amount excedes balance!"
|
||||
}
|
||||
|
||||
/*
|
||||
Structure to store minute buckets according to weight, precision or price.
|
||||
*/
|
||||
type bucketsorter []*MinuteBucket
|
||||
|
||||
func (bs bucketsorter) Len() int {
|
||||
return len(bs)
|
||||
}
|
||||
|
||||
func (bs bucketsorter) Swap(i, j int) {
|
||||
bs[i], bs[j] = bs[j], bs[i]
|
||||
}
|
||||
|
||||
func (bs bucketsorter) Less(j, i int) bool {
|
||||
return bs[i].Weight < bs[j].Weight ||
|
||||
bs[i].precision < bs[j].precision ||
|
||||
bs[i].Price > bs[j].Price
|
||||
}
|
||||
|
||||
/*
|
||||
Serializes the user balance for the storage. Used for key-value storage.
|
||||
*/
|
||||
func (ub *UserBalance) store() (result string) {
|
||||
buf := new(bytes.Buffer)
|
||||
gob.NewEncoder(buf).Encode(ub)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
De-serializes the user balance for the storage. Used for key-value storage.
|
||||
*/
|
||||
func (ub *UserBalance) restore(input string) {
|
||||
gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(ub)
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the tariff plan loading it from the storage if necessary.
|
||||
*/
|
||||
func (ub *UserBalance) getTariffPlan() (tp *TariffPlan, err error) {
|
||||
if ub.tariffPlan == nil && ub.TariffPlanId != "" {
|
||||
ub.tariffPlan, err = storageGetter.GetTariffPlan(ub.TariffPlanId)
|
||||
}
|
||||
return ub.tariffPlan, err
|
||||
}
|
||||
|
||||
/*
|
||||
Returns user's available minutes for the specified destination
|
||||
*/
|
||||
func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds float64, bucketList bucketsorter) {
|
||||
if len(ub.MinuteBuckets) == 0 {
|
||||
// log.Print("There are no minute buckets to check for user: ", ub.Id)
|
||||
return
|
||||
}
|
||||
for _, mb := range ub.MinuteBuckets {
|
||||
d := mb.getDestination()
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
contains, precision := d.containsPrefix(prefix)
|
||||
if contains {
|
||||
mb.precision = precision
|
||||
if mb.Seconds > 0 {
|
||||
bucketList = append(bucketList, mb)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(bucketList) // sorts the buckets according to priority, precision or price
|
||||
credit := ub.BalanceMap[CREDIT]
|
||||
for _, mb := range bucketList {
|
||||
s := mb.GetSecondsForCredit(credit)
|
||||
credit -= s * mb.Price
|
||||
seconds += s
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Debits some amount of user's money credit. Returns the remaining credit in user's balance.
|
||||
*/
|
||||
func (ub *UserBalance) debitMoneyBalance(amount float64) float64 {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
ub.BalanceMap[CREDIT] -= amount
|
||||
storageGetter.SetUserBalance(ub)
|
||||
return ub.BalanceMap[CREDIT]
|
||||
}
|
||||
|
||||
/*
|
||||
Debits the received amount of seconds from user's minute buckets.
|
||||
All the appropriate buckets will be debited until all amount of minutes is consumed.
|
||||
If the amount is bigger than the sum of all seconds in the minute buckets than nothing will be
|
||||
debited and an error will be returned.
|
||||
*/
|
||||
func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
avaliableNbSeconds, bucketList := ub.getSecondsForPrefix(prefix)
|
||||
if avaliableNbSeconds < amount {
|
||||
return new(AmountTooBig)
|
||||
}
|
||||
credit := ub.BalanceMap[CREDIT]
|
||||
// calculating money debit
|
||||
// this is needed because if the credit is less then the amount needed to be debited
|
||||
// we need to keep everything in place and return an error
|
||||
for _, mb := range bucketList {
|
||||
if mb.Seconds < amount {
|
||||
if mb.Price > 0 { // debit the money if the bucket has price
|
||||
credit -= mb.Seconds * mb.Price
|
||||
}
|
||||
} else {
|
||||
if mb.Price > 0 { // debit the money if the bucket has price
|
||||
credit -= amount * mb.Price
|
||||
}
|
||||
break
|
||||
}
|
||||
if credit < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if credit < 0 {
|
||||
return new(AmountTooBig)
|
||||
}
|
||||
ub.BalanceMap[CREDIT] = credit // credit is > 0
|
||||
for _, mb := range bucketList {
|
||||
if mb.Seconds < amount {
|
||||
amount -= mb.Seconds
|
||||
mb.Seconds = 0
|
||||
} else {
|
||||
mb.Seconds -= amount
|
||||
break
|
||||
}
|
||||
}
|
||||
storageGetter.SetUserBalance(ub)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Debits some amount of user's SMS balance. Returns the remaining SMS in user's balance.
|
||||
If the amount is bigger than the balance than nothing wil be debited and an error will be returned
|
||||
*/
|
||||
func (ub *UserBalance) debitSMSBuget(amount float64) (float64, error) {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
if ub.BalanceMap[SMS] < amount {
|
||||
return ub.BalanceMap[SMS], new(AmountTooBig)
|
||||
}
|
||||
ub.BalanceMap[SMS] -= amount
|
||||
|
||||
storageGetter.SetUserBalance(ub)
|
||||
return ub.BalanceMap[SMS], nil
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the specified amount of seconds.
|
||||
*/
|
||||
func (ub *UserBalance) addReceivedCallSeconds(direction, tor, destination string, amount float64) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
for
|
||||
ub.ReceivedCallSeconds += amount
|
||||
if tariffPlan, err := ub.getTariffPlan(); tariffPlan != nil && err == nil {
|
||||
if ub.ReceivedCallSeconds >= tariffPlan.ReceivedCallSecondsLimit {
|
||||
ub.ReceivedCallSeconds -= tariffPlan.ReceivedCallSecondsLimit
|
||||
if tariffPlan.RecivedCallBonus != nil { // apply the bonus
|
||||
ub.BalanceMap[CREDIT] += tariffPlan.RecivedCallBonus.Credit
|
||||
ub.BalanceMap[SMS] += tariffPlan.RecivedCallBonus.SmsCredit
|
||||
ub.BalanceMap[TRAFFIC] += tariffPlan.RecivedCallBonus.Traffic
|
||||
if tariffPlan.RecivedCallBonus.MinuteBucket != nil {
|
||||
for _, mb := range ub.MinuteBuckets {
|
||||
if mb.DestinationId == tariffPlan.RecivedCallBonus.MinuteBucket.DestinationId {
|
||||
mb.Seconds += tariffPlan.RecivedCallBonus.MinuteBucket.Seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return storageGetter.SetUserBalance(ub)
|
||||
}
|
||||
|
||||
/*
|
||||
Resets the user balance items to their tariff plan values.
|
||||
*/
|
||||
func (ub *UserBalance) resetUserBalance() (err error) {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
if tp, err := ub.getTariffPlan(storageGetter); err == nil {
|
||||
ub.SmsCredit = tp.SmsCredit
|
||||
ub.Traffic = tp.Traffic
|
||||
ub.MinuteBuckets = make([]*MinuteBucket, 0)
|
||||
for _, bucket := range tp.MinuteBuckets {
|
||||
mb := &MinuteBucket{Seconds: bucket.Seconds,
|
||||
Priority: bucket.Priority,
|
||||
Price: bucket.Price,
|
||||
DestinationId: bucket.DestinationId}
|
||||
ub.MinuteBuckets = append(ub.MinuteBuckets, mb)
|
||||
}
|
||||
err = storageGetter.SetUserBalance(ub)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -29,13 +29,13 @@ var (
|
||||
retea = &Destination{Id: "retea", Prefixes: []string{"0723", "0724"}}
|
||||
)
|
||||
|
||||
func TestUserBudgetStoreRestore(t *testing.T) {
|
||||
func TestUserBalanceStoreRestore(t *testing.T) {
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
s := rifsBudget.store()
|
||||
ub1 := &UserBudget{Id: "other"}
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
s := rifsBalance.store()
|
||||
ub1 := &UserBalance{Id: "other"}
|
||||
ub1.restore(s)
|
||||
if ub1.store() != s {
|
||||
t.Errorf("Expected %q was %q", s, ub1.store())
|
||||
@@ -47,7 +47,7 @@ func TestGetSecondsForPrefix(t *testing.T) {
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, destination: retea}
|
||||
tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
|
||||
ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
seconds, bucketList := ub1.getSecondsForPrefix(nil, "0723")
|
||||
expected := 110.0
|
||||
if seconds != expected || bucketList[0].Priority < bucketList[1].Priority {
|
||||
@@ -60,7 +60,7 @@ func TestGetPricedSeconds(t *testing.T) {
|
||||
b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea}
|
||||
tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
|
||||
ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
seconds, bucketList := ub1.getSecondsForPrefix(nil, "0723")
|
||||
expected := 21.0
|
||||
if seconds != expected || bucketList[0].Priority < bucketList[1].Priority {
|
||||
@@ -68,35 +68,35 @@ func TestGetPricedSeconds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserBudgetKyotoStore(t *testing.T) {
|
||||
func TestUserBalanceKyotoStore(t *testing.T) {
|
||||
getter, _ := NewKyotoStorage("../data/test.kch")
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
result, _ := getter.GetUserBudget(rifsBudget.Id)
|
||||
if !reflect.DeepEqual(rifsBudget, result) {
|
||||
t.Log(rifsBudget)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
result, _ := getter.GetUserBalance(rifsBalance.Id)
|
||||
if !reflect.DeepEqual(rifsBalance, result) {
|
||||
t.Log(rifsBalance)
|
||||
t.Log(result)
|
||||
t.Errorf("Expected %q was %q", rifsBudget, result)
|
||||
t.Errorf("Expected %q was %q", rifsBalance, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserBudgetRedisStore(t *testing.T) {
|
||||
func TestUserBalanceRedisStore(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
result, _ := getter.GetUserBudget(rifsBudget.Id)
|
||||
if !reflect.DeepEqual(rifsBudget, result) {
|
||||
t.Errorf("Expected %q was %q", rifsBudget, result)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
result, _ := getter.GetUserBalance(rifsBalance.Id)
|
||||
if !reflect.DeepEqual(rifsBalance, result) {
|
||||
t.Errorf("Expected %q was %q", rifsBalance, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserBudgetMongoStore(t *testing.T) {
|
||||
func TestUserBalanceMongoStore(t *testing.T) {
|
||||
getter, err := NewMongoStorage("127.0.0.1", "test")
|
||||
if err != nil {
|
||||
return
|
||||
@@ -104,244 +104,244 @@ func TestUserBudgetMongoStore(t *testing.T) {
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
result, _ := getter.GetUserBudget(rifsBudget.Id)
|
||||
if !reflect.DeepEqual(rifsBudget, result) {
|
||||
t.Errorf("Expected %q was %q", rifsBudget, result)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
result, _ := getter.GetUserBalance(rifsBalance.Id)
|
||||
if !reflect.DeepEqual(rifsBalance, result) {
|
||||
t.Errorf("Expected %q was %q", rifsBalance, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMoneyBudget(t *testing.T) {
|
||||
func TestDebitMoneyBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "o4her", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBudget.debitMoneyBudget(getter, 6)
|
||||
if rifsBudget.Credit != 15 || result != rifsBudget.Credit {
|
||||
t.Errorf("Expected %v was %v", 15, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "o4her", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBalance.debitMoneyBalance(getter, 6)
|
||||
if rifsBalance.Credit != 15 || result != rifsBalance.Credit {
|
||||
t.Errorf("Expected %v was %v", 15, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitAllMoneyBudget(t *testing.T) {
|
||||
func TestDebitAllMoneyBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
rifsBudget.debitMoneyBudget(getter, 21)
|
||||
result := rifsBudget.debitMoneyBudget(getter, 0)
|
||||
if rifsBudget.Credit != 0 || result != rifsBudget.Credit {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
rifsBalance.debitMoneyBalance(getter, 21)
|
||||
result := rifsBalance.debitMoneyBalance(getter, 0)
|
||||
if rifsBalance.Credit != 0 || result != rifsBalance.Credit {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMoreMoneyBudget(t *testing.T) {
|
||||
func TestDebitMoreMoneyBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBudget.debitMoneyBudget(getter, 22)
|
||||
if rifsBudget.Credit != -1 || result != rifsBudget.Credit {
|
||||
t.Errorf("Expected %v was %v", -1, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBalance.debitMoneyBalance(getter, 22)
|
||||
if rifsBalance.Credit != -1 || result != rifsBalance.Credit {
|
||||
t.Errorf("Expected %v was %v", -1, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitNegativeMoneyBudget(t *testing.T) {
|
||||
func TestDebitNegativeMoneyBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBudget.debitMoneyBudget(getter, -15)
|
||||
if rifsBudget.Credit != 36 || result != rifsBudget.Credit {
|
||||
t.Errorf("Expected %v was %v", 36, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
result := rifsBalance.debitMoneyBalance(getter, -15)
|
||||
if rifsBalance.Credit != 36 || result != rifsBalance.Credit {
|
||||
t.Errorf("Expected %v was %v", 36, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMinuteBudget(t *testing.T) {
|
||||
func TestDebitMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 6, "0723")
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 6, "0723")
|
||||
if b2.Seconds != 94 || err != nil {
|
||||
t.Log(err)
|
||||
t.Errorf("Expected %v was %v", 94, b2.Seconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMultipleBucketsMinuteBudget(t *testing.T) {
|
||||
func TestDebitMultipleBucketsMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 105, "0723")
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 105, "0723")
|
||||
if b2.Seconds != 0 || b1.Seconds != 5 || err != nil {
|
||||
t.Log(err)
|
||||
t.Errorf("Expected %v was %v", 0, b2.Seconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitAllMinuteBudget(t *testing.T) {
|
||||
func TestDebitAllMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 110, "0723")
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 110, "0723")
|
||||
if b2.Seconds != 0 || b1.Seconds != 0 || err != nil {
|
||||
t.Errorf("Expected %v was %v", 0, b2.Seconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMoreMinuteBudget(t *testing.T) {
|
||||
func TestDebitMoreMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 115, "0723")
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 115, "0723")
|
||||
if b2.Seconds != 100 || b1.Seconds != 10 || err == nil {
|
||||
t.Errorf("Expected %v was %v", 1000, b2.Seconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitPriceMinuteBudget(t *testing.T) {
|
||||
func TestDebitPriceMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 1.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 5, "0723")
|
||||
if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 16 {
|
||||
t.Log(rifsBudget.Credit)
|
||||
t.Errorf("Expected %v was %v", 16, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 5, "0723")
|
||||
if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 16 {
|
||||
t.Log(rifsBalance.Credit)
|
||||
t.Errorf("Expected %v was %v", 16, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitPriceAllMinuteBudget(t *testing.T) {
|
||||
func TestDebitPriceAllMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 1.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 21, "0723")
|
||||
if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 0 {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBudget.Credit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 21, "0723")
|
||||
if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 0 {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitPriceMoreMinuteBudget(t *testing.T) {
|
||||
func TestDebitPriceMoreMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 1.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, 25, "0723")
|
||||
if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBudget.Credit != 21 {
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, 25, "0723")
|
||||
if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBalance.Credit != 21 {
|
||||
t.Log(b1, b2, err)
|
||||
t.Errorf("Expected %v was %v", 21, rifsBudget.Credit)
|
||||
t.Errorf("Expected %v was %v", 21, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitPriceNegativeMinuteBudget(t *testing.T) {
|
||||
func TestDebitPriceNegativeMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 1.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, -15, "0723")
|
||||
if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 36 {
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, -15, "0723")
|
||||
if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 36 {
|
||||
t.Log(b1, b2, err)
|
||||
t.Errorf("Expected %v was %v", 36, rifsBudget.Credit)
|
||||
t.Errorf("Expected %v was %v", 36, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitNegativeMinuteBudget(t *testing.T) {
|
||||
func TestDebitNegativeMinuteBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBudget.debitMinutesBudget(getter, -15, "0723")
|
||||
if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 21 {
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10}
|
||||
err := rifsBalance.debitMinutesBalance(getter, -15, "0723")
|
||||
if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 21 {
|
||||
t.Log(b1, b2, err)
|
||||
t.Errorf("Expected %v was %v", 21, rifsBudget.Credit)
|
||||
t.Errorf("Expected %v was %v", 21, rifsBalance.Credit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitSMSBudget(t *testing.T) {
|
||||
func TestDebitSMSBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBudget.debitSMSBuget(getter, 12)
|
||||
if rifsBudget.SmsCredit != 88 || result != rifsBudget.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 88, rifsBudget.SmsCredit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBalance.debitSMSBuget(getter, 12)
|
||||
if rifsBalance.SmsCredit != 88 || result != rifsBalance.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 88, rifsBalance.SmsCredit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitAllSMSBudget(t *testing.T) {
|
||||
func TestDebitAllSMSBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBudget.debitSMSBuget(getter, 100)
|
||||
if rifsBudget.SmsCredit != 0 || result != rifsBudget.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBudget.SmsCredit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBalance.debitSMSBuget(getter, 100)
|
||||
if rifsBalance.SmsCredit != 0 || result != rifsBalance.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 0, rifsBalance.SmsCredit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitMoreSMSBudget(t *testing.T) {
|
||||
func TestDebitMoreSMSBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBudget.debitSMSBuget(getter, 110)
|
||||
if rifsBudget.SmsCredit != 100 || result != rifsBudget.SmsCredit || err == nil {
|
||||
t.Errorf("Expected %v was %v", 100, rifsBudget.SmsCredit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBalance.debitSMSBuget(getter, 110)
|
||||
if rifsBalance.SmsCredit != 100 || result != rifsBalance.SmsCredit || err == nil {
|
||||
t.Errorf("Expected %v was %v", 100, rifsBalance.SmsCredit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebitNegativeSMSBudget(t *testing.T) {
|
||||
func TestDebitNegativeSMSBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBudget.debitSMSBuget(getter, -15)
|
||||
if rifsBudget.SmsCredit != 115 || result != rifsBudget.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 115, rifsBudget.SmsCredit)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10}
|
||||
result, err := rifsBalance.debitSMSBuget(getter, -15)
|
||||
if rifsBalance.SmsCredit != 115 || result != rifsBalance.SmsCredit || err != nil {
|
||||
t.Errorf("Expected %v was %v", 115, rifsBalance.SmsCredit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetUserBudget(t *testing.T) {
|
||||
func TestResetUserBalance(t *testing.T) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
rifsBudget.MinuteBuckets[0].Seconds, rifsBudget.MinuteBuckets[1].Seconds = 0.0, 0.0
|
||||
err := rifsBudget.resetUserBudget(getter)
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
rifsBalance.MinuteBuckets[0].Seconds, rifsBalance.MinuteBuckets[1].Seconds = 0.0, 0.0
|
||||
err := rifsBalance.resetUserBalance(getter)
|
||||
if err != nil ||
|
||||
rifsBudget.MinuteBuckets[0] == b1 ||
|
||||
rifsBudget.MinuteBuckets[0].Seconds != seara.MinuteBuckets[0].Seconds ||
|
||||
rifsBudget.MinuteBuckets[1].Seconds != seara.MinuteBuckets[1].Seconds ||
|
||||
rifsBudget.SmsCredit != seara.SmsCredit {
|
||||
t.Log(rifsBudget.MinuteBuckets[0])
|
||||
t.Log(rifsBudget.MinuteBuckets[1])
|
||||
t.Log(rifsBudget.SmsCredit)
|
||||
t.Log(rifsBudget.Traffic)
|
||||
t.Errorf("Expected %v was %v", seara, rifsBudget)
|
||||
rifsBalance.MinuteBuckets[0] == b1 ||
|
||||
rifsBalance.MinuteBuckets[0].Seconds != seara.MinuteBuckets[0].Seconds ||
|
||||
rifsBalance.MinuteBuckets[1].Seconds != seara.MinuteBuckets[1].Seconds ||
|
||||
rifsBalance.SmsCredit != seara.SmsCredit {
|
||||
t.Log(rifsBalance.MinuteBuckets[0])
|
||||
t.Log(rifsBalance.MinuteBuckets[1])
|
||||
t.Log(rifsBalance.SmsCredit)
|
||||
t.Log(rifsBalance.Traffic)
|
||||
t.Errorf("Expected %v was %v", seara, rifsBalance)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -349,8 +349,8 @@ func TestResetUserBudget(t *testing.T) {
|
||||
func TestGetVolumeDiscountHaving(t *testing.T) {
|
||||
vd := &VolumeDiscount{100, 11}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd}}
|
||||
rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 100}
|
||||
result, err := rifsBudget.getVolumeDiscount(nil)
|
||||
rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 100}
|
||||
result, err := rifsBalance.getVolumeDiscount(nil)
|
||||
if err != nil || result != 11 {
|
||||
t.Errorf("Expected %v was %v", 11, result)
|
||||
}
|
||||
@@ -359,8 +359,8 @@ func TestGetVolumeDiscountHaving(t *testing.T) {
|
||||
func TestGetVolumeDiscountNotHaving(t *testing.T) {
|
||||
vd := &VolumeDiscount{100, 11}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd}}
|
||||
rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 99}
|
||||
result, err := rifsBudget.getVolumeDiscount(nil)
|
||||
rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 99}
|
||||
result, err := rifsBalance.getVolumeDiscount(nil)
|
||||
if err != nil || result != 0 {
|
||||
t.Errorf("Expected %v was %v", 0, result)
|
||||
}
|
||||
@@ -370,8 +370,8 @@ func TestGetVolumeDiscountSteps(t *testing.T) {
|
||||
vd1 := &VolumeDiscount{100, 11}
|
||||
vd2 := &VolumeDiscount{500, 20}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd1, vd2}}
|
||||
rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 551}
|
||||
result, err := rifsBudget.getVolumeDiscount(nil)
|
||||
rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 551}
|
||||
result, err := rifsBalance.getVolumeDiscount(nil)
|
||||
if err != nil || result != 20 {
|
||||
t.Errorf("Expected %v was %v", 20, result)
|
||||
}
|
||||
@@ -382,10 +382,10 @@ func TestRecivedCallsBonus(t *testing.T) {
|
||||
defer getter.Close()
|
||||
rcb := &RecivedCallBonus{Credit: 100}
|
||||
seara := &TariffPlan{Id: "seara_voo", SmsCredit: 100, ReceivedCallSecondsLimit: 10, RecivedCallBonus: rcb}
|
||||
rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, ReceivedCallSeconds: 1}
|
||||
err := rifsBudget.addReceivedCallSeconds(getter, 12)
|
||||
if err != nil || rifsBudget.Credit != 121 || rifsBudget.ReceivedCallSeconds != 3 {
|
||||
t.Error("Wrong Received call bonus procedure: ", rifsBudget)
|
||||
rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, ReceivedCallSeconds: 1}
|
||||
err := rifsBalance.addReceivedCallSeconds(getter, 12)
|
||||
if err != nil || rifsBalance.Credit != 121 || rifsBalance.ReceivedCallSeconds != 3 {
|
||||
t.Error("Wrong Received call bonus procedure: ", rifsBalance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,49 +397,49 @@ func BenchmarkGetSecondForPrefix(b *testing.B) {
|
||||
b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea}
|
||||
tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
|
||||
ub1 := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
ub1 := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ub1.getSecondsForPrefix(nil, "0723")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUserBudgetKyotoStoreRestore(b *testing.B) {
|
||||
func BenchmarkUserBalanceKyotoStoreRestore(b *testing.B) {
|
||||
getter, _ := NewKyotoStorage("../data/test.kch")
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
for i := 0; i < b.N; i++ {
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
getter.GetUserBudget(rifsBudget.Id)
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
getter.GetUserBalance(rifsBalance.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUserBudgetRedisStoreRestore(b *testing.B) {
|
||||
func BenchmarkUserBalanceRedisStoreRestore(b *testing.B) {
|
||||
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
for i := 0; i < b.N; i++ {
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
getter.GetUserBudget(rifsBudget.Id)
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
getter.GetUserBalance(rifsBalance.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUserBudgetMongoStoreRestore(b *testing.B) {
|
||||
func BenchmarkUserBalanceMongoStoreRestore(b *testing.B) {
|
||||
getter, _ := NewMongoStorage("127.0.0.1", "test")
|
||||
defer getter.Close()
|
||||
b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"}
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"}
|
||||
seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10}
|
||||
for i := 0; i < b.N; i++ {
|
||||
getter.SetUserBudget(rifsBudget)
|
||||
getter.GetUserBudget(rifsBudget.Id)
|
||||
getter.SetUserBalance(rifsBalance)
|
||||
getter.GetUserBalance(rifsBalance.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ func BenchmarkGetSecondsForPrefix(b *testing.B) {
|
||||
b2 := &MinuteBucket{Seconds: 100, Priority: 20, destination: retea}
|
||||
tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}}
|
||||
|
||||
ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10}
|
||||
for i := 0; i < b.N; i++ {
|
||||
ub1.getSecondsForPrefix(nil, "0723")
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2012 Radu Ioan Fericean
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package timespans
|
||||
|
||||
import (
|
||||
// "log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
UB_TYPE_POSTPAID = "postpaid"
|
||||
UB_TYPE_PREPAID = "prepaid"
|
||||
)
|
||||
|
||||
/*
|
||||
Structure conatining information about user's credit (minutes, cents, sms...).'
|
||||
*/
|
||||
type UserBudget struct {
|
||||
Id string
|
||||
Type string // prepaid-postpaid
|
||||
BalanceMap map[string]float64
|
||||
OutboundVolumes []*TrafficVolume
|
||||
InboundVolumes []*TrafficVolume
|
||||
ResetDayOfTheMonth int
|
||||
TariffPlanId string
|
||||
tariffPlan *TariffPlan
|
||||
MinuteBuckets []*MinuteBucket
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
/*
|
||||
Error type for overflowed debit methods.
|
||||
*/
|
||||
type AmountTooBig byte
|
||||
|
||||
func (a AmountTooBig) Error() string {
|
||||
return "Amount excedes budget!"
|
||||
}
|
||||
|
||||
/*
|
||||
Structure to store minute buckets according to priority, precision or price.
|
||||
*/
|
||||
type bucketsorter []*MinuteBucket
|
||||
|
||||
func (bs bucketsorter) Len() int {
|
||||
return len(bs)
|
||||
}
|
||||
|
||||
func (bs bucketsorter) Swap(i, j int) {
|
||||
bs[i], bs[j] = bs[j], bs[i]
|
||||
}
|
||||
|
||||
func (bs bucketsorter) Less(j, i int) bool {
|
||||
return bs[i].Priority < bs[j].Priority ||
|
||||
bs[i].precision < bs[j].precision ||
|
||||
bs[i].Price > bs[j].Price
|
||||
}
|
||||
|
||||
/*
|
||||
Serializes the user budget for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (ub *UserBudget) store() (result string) {
|
||||
result += ub.Type + ";"
|
||||
result += strconv.FormatFloat(ub.Credit, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(ub.SmsCredit, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(ub.Traffic, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(ub.VolumeDiscountSeconds, 'f', -1, 64) + ";"
|
||||
result += strconv.FormatFloat(ub.ReceivedCallSeconds, 'f', -1, 64) + ";"
|
||||
result += strconv.Itoa(ub.ResetDayOfTheMonth) + ";"
|
||||
result += ub.TariffPlanId
|
||||
if ub.MinuteBuckets != nil {
|
||||
result += ";"
|
||||
}
|
||||
for i, mb := range ub.MinuteBuckets {
|
||||
if i > 0 {
|
||||
result += ","
|
||||
}
|
||||
result += mb.store()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
De-serializes the user budget for the storage. Used for key-value storages.
|
||||
*/
|
||||
func (ub *UserBudget) restore(input string) {
|
||||
elements := strings.Split(input, ";")
|
||||
ub.Type = elements[0]
|
||||
ub.Credit, _ = strconv.ParseFloat(elements[1], 64)
|
||||
ub.SmsCredit, _ = strconv.ParseFloat(elements[2], 64)
|
||||
ub.Traffic, _ = strconv.ParseFloat(elements[3], 64)
|
||||
ub.VolumeDiscountSeconds, _ = strconv.ParseFloat(elements[4], 64)
|
||||
ub.ReceivedCallSeconds, _ = strconv.ParseFloat(elements[5], 64)
|
||||
ub.ResetDayOfTheMonth, _ = strconv.Atoi(elements[6])
|
||||
ub.TariffPlanId = elements[7]
|
||||
if len(elements) > 8 {
|
||||
for _, mbs := range strings.Split(elements[8], ",") {
|
||||
mb := &MinuteBucket{}
|
||||
mb.restore(mbs)
|
||||
ub.MinuteBuckets = append(ub.MinuteBuckets, mb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the tariff plan loading it from the storage if necessary.
|
||||
*/
|
||||
func (ub *UserBudget) getTariffPlan(storage StorageGetter) (tp *TariffPlan, err error) {
|
||||
if ub.tariffPlan == nil && ub.TariffPlanId != "" {
|
||||
ub.tariffPlan, err = storage.GetTariffPlan(ub.TariffPlanId)
|
||||
}
|
||||
return ub.tariffPlan, err
|
||||
}
|
||||
|
||||
/*
|
||||
Returns thevolume discount procentage according to the nuber of acumulated volume discount seconds.
|
||||
*/
|
||||
func (ub *UserBudget) getVolumeDiscount(storage StorageGetter) (float64, error) {
|
||||
tariffPlan, err := ub.getTariffPlan(storage)
|
||||
if err != nil || tariffPlan == nil {
|
||||
return 0.0, err
|
||||
}
|
||||
thresholds := len(tariffPlan.VolumeDiscountThresholds)
|
||||
for i, vd := range tariffPlan.VolumeDiscountThresholds {
|
||||
if ub.VolumeDiscountSeconds >= vd.Volume &&
|
||||
(i > thresholds-2 || ub.VolumeDiscountSeconds < tariffPlan.VolumeDiscountThresholds[i+1].Volume) {
|
||||
return vd.Discount, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Returns user's avaliable minutes for the specified destination
|
||||
*/
|
||||
func (ub *UserBudget) getSecondsForPrefix(sg StorageGetter, prefix string) (seconds float64, bucketList bucketsorter) {
|
||||
if len(ub.MinuteBuckets) == 0 {
|
||||
// log.Print("There are no minute buckets to check for user: ", ub.Id)
|
||||
return
|
||||
}
|
||||
for _, mb := range ub.MinuteBuckets {
|
||||
d := mb.getDestination(sg)
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
contains, precision := d.containsPrefix(prefix)
|
||||
if contains {
|
||||
mb.precision = precision
|
||||
if mb.Seconds > 0 {
|
||||
bucketList = append(bucketList, mb)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(bucketList) // sorts the buckets according to priority, precision or price
|
||||
credit := ub.Credit
|
||||
for _, mb := range bucketList {
|
||||
s := mb.GetSecondsForCredit(credit)
|
||||
credit -= s * mb.Price
|
||||
seconds += s
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Debits some amount of user's money credit. Returns the remaining credit in user's budget.
|
||||
*/
|
||||
func (ub *UserBudget) debitMoneyBudget(sg StorageGetter, amount float64) float64 {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
ub.Credit -= amount
|
||||
sg.SetUserBudget(ub)
|
||||
return ub.Credit
|
||||
}
|
||||
|
||||
/*
|
||||
Debits the recived amount of seconds from user's minute buckets.
|
||||
All the appropriate buckets will be debited until all amount of minutes is consumed.
|
||||
If the amount is bigger than the sum of all seconds in the minute buckets than nothing will be
|
||||
debited and an error will be returned.
|
||||
*/
|
||||
func (ub *UserBudget) debitMinutesBudget(sg StorageGetter, amount float64, prefix string) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
avaliableNbSeconds, bucketList := ub.getSecondsForPrefix(sg, prefix)
|
||||
if avaliableNbSeconds < amount {
|
||||
return new(AmountTooBig)
|
||||
}
|
||||
credit := ub.Credit
|
||||
// calculating money debit
|
||||
// this is needed because if the credit is less then the amount needed to be debited
|
||||
// we need to keep everithing in place and return an error
|
||||
for _, mb := range bucketList {
|
||||
if mb.Seconds < amount {
|
||||
if mb.Price > 0 { // debit the money if the bucket has price
|
||||
credit -= mb.Seconds * mb.Price
|
||||
}
|
||||
} else {
|
||||
if mb.Price > 0 { // debit the money if the bucket has price
|
||||
credit -= amount * mb.Price
|
||||
}
|
||||
break
|
||||
}
|
||||
if credit < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if credit < 0 {
|
||||
return new(AmountTooBig)
|
||||
}
|
||||
ub.Credit = credit // credit is > 0
|
||||
for _, mb := range bucketList {
|
||||
if mb.Seconds < amount {
|
||||
amount -= mb.Seconds
|
||||
mb.Seconds = 0
|
||||
} else {
|
||||
mb.Seconds -= amount
|
||||
break
|
||||
}
|
||||
}
|
||||
sg.SetUserBudget(ub)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Debits some amount of user's SMS budget. Returns the remaining SMS in user's budget.
|
||||
If the amount is bigger than the budget than nothing wil be debited and an error will be returned
|
||||
*/
|
||||
func (ub *UserBudget) debitSMSBuget(sg StorageGetter, amount float64) (float64, error) {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
if ub.SmsCredit < amount {
|
||||
return ub.SmsCredit, new(AmountTooBig)
|
||||
}
|
||||
ub.SmsCredit -= amount
|
||||
|
||||
sg.SetUserBudget(ub)
|
||||
return ub.SmsCredit, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the the specified amount to volume discount seconds budget.
|
||||
*/
|
||||
func (ub *UserBudget) addVolumeDiscountSeconds(sg StorageGetter, amount float64) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
ub.VolumeDiscountSeconds += amount
|
||||
return sg.SetUserBudget(ub)
|
||||
}
|
||||
|
||||
/*
|
||||
Resets the volume discounts seconds (sets zero value).
|
||||
*/
|
||||
func (ub *UserBudget) resetVolumeDiscountSeconds(sg StorageGetter) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
ub.VolumeDiscountSeconds = 0
|
||||
return sg.SetUserBudget(ub)
|
||||
}
|
||||
|
||||
/*
|
||||
Adds the spcifeied amount of seconds to the reci.
|
||||
*/
|
||||
func (ub *UserBudget) addReceivedCallSeconds(sg StorageGetter, amount float64) error {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
ub.ReceivedCallSeconds += amount
|
||||
if tariffPlan, err := ub.getTariffPlan(sg); tariffPlan != nil && err == nil {
|
||||
if ub.ReceivedCallSeconds >= tariffPlan.ReceivedCallSecondsLimit {
|
||||
ub.ReceivedCallSeconds -= tariffPlan.ReceivedCallSecondsLimit
|
||||
if tariffPlan.RecivedCallBonus != nil { // apply the bonus
|
||||
ub.Credit += tariffPlan.RecivedCallBonus.Credit
|
||||
ub.SmsCredit += tariffPlan.RecivedCallBonus.SmsCredit
|
||||
ub.Traffic += tariffPlan.RecivedCallBonus.Traffic
|
||||
if tariffPlan.RecivedCallBonus.MinuteBucket != nil {
|
||||
for _, mb := range ub.MinuteBuckets {
|
||||
if mb.DestinationId == tariffPlan.RecivedCallBonus.MinuteBucket.DestinationId {
|
||||
mb.Seconds += tariffPlan.RecivedCallBonus.MinuteBucket.Seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sg.SetUserBudget(ub)
|
||||
}
|
||||
|
||||
/*
|
||||
Resets the user budget items to their tariff plan values.
|
||||
*/
|
||||
func (ub *UserBudget) resetUserBudget(sg StorageGetter) (err error) {
|
||||
ub.mux.Lock()
|
||||
defer ub.mux.Unlock()
|
||||
if tp, err := ub.getTariffPlan(sg); err == nil {
|
||||
ub.SmsCredit = tp.SmsCredit
|
||||
ub.Traffic = tp.Traffic
|
||||
ub.MinuteBuckets = make([]*MinuteBucket, 0)
|
||||
for _, bucket := range tp.MinuteBuckets {
|
||||
mb := &MinuteBucket{Seconds: bucket.Seconds,
|
||||
Priority: bucket.Priority,
|
||||
Price: bucket.Price,
|
||||
DestinationId: bucket.DestinationId}
|
||||
ub.MinuteBuckets = append(ub.MinuteBuckets, mb)
|
||||
}
|
||||
err = sg.SetUserBudget(ub)
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user