mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Merge branch 'master' of https://github.com/cgrates/cgrates
This commit is contained in:
@@ -135,7 +135,7 @@ func TestGetSpecialPricedSeconds(t *testing.T) {
|
||||
Destination: "0723",
|
||||
}
|
||||
seconds, credit, bucketList := ub1.getCreditForPrefix(cd)
|
||||
expected := 21 * time.Second
|
||||
expected := 20 * time.Second
|
||||
if credit != 0 || seconds != expected || len(bucketList) != 2 || bucketList[0].Weight < bucketList[1].Weight {
|
||||
t.Log(seconds, credit, bucketList)
|
||||
t.Errorf("Expected %v was %v", expected, seconds)
|
||||
|
||||
@@ -83,22 +83,35 @@ func (b *Balance) Clone() *Balance {
|
||||
}
|
||||
|
||||
// Returns the available number of seconds for a specified credit
|
||||
func (b *Balance) GetMinutesForCredit(cd *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) {
|
||||
duration = time.Duration(b.Value) * time.Second
|
||||
func (b *Balance) GetMinutesForCredit(origCD *CallDescriptor, initialCredit float64) (duration time.Duration, credit float64) {
|
||||
cd := origCD.Clone()
|
||||
availableDuration := time.Duration(b.Value) * time.Second
|
||||
duration = availableDuration
|
||||
credit = initialCredit
|
||||
cc, err := b.GetCost(cd)
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err))
|
||||
return 0, credit
|
||||
}
|
||||
if cc.deductConnectFee {
|
||||
connectFee := cc.GetConnectFee()
|
||||
if connectFee <= credit {
|
||||
credit -= connectFee
|
||||
// remove connect fee from the total cost
|
||||
cc.Cost -= connectFee
|
||||
} else {
|
||||
return 0, credit
|
||||
}
|
||||
}
|
||||
if cc.Cost > 0 {
|
||||
duration = 0
|
||||
for _, ts := range cc.Timespans {
|
||||
ts.createIncrementsSlice()
|
||||
for _, incr := range ts.Increments {
|
||||
if incr.Cost <= credit {
|
||||
if incr.Cost <= credit && availableDuration-incr.Duration >= 0 {
|
||||
credit -= incr.Cost
|
||||
duration += incr.Duration
|
||||
availableDuration -= incr.Duration
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@@ -112,6 +125,7 @@ func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) {
|
||||
if b.RateSubject != "" {
|
||||
cd.Subject = b.RateSubject
|
||||
cd.Account = cd.Subject
|
||||
cd.RatingInfos = nil
|
||||
return cd.GetCost()
|
||||
}
|
||||
cc := cd.CreateCallCost()
|
||||
@@ -221,15 +235,21 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan
|
||||
if moneyBal != nil && b.Value >= seconds {
|
||||
b.Value -= seconds
|
||||
b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
moneyBal.Value -= cost
|
||||
|
||||
nInc.BalanceInfo.MinuteBalanceUuid = b.Uuid
|
||||
nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid
|
||||
nInc.BalanceInfo.AccountId = ub.Id
|
||||
nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds}
|
||||
if cost != 0 {
|
||||
nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid
|
||||
moneyBal.Value -= cost
|
||||
moneyBal.Value = utils.Round(moneyBal.Value, roundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
}
|
||||
nInc.paid = true
|
||||
if count {
|
||||
ub.countUnits(&Action{BalanceType: MINUTES, Direction: newCC.Direction, Balance: &Balance{Value: seconds, DestinationId: newCC.Destination}})
|
||||
ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}})
|
||||
if cost != 0 {
|
||||
ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
increment.paid = false
|
||||
|
||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
@@ -32,7 +31,7 @@ type CallCost struct {
|
||||
}
|
||||
|
||||
// Pretty printing for call cost
|
||||
func (cc *CallCost) String() (r string) {
|
||||
/*func (cc *CallCost) String() (r string) {
|
||||
connectFee := 0.0
|
||||
if cc.deductConnectFee {
|
||||
connectFee = cc.GetConnectFee()
|
||||
@@ -43,7 +42,7 @@ func (cc *CallCost) String() (r string) {
|
||||
}
|
||||
r += " )"
|
||||
return
|
||||
}
|
||||
}*/
|
||||
|
||||
// Merges the received timespan if they are similar (same activation period, same interval, same minute info.
|
||||
func (cc *CallCost) Merge(other *CallCost) {
|
||||
|
||||
@@ -397,7 +397,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) {
|
||||
}
|
||||
err := cd.LoadRatingPlans()
|
||||
if err != nil {
|
||||
Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetAccountKey(), err))
|
||||
Logger.Err(fmt.Sprintf("error getting cost for key %s: %v", cd.GetKey(cd.Subject), err))
|
||||
return &CallCost{Cost: -1}, err
|
||||
}
|
||||
timespans := cd.splitInTimeSpans(nil)
|
||||
@@ -471,9 +471,10 @@ func (origCD *CallDescriptor) GetMaxSessionDuration() (time.Duration, error) {
|
||||
// we must move the timestart for the interval with the available duration because
|
||||
// that was already checked
|
||||
cd.TimeStart = cd.TimeStart.Add(availableDuration)
|
||||
|
||||
// substract the connect fee
|
||||
cc, err := cd.GetCost()
|
||||
if cc.deductConnectFee {
|
||||
if availableDuration == 0 && cc.deductConnectFee { // only if we did not already used minutes
|
||||
availableCredit -= cc.GetConnectFee()
|
||||
}
|
||||
if err != nil {
|
||||
@@ -659,7 +660,7 @@ func (cd *CallDescriptor) Clone() *CallDescriptor {
|
||||
CallDuration: cd.CallDuration,
|
||||
Amount: cd.Amount,
|
||||
FallbackSubject: cd.FallbackSubject,
|
||||
RatingInfos: cd.RatingInfos,
|
||||
Increments: cd.Increments,
|
||||
//RatingInfos: cd.RatingInfos,
|
||||
//Increments: cd.Increments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ func TestDebitAndMaxDebit(t *testing.T) {
|
||||
Subject: "minu_from_tm",
|
||||
Account: "minu",
|
||||
Destination: "0723",
|
||||
Amount: 5400}
|
||||
}
|
||||
cd2 := cd1.Clone()
|
||||
cc1, err1 := cd1.Debit()
|
||||
cc2, err2 := cd2.MaxDebit()
|
||||
@@ -434,6 +434,62 @@ func TestDebitAndMaxDebit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxDebitZeroDefinedRate(t *testing.T) {
|
||||
ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT")
|
||||
for _, at := range ap {
|
||||
at.Execute()
|
||||
}
|
||||
cd1 := &CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.directvoip.co.uk",
|
||||
Subject: "12345",
|
||||
Account: "12345",
|
||||
Destination: "447956",
|
||||
TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 4, 6, 1, 0, 0, time.UTC),
|
||||
LoopIndex: 0,
|
||||
CallDuration: 0}
|
||||
cc, err := cd1.MaxDebit()
|
||||
if err != nil {
|
||||
t.Error("Error maxdebiting: ", err)
|
||||
}
|
||||
if cc.GetDuration() != 49*time.Second {
|
||||
t.Error("Error obtaining max debit duration: ", cc.GetDuration())
|
||||
}
|
||||
if cc.Cost != 0.91 {
|
||||
t.Error("Error in max debit cost: ", cc.Cost)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) {
|
||||
ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT")
|
||||
for _, at := range ap {
|
||||
at.Execute()
|
||||
}
|
||||
cd1 := &CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.directvoip.co.uk",
|
||||
Subject: "12345",
|
||||
Account: "12345",
|
||||
Destination: "447956",
|
||||
TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 4, 6, 0, 40, 0, time.UTC),
|
||||
LoopIndex: 0,
|
||||
CallDuration: 0}
|
||||
cc, err := cd1.MaxDebit()
|
||||
if err != nil {
|
||||
t.Error("Error maxdebiting: ", err)
|
||||
}
|
||||
if cc.GetDuration() != 40*time.Second {
|
||||
t.Error("Error obtaining max debit duration: ", cc.GetDuration())
|
||||
}
|
||||
if cc.Cost != 0.01 {
|
||||
t.Error("Error in max debit cost: ", cc.Cost)
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************** BENCHMARKS ***************************************/
|
||||
func BenchmarkStorageGetting(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
@@ -37,6 +37,7 @@ func TestHistoryDestinations(t *testing.T) {
|
||||
scribe := historyScribe.(*history.MockScribe)
|
||||
buf := scribe.BufMap[history.DESTINATIONS_FN]
|
||||
expected := `[{"Id":"ALL","Prefixes":["49","41","43"]},
|
||||
{"Id":"DST_UK_Mobile_BIG5","Prefixes":["447956"]},
|
||||
{"Id":"GERMANY","Prefixes":["49"]},
|
||||
{"Id":"GERMANY_O2","Prefixes":["41"]},
|
||||
{"Id":"GERMANY_PREMIUM","Prefixes":["43"]},
|
||||
|
||||
@@ -39,7 +39,8 @@ type CSVReader struct {
|
||||
actionsTimings map[string][]*ActionTiming
|
||||
actionsTriggers map[string][]*ActionTrigger
|
||||
aliases map[string]string
|
||||
accountActions []*Account
|
||||
accountActions map[string]*Account
|
||||
dirtyAliases []string // used to clean aliases that might have changed
|
||||
destinations []*Destination
|
||||
timings map[string]*utils.TPTiming
|
||||
rates map[string]*utils.TPRate
|
||||
@@ -60,6 +61,7 @@ func NewFileCSVReader(dataStorage RatingStorage, accountingStorage AccountingSto
|
||||
c.actions = make(map[string][]*Action)
|
||||
c.actionsTimings = make(map[string][]*ActionTiming)
|
||||
c.actionsTriggers = make(map[string][]*ActionTrigger)
|
||||
c.accountActions = make(map[string]*Account)
|
||||
c.rates = make(map[string]*utils.TPRate)
|
||||
c.destinationRates = make(map[string]*utils.TPDestinationRate)
|
||||
c.timings = make(map[string]*utils.TPTiming)
|
||||
@@ -249,6 +251,9 @@ func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) {
|
||||
if verbose {
|
||||
log.Print("Aliases")
|
||||
}
|
||||
if err := dataStorage.RemoveAccountAliases(csvr.dirtyAliases); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, alias := range csvr.aliases {
|
||||
err = dataStorage.SetAlias(key, alias)
|
||||
if err != nil {
|
||||
@@ -356,7 +361,7 @@ func (csvr *CSVReader) LoadDestinationRates() (err error) {
|
||||
tag := record[0]
|
||||
r, exists := csvr.rates[record[2]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not get rates for tag %v", record[2]))
|
||||
return fmt.Errorf("Could not get rates for tag %v", record[2])
|
||||
}
|
||||
destinationExists := false
|
||||
for _, d := range csvr.destinations {
|
||||
@@ -408,11 +413,11 @@ func (csvr *CSVReader) LoadRatingPlans() (err error) {
|
||||
tag := record[0]
|
||||
t, exists := csvr.timings[record[2]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not get timing for tag %v", record[2]))
|
||||
return fmt.Errorf("Could not get timing for tag %v", record[2])
|
||||
}
|
||||
drs, exists := csvr.destinationRates[record[1]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1]))
|
||||
return fmt.Errorf("Could not find destination rate for tag %v", record[1])
|
||||
}
|
||||
rpl := NewRatingPlan(t, record[3])
|
||||
plan, exists := csvr.ratingPlans[tag]
|
||||
@@ -441,8 +446,9 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) {
|
||||
tenant, tor, direction, subject, fallbacksubject := record[0], record[1], record[2], record[3], record[6]
|
||||
at, err := utils.ParseDate(record[4])
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", record[4]))
|
||||
return fmt.Errorf("Cannot parse activation time from %v", record[4])
|
||||
}
|
||||
csvr.dirtyAliases = append(csvr.dirtyAliases, subject)
|
||||
// extract aliases from subject
|
||||
aliases := strings.Split(subject, ";")
|
||||
if len(aliases) > 1 {
|
||||
@@ -464,7 +470,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) {
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", record[5]))
|
||||
return fmt.Errorf("Could not load rating plans for tag: %v", record[5])
|
||||
}
|
||||
rpa := &RatingPlanActivation{
|
||||
ActivationTime: at,
|
||||
@@ -529,7 +535,7 @@ func (csvr *CSVReader) LoadActions() (err error) {
|
||||
} else {
|
||||
units, err = strconv.ParseFloat(record[4], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse action units: %v", err))
|
||||
return fmt.Errorf("Could not parse action units: %v", err)
|
||||
}
|
||||
}
|
||||
var balanceWeight float64
|
||||
@@ -538,12 +544,12 @@ func (csvr *CSVReader) LoadActions() (err error) {
|
||||
} else {
|
||||
balanceWeight, err = strconv.ParseFloat(record[8], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse action balance weight: %v", err))
|
||||
return fmt.Errorf("Could not parse action balance weight: %v", err)
|
||||
}
|
||||
}
|
||||
weight, err := strconv.ParseFloat(record[11], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse action weight: %v", err))
|
||||
return fmt.Errorf("Could not parse action weight: %v", err)
|
||||
}
|
||||
a := &Action{
|
||||
Id: utils.GenUUID(),
|
||||
@@ -563,7 +569,7 @@ func (csvr *CSVReader) LoadActions() (err error) {
|
||||
},
|
||||
}
|
||||
if _, err := utils.ParseDate(a.ExpirationString); err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err))
|
||||
return fmt.Errorf("Could not parse expiration time: %v", err)
|
||||
}
|
||||
csvr.actions[tag] = append(csvr.actions[tag], a)
|
||||
}
|
||||
@@ -584,15 +590,15 @@ func (csvr *CSVReader) LoadActionTimings() (err error) {
|
||||
tag := record[0]
|
||||
_, exists := csvr.actions[record[1]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("ActionPlan: Could not load the action for tag: %v", record[1]))
|
||||
return fmt.Errorf("ActionPlan: Could not load the action for tag: %v", record[1])
|
||||
}
|
||||
t, exists := csvr.timings[record[2]]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("ActionPlan: Could not load the timing for tag: %v", record[2]))
|
||||
return fmt.Errorf("ActionPlan: Could not load the timing for tag: %v", record[2])
|
||||
}
|
||||
weight, err := strconv.ParseFloat(record[3], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("ActionTiming: Could not parse action timing weight: %v", err))
|
||||
return fmt.Errorf("ActionTiming: Could not parse action timing weight: %v", err)
|
||||
}
|
||||
at := &ActionTiming{
|
||||
Id: utils.GenUUID(),
|
||||
@@ -628,11 +634,11 @@ func (csvr *CSVReader) LoadActionTriggers() (err error) {
|
||||
tag := record[0]
|
||||
value, err := strconv.ParseFloat(record[4], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse action trigger value: %v", err))
|
||||
return fmt.Errorf("Could not parse action trigger value: %v", err)
|
||||
}
|
||||
weight, err := strconv.ParseFloat(record[7], 64)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Could not parse action trigger weight: %v", err))
|
||||
return fmt.Errorf("Could not parse action trigger weight: %v", err)
|
||||
}
|
||||
at := &ActionTrigger{
|
||||
Id: utils.GenUUID(),
|
||||
@@ -661,6 +667,7 @@ func (csvr *CSVReader) LoadAccountActions() (err error) {
|
||||
}
|
||||
for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() {
|
||||
tenant, account, direction := record[0], record[1], record[2]
|
||||
csvr.dirtyAliases = append(csvr.dirtyAliases, account)
|
||||
// extract aliases from subject
|
||||
aliases := strings.Split(account, ";")
|
||||
if len(aliases) > 1 {
|
||||
@@ -670,19 +677,22 @@ func (csvr *CSVReader) LoadAccountActions() (err error) {
|
||||
}
|
||||
}
|
||||
tag := fmt.Sprintf("%s:%s:%s", direction, tenant, account)
|
||||
if _, alreadyDefined := csvr.accountActions[tag]; alreadyDefined {
|
||||
return fmt.Errorf("Duplicate account action found: %s", tag)
|
||||
}
|
||||
aTriggers, exists := csvr.actionsTriggers[record[4]]
|
||||
if record[4] != "" && !exists {
|
||||
// only return error if there was something ther for the tag
|
||||
return errors.New(fmt.Sprintf("Could not get action triggers for tag %v", record[4]))
|
||||
return fmt.Errorf("Could not get action triggers for tag %s", record[4])
|
||||
}
|
||||
ub := &Account{
|
||||
Id: tag,
|
||||
ActionTriggers: aTriggers,
|
||||
}
|
||||
csvr.accountActions = append(csvr.accountActions, ub)
|
||||
csvr.accountActions[tag] = ub
|
||||
aTimings, exists := csvr.actionsTimings[record[3]]
|
||||
if !exists {
|
||||
log.Printf("Could not get action timing for tag %v", record[3])
|
||||
log.Printf("Could not get action timing for tag %s", record[3])
|
||||
// must not continue here
|
||||
}
|
||||
for _, at := range aTimings {
|
||||
|
||||
@@ -43,12 +43,15 @@ RET,0724
|
||||
PSTN_71,+4971
|
||||
PSTN_72,+4972
|
||||
PSTN_70,+4970
|
||||
DST_UK_Mobile_BIG5,447956
|
||||
`
|
||||
timings = `
|
||||
WORKDAYS_00,*any,*any,*any,1;2;3;4;5,00:00:00
|
||||
WORKDAYS_18,*any,*any,*any,1;2;3;4;5,18:00:00
|
||||
WEEKENDS,*any,*any,*any,6;7,00:00:00
|
||||
ONE_TIME_RUN,2012,,,,*asap
|
||||
ALWAYS,*any,*any,*any,*any,00:00:00
|
||||
ASAP,*any,*any,*any,*any,*asap
|
||||
`
|
||||
rates = `
|
||||
R1,0,0.2,60s,1s,0,*middle,2
|
||||
@@ -61,6 +64,8 @@ LANDLINE_OFFPEAK,0,1,1s,1s,60s,*up,4
|
||||
GBP_71,0.000000,5.55555,1s,1s,0s,*up,4
|
||||
GBP_72,0.000000,7.77777,1s,1s,0s,*up,4
|
||||
GBP_70,0.000000,1,1s,1s,0s,*up,4
|
||||
RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s,*up,8
|
||||
RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s,*up,8
|
||||
`
|
||||
destinationRates = `
|
||||
RT_STANDARD,GERMANY,R1
|
||||
@@ -75,8 +80,10 @@ T1,NAT,LANDLINE_OFFPEAK
|
||||
T2,GERMANY,GBP_72
|
||||
T2,GERMANY_O2,GBP_70
|
||||
T2,GERMANY_PREMIUM,GBP_71
|
||||
DR_UK_Mobile_BIG5_PKG,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5_PKG
|
||||
DR_UK_Mobile_BIG5,DST_UK_Mobile_BIG5,RT_UK_Mobile_BIG5
|
||||
`
|
||||
destinationRateTimings = `
|
||||
ratingPlans = `
|
||||
STANDARD,RT_STANDARD,WORKDAYS_00,10
|
||||
STANDARD,RT_STD_WEEKEND,WORKDAYS_18,10
|
||||
STANDARD,RT_STD_WEEKEND,WEEKENDS,10
|
||||
@@ -91,6 +98,8 @@ TDRT,T1,WORKDAYS_00,10
|
||||
TDRT,T2,WORKDAYS_00,10
|
||||
G,RT_STANDARD,WORKDAYS_00,10
|
||||
R,P1,WORKDAYS_00,10
|
||||
RP_UK_Mobile_BIG5_PKG,DR_UK_Mobile_BIG5_PKG,ALWAYS,10
|
||||
RP_UK,DR_UK_Mobile_BIG5,ALWAYS,10
|
||||
`
|
||||
ratingProfiles = `
|
||||
CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb
|
||||
@@ -109,6 +118,8 @@ vdf,0,*out,fallback1,2013-11-18T13:45:00Z,G,fallback2
|
||||
vdf,0,*out,fallback1,2013-11-18T13:46:00Z,G,fallback2
|
||||
vdf,0,*out,fallback1,2013-11-18T13:47:00Z,G,fallback2
|
||||
vdf,0,*out,fallback2,2013-11-18T13:45:00Z,R,rif
|
||||
cgrates.directvoip.co.uk,call,*out,*any,2013-01-06T00:00:00Z,RP_UK,
|
||||
cgrates.directvoip.co.uk,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK_Mobile_BIG5_PKG,
|
||||
`
|
||||
sharedGroups = `
|
||||
SG1,*any,*lowest_first,,
|
||||
@@ -119,24 +130,32 @@ SG2,*any,*lowest_first,EVENING,
|
||||
MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,,10
|
||||
MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,,10
|
||||
SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10
|
||||
TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10
|
||||
TOPUP10_AC1,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10
|
||||
`
|
||||
actionTimings = `
|
||||
MORE_MINUTES,MINI,ONE_TIME_RUN,10
|
||||
MORE_MINUTES,SHARED,ONE_TIME_RUN,10
|
||||
TOPUP10_AT,TOPUP10_AC,ASAP,10
|
||||
TOPUP10_AT,TOPUP10_AC1,ASAP,10
|
||||
`
|
||||
actionTriggers = `
|
||||
STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10
|
||||
STANDARD_TRIGGER,*minutes,*out,*max_balance,200,GERMANY,SOME_2,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*min_balance,2,,LOG_WARNING,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_balance,20,,LOG_WARNING,10
|
||||
STANDARD_TRIGGERS,*monetary,*out,*max_counter,5,FS_USERS,LOG_WARNING,10
|
||||
`
|
||||
accountActions = `
|
||||
vdf,minitsboy;a1;a2,*out,MORE_MINUTES,STANDARD_TRIGGER
|
||||
cgrates.directvoip.co.uk,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS
|
||||
`
|
||||
)
|
||||
|
||||
var csvr *CSVReader
|
||||
|
||||
func init() {
|
||||
csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, destinationRateTimings, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions)
|
||||
csvr = NewStringCSVReader(dataStorage, accountingStorage, ',', destinations, timings, rates, destinationRates, ratingPlans, ratingProfiles, sharedGroups, actions, actionTimings, actionTriggers, accountActions)
|
||||
csvr.LoadDestinations()
|
||||
csvr.LoadTimings()
|
||||
csvr.LoadRates()
|
||||
@@ -154,7 +173,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestLoadDestinations(t *testing.T) {
|
||||
if len(csvr.destinations) != 9 {
|
||||
if len(csvr.destinations) != 10 {
|
||||
t.Error("Failed to load destinations: ", len(csvr.destinations))
|
||||
}
|
||||
for _, d := range csvr.destinations {
|
||||
@@ -195,14 +214,12 @@ func TestLoadDestinations(t *testing.T) {
|
||||
if !reflect.DeepEqual(d.Prefixes, []string{`+4970`}) {
|
||||
t.Error("Faild to load destinations", d)
|
||||
}
|
||||
default:
|
||||
t.Error("Unknown destination tag!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTimimgs(t *testing.T) {
|
||||
if len(csvr.timings) != 4 {
|
||||
if len(csvr.timings) != 6 {
|
||||
t.Error("Failed to load timings: ", csvr.timings)
|
||||
}
|
||||
timing := csvr.timings["WORKDAYS_00"]
|
||||
@@ -252,7 +269,7 @@ func TestLoadTimimgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRates(t *testing.T) {
|
||||
if len(csvr.rates) != 9 {
|
||||
if len(csvr.rates) != 11 {
|
||||
t.Error("Failed to load rates: ", csvr.rates)
|
||||
}
|
||||
rate := csvr.rates["R1"].RateSlots[0]
|
||||
@@ -322,7 +339,7 @@ func TestLoadRates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadDestinationRates(t *testing.T) {
|
||||
if len(csvr.destinationRates) != 7 {
|
||||
if len(csvr.destinationRates) != 9 {
|
||||
t.Error("Failed to load destinationrates: ", csvr.destinationRates)
|
||||
}
|
||||
drs := csvr.destinationRates["RT_STANDARD"]
|
||||
@@ -434,7 +451,7 @@ func TestLoadDestinationRates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadDestinationRateTimings(t *testing.T) {
|
||||
if len(csvr.ratingPlans) != 7 {
|
||||
if len(csvr.ratingPlans) != 9 {
|
||||
t.Error("Failed to load rate timings: ", csvr.ratingPlans)
|
||||
}
|
||||
rplan := csvr.ratingPlans["STANDARD"]
|
||||
@@ -554,7 +571,7 @@ func TestLoadDestinationRateTimings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRatingProfiles(t *testing.T) {
|
||||
if len(csvr.ratingProfiles) != 12 {
|
||||
if len(csvr.ratingProfiles) != 14 {
|
||||
t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles)
|
||||
}
|
||||
rp := csvr.ratingProfiles["*out:test:0:trp"]
|
||||
@@ -572,7 +589,7 @@ func TestLoadRatingProfiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadActions(t *testing.T) {
|
||||
if len(csvr.actions) != 2 {
|
||||
if len(csvr.actions) != 4 {
|
||||
t.Error("Failed to load actions: ", csvr.actions)
|
||||
}
|
||||
as1 := csvr.actions["MINI"]
|
||||
@@ -681,7 +698,7 @@ func TestLoadSharedGroups(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadActionTimings(t *testing.T) {
|
||||
if len(csvr.actionsTimings) != 1 {
|
||||
if len(csvr.actionsTimings) != 2 {
|
||||
t.Error("Failed to load action timings: ", csvr.actionsTimings)
|
||||
}
|
||||
atm := csvr.actionsTimings["MORE_MINUTES"][0]
|
||||
@@ -707,7 +724,7 @@ func TestLoadActionTimings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadActionTriggers(t *testing.T) {
|
||||
if len(csvr.actionsTriggers) != 1 {
|
||||
if len(csvr.actionsTriggers) != 2 {
|
||||
t.Error("Failed to load action triggers: ", csvr.actionsTriggers)
|
||||
}
|
||||
atr := csvr.actionsTriggers["STANDARD_TRIGGER"][0]
|
||||
@@ -743,10 +760,10 @@ func TestLoadActionTriggers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadAccountActions(t *testing.T) {
|
||||
if len(csvr.accountActions) != 1 {
|
||||
if len(csvr.accountActions) != 2 {
|
||||
t.Error("Failed to load account actions: ", csvr.accountActions)
|
||||
}
|
||||
aa := csvr.accountActions[0]
|
||||
aa := csvr.accountActions["*out:vdf:minitsboy"]
|
||||
expected := &Account{
|
||||
Id: "*out:vdf:minitsboy",
|
||||
ActionTriggers: csvr.actionsTriggers["STANDARD_TRIGGER"],
|
||||
|
||||
@@ -35,7 +35,8 @@ type DbReader struct {
|
||||
actions map[string][]*Action
|
||||
actionsTimings map[string][]*ActionTiming
|
||||
actionsTriggers map[string][]*ActionTrigger
|
||||
accountActions []*Account
|
||||
accountActions map[string]*Account
|
||||
dirtyAliases []string // used to clean aliases that might have changed
|
||||
destinations []*Destination
|
||||
aliases map[string]string
|
||||
timings map[string]*utils.TPTiming
|
||||
@@ -59,6 +60,7 @@ func NewDbReader(storDB LoadStorage, ratingDb RatingStorage, accountDb Accountin
|
||||
c.ratingProfiles = make(map[string]*RatingProfile)
|
||||
c.sharedGroups = make(map[string]*SharedGroup)
|
||||
c.aliases = make(map[string]string)
|
||||
c.accountActions = make(map[string]*Account)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -194,6 +196,9 @@ func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) {
|
||||
if verbose {
|
||||
log.Print("Aliases")
|
||||
}
|
||||
if err := storage.RemoveAccountAliases(dbr.dirtyAliases); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, alias := range dbr.aliases {
|
||||
err = storage.SetAlias(key, alias)
|
||||
if err != nil {
|
||||
@@ -230,7 +235,7 @@ func (dbr *DbReader) LoadDestinationRates() (err error) {
|
||||
for _, dr := range drs.DestinationRates {
|
||||
rate, exists := dbr.rates[dr.RateId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not find rate for tag %v", dr.RateId))
|
||||
return fmt.Errorf("Could not find rate for tag %v", dr.RateId)
|
||||
}
|
||||
dr.Rate = rate
|
||||
destinationExists := false
|
||||
@@ -244,7 +249,7 @@ func (dbr *DbReader) LoadDestinationRates() (err error) {
|
||||
if dbExists, err := dbr.dataDb.HasData(DESTINATION_PREFIX, dr.DestinationId); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
return errors.New(fmt.Sprintf("Could not get destination for tag %v", dr.DestinationId))
|
||||
return fmt.Errorf("Could not get destination for tag %v", dr.DestinationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,12 +266,12 @@ func (dbr *DbReader) LoadRatingPlans() error {
|
||||
for _, rplBnd := range rplBnds {
|
||||
t, exists := dbr.timings[rplBnd.TimingId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not get timing for tag %v", rplBnd.TimingId))
|
||||
return fmt.Errorf("Could not get timing for tag %v", rplBnd.TimingId)
|
||||
}
|
||||
rplBnd.SetTiming(t)
|
||||
drs, exists := dbr.destinationRates[rplBnd.DestinationRatesId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", rplBnd.DestinationRatesId))
|
||||
return fmt.Errorf("Could not find destination rate for tag %v", rplBnd.DestinationRatesId)
|
||||
}
|
||||
plan, exists := dbr.ratingPlans[tag]
|
||||
if !exists {
|
||||
@@ -287,6 +292,7 @@ func (dbr *DbReader) LoadRatingProfiles() error {
|
||||
return err
|
||||
}
|
||||
for _, tpRpf := range mpTpRpfs {
|
||||
dbr.dirtyAliases = append(dbr.dirtyAliases, tpRpf.Subject)
|
||||
// extract aliases from subject
|
||||
aliases := strings.Split(tpRpf.Subject, ";")
|
||||
if len(aliases) > 1 {
|
||||
@@ -299,14 +305,14 @@ func (dbr *DbReader) LoadRatingProfiles() error {
|
||||
for _, tpRa := range tpRpf.RatingPlanActivations {
|
||||
at, err := utils.ParseDate(tpRa.ActivationTime)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime))
|
||||
return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime)
|
||||
}
|
||||
_, exists := dbr.ratingPlans[tpRa.RatingPlanId]
|
||||
if !exists {
|
||||
if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", tpRa.RatingPlanId))
|
||||
return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId)
|
||||
}
|
||||
}
|
||||
rpf.RatingPlanActivations = append(rpf.RatingPlanActivations,
|
||||
@@ -382,7 +388,7 @@ func (dbr *DbReader) LoadRatingProfileFiltered(qriedRpf *utils.TPRatingProfile)
|
||||
var resultRatingProfile *RatingProfile
|
||||
mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(qriedRpf) //map[string]*utils.TPRatingProfile
|
||||
if err != nil {
|
||||
return fmt.Errorf("No RateProfile for filter %v, error: %s", qriedRpf, err.Error())
|
||||
return fmt.Errorf("No RateProfile for filter %v, error: %v", qriedRpf, err)
|
||||
}
|
||||
for _, tpRpf := range mpTpRpfs {
|
||||
// Logger.Debug(fmt.Sprintf("Rating profile: %v", tpRpf))
|
||||
@@ -390,14 +396,14 @@ func (dbr *DbReader) LoadRatingProfileFiltered(qriedRpf *utils.TPRatingProfile)
|
||||
for _, tpRa := range tpRpf.RatingPlanActivations {
|
||||
at, err := utils.ParseDate(tpRa.ActivationTime)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime))
|
||||
return fmt.Errorf("Cannot parse activation time from %v", tpRa.ActivationTime)
|
||||
}
|
||||
_, exists := dbr.ratingPlans[tpRa.RatingPlanId]
|
||||
if !exists {
|
||||
if dbExists, err := dbr.dataDb.HasData(RATING_PLAN_PREFIX, tpRa.RatingPlanId); err != nil {
|
||||
return err
|
||||
} else if !dbExists {
|
||||
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", tpRa.RatingPlanId))
|
||||
return fmt.Errorf("Could not load rating plans for tag: %v", tpRa.RatingPlanId)
|
||||
}
|
||||
}
|
||||
resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations,
|
||||
@@ -457,11 +463,11 @@ func (dbr *DbReader) LoadActionTimings() (err error) {
|
||||
|
||||
_, exists := dbr.actions[at.ActionsId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("ActionTiming: Could not load the action for tag: %v", at.ActionsId))
|
||||
return fmt.Errorf("ActionTiming: Could not load the action for tag: %v", at.ActionsId)
|
||||
}
|
||||
t, exists := dbr.timings[at.TimingId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("ActionTiming: Could not load the timing for tag: %v", at.TimingId))
|
||||
return fmt.Errorf("ActionTiming: Could not load the timing for tag: %v", at.TimingId)
|
||||
}
|
||||
actTmg := &ActionTiming{
|
||||
Id: utils.GenUUID(),
|
||||
@@ -513,6 +519,10 @@ func (dbr *DbReader) LoadAccountActions() (err error) {
|
||||
return err
|
||||
}
|
||||
for _, aa := range acs {
|
||||
if _, alreadyDefined := dbr.accountActions[aa.KeyId()]; alreadyDefined {
|
||||
return fmt.Errorf("Duplicate account action found: %s", aa.KeyId())
|
||||
}
|
||||
dbr.dirtyAliases = append(dbr.dirtyAliases, aa.Account)
|
||||
// extract aliases from subject
|
||||
aliases := strings.Split(aa.Account, ";")
|
||||
if len(aliases) > 1 {
|
||||
@@ -523,13 +533,13 @@ func (dbr *DbReader) LoadAccountActions() (err error) {
|
||||
}
|
||||
aTriggers, exists := dbr.actionsTriggers[aa.ActionTriggersId]
|
||||
if !exists {
|
||||
return errors.New(fmt.Sprintf("Could not get action triggers for tag %v", aa.ActionTriggersId))
|
||||
return fmt.Errorf("Could not get action triggers for tag %v", aa.ActionTriggersId)
|
||||
}
|
||||
ub := &Account{
|
||||
Id: aa.KeyId(),
|
||||
ActionTriggers: aTriggers,
|
||||
}
|
||||
dbr.accountActions = append(dbr.accountActions, ub)
|
||||
dbr.accountActions[aa.KeyId()] = ub
|
||||
aTimings, exists := dbr.actionsTimings[aa.ActionPlanId]
|
||||
if !exists {
|
||||
log.Printf("Could not get action timing for tag %v", aa.ActionPlanId)
|
||||
|
||||
@@ -78,6 +78,7 @@ type RatingStorage interface {
|
||||
SetRatingProfile(*RatingProfile) error
|
||||
GetAlias(string, bool) (string, error)
|
||||
SetAlias(string, string) error
|
||||
RemoveAccountAliases([]string) error
|
||||
GetDestination(string) (*Destination, error)
|
||||
SetDestination(*Destination) error
|
||||
}
|
||||
|
||||
@@ -221,6 +221,15 @@ func (ms *MapStorage) SetAlias(key, alias string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MapStorage) RemoveAccountAliases(accounts []string) (err error) {
|
||||
for key, value := range ms.dict {
|
||||
if strings.HasPrefix(key, ALIAS_PREFIX) && utils.IsSliceMember(accounts, string(value)) {
|
||||
delete(ms.dict, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MapStorage) GetDestination(key string) (dest *Destination, err error) {
|
||||
key = DESTINATION_PREFIX + key
|
||||
if values, ok := ms.dict[key]; ok {
|
||||
|
||||
@@ -287,6 +287,26 @@ func (rs *RedisStorage) SetAlias(key, alias string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) RemoveAccountAliases(accounts []string) (err error) {
|
||||
if alsKeys, err := rs.db.Keys(ALIAS_PREFIX + "*"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, key := range alsKeys {
|
||||
alias, err := rs.GetAlias(key, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.IsSliceMember(accounts, alias) {
|
||||
if _, err = rs.db.Del(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) {
|
||||
key = DESTINATION_PREFIX + key
|
||||
var values []byte
|
||||
|
||||
@@ -77,13 +77,21 @@ func GenUUID() string {
|
||||
// Round(NaN) = NaN
|
||||
func Round(x float64, prec int, method string) float64 {
|
||||
var rounder float64
|
||||
maxPrec := 7 // define a max precison to cut float errors
|
||||
if maxPrec < prec {
|
||||
maxPrec = prec
|
||||
}
|
||||
pow := math.Pow(10, float64(prec))
|
||||
intermed := x * pow
|
||||
_, frac := math.Modf(intermed)
|
||||
|
||||
switch method {
|
||||
case ROUNDING_UP:
|
||||
rounder = math.Ceil(intermed)
|
||||
if frac >= math.Pow10(-maxPrec) { // Max precision we go, rest is float chaos
|
||||
rounder = math.Ceil(intermed)
|
||||
} else {
|
||||
rounder = math.Floor(intermed)
|
||||
}
|
||||
case ROUNDING_DOWN:
|
||||
rounder = math.Floor(intermed)
|
||||
case ROUNDING_MIDDLE:
|
||||
|
||||
@@ -105,6 +105,14 @@ func TestRoundByMethodUp2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundByMethodUp3(t *testing.T) {
|
||||
result := Round(0.0701, 2, ROUNDING_UP)
|
||||
expected := 0.08
|
||||
if result != expected {
|
||||
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundByMethodDown1(t *testing.T) {
|
||||
result := Round(12.49, 1, ROUNDING_DOWN)
|
||||
expected := 12.4
|
||||
@@ -254,7 +262,7 @@ func TestMissingStructFieldsIncorrect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRound(t *testing.T) {
|
||||
func TestRoundTo(t *testing.T) {
|
||||
minute := time.Minute
|
||||
result := RoundTo(minute, 0*time.Second)
|
||||
expected := 0 * time.Second
|
||||
@@ -293,6 +301,19 @@ func TestRound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundAlredyHavingPrecision(t *testing.T) {
|
||||
x := 0.07
|
||||
if y := Round(x, 2, ROUNDING_UP); y != x {
|
||||
t.Error("Error rounding when already has desired precision: ", y)
|
||||
}
|
||||
if y := Round(x, 2, ROUNDING_MIDDLE); y != x {
|
||||
t.Error("Error rounding when already has desired precision: ", y)
|
||||
}
|
||||
if y := Round(x, 2, ROUNDING_DOWN); y != x {
|
||||
t.Error("Error rounding when already has desired precision: ", y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitPrefix(t *testing.T) {
|
||||
a := SplitPrefix("0123456789", 1)
|
||||
if len(a) != 10 {
|
||||
|
||||
Reference in New Issue
Block a user