improved expiration time

This commit is contained in:
Radu Ioan Fericean
2013-07-19 16:06:21 +03:00
parent 17597375dc
commit 6c8d2c7187
11 changed files with 328 additions and 110 deletions

View File

@@ -147,14 +147,10 @@ func (self *Apier) SetRatingProfile(attrs AttrSetRatingProfile, reply *string) e
}
dbReader := rater.NewDbReader(self.StorDb, self.DataDb, attrs.TPid)
newRP, err := dbReader.LoadRatingProfileByTag(attrs.RateProfileId)
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
err = self.DataDb.SetRatingProfile(newRP)
if err != nil {
if err := dbReader.LoadRatingProfileByTag(attrs.RateProfileId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
@@ -240,3 +236,22 @@ func (self *Apier) AddAccount(attr *AttrAccount, reply *float64) error {
}
return nil
}
type AttrSetAccountAction struct {
TPid string
RateProfileId string
}
// Process dependencies and load a specific rating profile from storDb into dataDb.
func (self *Apier) SetAccountAction(attrs AttrSetAccountAction, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateProfileId"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
dbReader := rater.NewDbReader(self.StorDb, self.DataDb, attrs.TPid)
if err := dbReader.LoadAccountActionByTag(attrs.RateProfileId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}

View File

@@ -21,19 +21,21 @@ package rater
import (
"fmt"
"sort"
"time"
)
/*
Structure to be filled for each tariff plan with the bonus value for received calls minutes.
*/
type Action struct {
Id string
ActionType string
BalanceId string
Direction string
Units float64
Weight float64
MinuteBucket *MinuteBucket
Id string
ActionType string
BalanceId string
Direction string
ExpirationDate time.Time
Units float64
Weight float64
MinuteBucket *MinuteBucket
}
const (
@@ -164,17 +166,10 @@ func genericDebit(ub *UserBalance, a *Action) (err error) {
if ub.BalanceMap == nil {
ub.BalanceMap = make(map[string]BalanceChain)
}
switch a.BalanceId {
case CREDIT:
ub.debitBalance(CREDIT, a.Units, false)
case SMS:
ub.debitBalance(SMS, a.Units, false)
case MINUTES:
if a.BalanceId == MINUTES {
ub.debitMinuteBucket(a.MinuteBucket)
case TRAFFIC:
ub.debitBalance(TRAFFIC, a.Units, false)
case TRAFFIC_TIME:
ub.debitBalance(TRAFFIC_TIME, a.Units, false)
} else {
ub.debitBalanceAction(a)
}
return
}

View File

@@ -508,7 +508,7 @@ func TestActionTopupResetCredit(t *testing.T) {
len(ub.UnitCounters) != 1 ||
len(ub.MinuteBuckets) != 2 ||
ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true {
t.Errorf("Topup reset action failed: %#v", ub)
t.Errorf("Topup reset action failed: %#v", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue())
}
}
@@ -542,14 +542,14 @@ func TestActionTopupCredit(t *testing.T) {
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Units: 10}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}
topupAction(ub, a)
if ub.Type != UB_TYPE_PREPAID ||
ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 110 ||
len(ub.UnitCounters) != 1 ||
len(ub.MinuteBuckets) != 2 ||
ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true {
t.Error("Topup action failed!", ub)
t.Error("Topup action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue())
}
}
@@ -583,14 +583,14 @@ func TestActionDebitCredit(t *testing.T) {
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Units: 10}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}
debitAction(ub, a)
if ub.Type != UB_TYPE_PREPAID ||
ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 90 ||
len(ub.UnitCounters) != 1 ||
len(ub.MinuteBuckets) != 2 ||
ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true {
t.Error("Debit action failed!", ub)
t.Error("Debit action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue())
}
}

View File

@@ -379,27 +379,33 @@ func (csvr *CSVReader) LoadActions() (err error) {
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action units: %v", err))
}
unix, err := strconv.ParseInt(record[5], 10, 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse expiration date: %v", err))
}
expDate := time.Unix(unix, 0)
var a *Action
if record[2] != MINUTES {
a = &Action{
ActionType: record[1],
BalanceId: record[2],
Direction: record[3],
Units: units,
ActionType: record[1],
BalanceId: record[2],
Direction: record[3],
Units: units,
ExpirationDate: expDate,
}
} else {
price, percent := 0.0, 0.0
value, err := strconv.ParseFloat(record[7], 64)
value, err := strconv.ParseFloat(record[8], 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action price: %v", err))
}
if record[6] == PERCENT {
if record[7] == PERCENT {
percent = value
}
if record[6] == ABSOLUTE {
if record[7] == ABSOLUTE {
price = value
}
minutesWeight, err := strconv.ParseFloat(record[8], 64)
minutesWeight, err := strconv.ParseFloat(record[9], 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action minutes weight: %v", err))
}
@@ -408,17 +414,19 @@ func (csvr *CSVReader) LoadActions() (err error) {
return errors.New(fmt.Sprintf("Could not parse action weight: %v", err))
}
a = &Action{
Id: utils.GenUUID(),
ActionType: record[1],
BalanceId: record[2],
Direction: record[3],
Weight: weight,
Id: utils.GenUUID(),
ActionType: record[1],
BalanceId: record[2],
Direction: record[3],
Weight: weight,
ExpirationDate: expDate,
MinuteBucket: &MinuteBucket{
Seconds: units,
Weight: minutesWeight,
Price: price,
Percent: percent,
DestinationId: record[5],
Seconds: units,
Weight: minutesWeight,
Price: price,
Percent: percent,
DestinationId: record[6],
ExpirationDate: expDate,
},
}
}

View File

@@ -84,7 +84,7 @@ vdf,0,OUT,inf,inf,STANDARD,2012-02-28T00:00:00Z
vdf,0,OUT,fall,one|rif,PREMIUM,2012-02-28T00:00:00Z
`
actions = `
MINI,TOPUP,MINUTES,OUT,100,NAT,ABSOLUTE,0,10,10
MINI,TOPUP,MINUTES,OUT,100,1374239002,NAT,ABSOLUTE,0,10,10
`
actionTimings = `
MORE_MINUTES,MINI,ONE_TIME_RUN,10

View File

@@ -28,8 +28,8 @@ import (
type DbReader struct {
tpid string
storDB DataStorage
storage DataStorage
storDb DataStorage
dataDb DataStorage
actions map[string][]*Action
actionsTimings map[string][]*ActionTiming
actionsTriggers map[string][]*ActionTrigger
@@ -44,8 +44,8 @@ type DbReader struct {
func NewDbReader(storDB DataStorage, storage DataStorage, tpid string) *DbReader {
c := new(DbReader)
c.storDB = storDB
c.storage = storage
c.storDb = storDB
c.dataDb = storage
c.tpid = tpid
c.activationPeriods = make(map[string]*ActivationPeriod)
c.actionsTimings = make(map[string][]*ActionTiming)
@@ -53,7 +53,7 @@ func NewDbReader(storDB DataStorage, storage DataStorage, tpid string) *DbReader
}
func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) {
storage := dbr.storage
storage := dbr.dataDb
if flush {
storage.Flush()
}
@@ -121,22 +121,22 @@ func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) {
}
func (dbr *DbReader) LoadDestinations() (err error) {
dbr.destinations, err = dbr.storDB.GetTpDestinations(dbr.tpid, "")
dbr.destinations, err = dbr.storDb.GetTpDestinations(dbr.tpid, "")
return
}
func (dbr *DbReader) LoadTimings() (err error) {
dbr.timings, err = dbr.storDB.GetTpTimings(dbr.tpid, "")
dbr.timings, err = dbr.storDb.GetTpTimings(dbr.tpid, "")
return err
}
func (dbr *DbReader) LoadRates() (err error) {
dbr.rates, err = dbr.storDB.GetTpRates(dbr.tpid, "")
dbr.rates, err = dbr.storDb.GetTpRates(dbr.tpid, "")
return err
}
func (dbr *DbReader) LoadDestinationRates() (err error) {
dbr.destinationRates, err = dbr.storDB.GetTpDestinationRates(dbr.tpid, "")
dbr.destinationRates, err = dbr.storDb.GetTpDestinationRates(dbr.tpid, "")
if err != nil {
return err
}
@@ -153,7 +153,7 @@ func (dbr *DbReader) LoadDestinationRates() (err error) {
}
func (dbr *DbReader) LoadDestinationRateTimings() error {
rts, err := dbr.storDB.GetTpDestinationRateTimings(dbr.tpid, "")
rts, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, "")
if err != nil {
return err
}
@@ -179,7 +179,7 @@ func (dbr *DbReader) LoadDestinationRateTimings() error {
}
func (dbr *DbReader) LoadRatingProfiles() error {
rpfs, err := dbr.storDB.GetTpRatingProfiles(dbr.tpid, "")
rpfs, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, "")
if err != nil {
return err
}
@@ -200,75 +200,74 @@ func (dbr *DbReader) LoadRatingProfiles() error {
return nil
}
func (dbr *DbReader) LoadRatingProfileByTag(tag string) (*RatingProfile, error) {
func (dbr *DbReader) LoadRatingProfileByTag(tag string) error {
activationPeriods := make(map[string]*ActivationPeriod)
resultRatingProfile := &RatingProfile{Id: tag}
rpm, err := dbr.storDB.GetTpRatingProfiles(dbr.tpid, tag)
rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag)
if err != nil {
return nil, err
return err
} else if len(rpm) == 0 {
return nil, fmt.Errorf("No RateProfile with id: %s", tag)
return fmt.Errorf("No RateProfile with id: %s", tag)
}
for _, ratingProfile := range rpm {
resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key
at := time.Unix(ratingProfile.activationTime, 0)
drtm, err := dbr.storDB.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.destRatesTimingTag)
drtm, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.destRatesTimingTag)
if err != nil {
return nil, err
return err
} else if len(drtm) == 0 {
return nil, fmt.Errorf("No DestRateTimings profile with id: %s", ratingProfile.destRatesTimingTag)
return fmt.Errorf("No DestRateTimings profile with id: %s", ratingProfile.destRatesTimingTag)
}
for _, destrateTiming := range drtm {
tm, err := dbr.storDB.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag)
tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag)
if err != nil {
return nil, err
return err
} else if len(tm) == 0 {
return nil, fmt.Errorf("No Timings profile with id: %s", destrateTiming.TimingsTag)
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.TimingsTag)
}
destrateTiming.timing = tm[destrateTiming.TimingsTag]
drm, err := dbr.storDB.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag)
drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag)
if err != nil {
return nil, err
return err
} else if len(drm) == 0 {
return nil, fmt.Errorf("No Timings profile with id: %s", destrateTiming.DestinationRatesTag)
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.DestinationRatesTag)
}
for _, drate := range drm[destrateTiming.DestinationRatesTag] {
rt, err := dbr.storDB.GetTpRates(dbr.tpid, drate.RateTag)
rt, err := dbr.storDb.GetTpRates(dbr.tpid, drate.RateTag)
if err != nil {
return nil, err
return err
} else if len(rt) == 0 {
return nil, fmt.Errorf("No Rates profile with id: %s", drate.RateTag)
return fmt.Errorf("No Rates profile with id: %s", drate.RateTag)
}
drate.Rate = rt[drate.RateTag]
if _, exists := activationPeriods[destrateTiming.Tag]; !exists {
activationPeriods[destrateTiming.Tag] = &ActivationPeriod{}
}
activationPeriods[destrateTiming.Tag].AddIntervalIfNotPresent(destrateTiming.GetInterval(drate))
dm, err := dbr.storDB.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
if err != nil {
return nil, err
return err
}
for _, destination := range dm {
ap := activationPeriods[ratingProfile.destRatesTimingTag]
newAP := &ActivationPeriod{ActivationTime: at}
newAP.Intervals = append(newAP.Intervals, ap.Intervals...)
resultRatingProfile.AddActivationPeriodIfNotPresent(destination.Id, newAP)
dbr.storage.SetDestination(destination)
dbr.dataDb.SetDestination(destination)
}
}
}
}
return resultRatingProfile, nil
return dbr.dataDb.SetRatingProfile(resultRatingProfile)
}
func (dbr *DbReader) LoadActions() (err error) {
dbr.actions, err = dbr.storDB.GetTpActions(dbr.tpid, "")
dbr.actions, err = dbr.storDb.GetTpActions(dbr.tpid, "")
return err
}
func (dbr *DbReader) LoadActionTimings() (err error) {
atsMap, err := dbr.storDB.GetTpActionTimings(dbr.tpid, "")
atsMap, err := dbr.storDb.GetTpActionTimings(dbr.tpid, "")
if err != nil {
return err
}
@@ -301,12 +300,12 @@ func (dbr *DbReader) LoadActionTimings() (err error) {
}
func (dbr *DbReader) LoadActionTriggers() (err error) {
dbr.actionsTriggers, err = dbr.storDB.GetTpActionTriggers(dbr.tpid, "")
dbr.actionsTriggers, err = dbr.storDb.GetTpActionTriggers(dbr.tpid, "")
return err
}
func (dbr *DbReader) LoadAccountActions() (err error) {
acs, err := dbr.storDB.GetTpAccountActions(dbr.tpid, "")
acs, err := dbr.storDb.GetTpAccountActions(dbr.tpid, "")
if err != nil {
return err
}
@@ -335,3 +334,117 @@ func (dbr *DbReader) LoadAccountActions() (err error) {
}
return nil
}
func (dbr *DbReader) LoadAccountActionsByTag(tag string) error {
accountActions, err := dbr.storDb.GetTpAccountActions(dbr.tpid, tag)
if err != nil || len(accountActions) != 1 {
return err
}
accountAction := accountActions[0]
actionTimingsMap, err := dbr.storDb.GetTpActionTimings(dbr.tpid, accountAction.ActionTimingsTag)
if err != nil {
return err
}
actionTriggersMap, err := dbr.storDb.GetTpActionTriggers(dbr.tpid, accountAction.ActionTriggersTag)
if err != nil {
return err
}
// collecting action ids
var actionsIds []string
for _, atms := range actionTimingsMap {
for _, atm := range atms {
actionsIds = append(actionsIds, atm.ActionsId)
}
}
for _, atrs := range actionTriggersMap {
for _, atr := range atrs {
actionsIds = append(actionsIds, atr.ActionsId)
}
}
var acts map[string][]*Action
for _, actId := range actionsIds {
actions, err := dbr.storDb.GetTpActions(dbr.tpid, actId)
if err != nil {
return err
}
for id, act := range actions {
acts[id] = act
}
}
// write action timings
for k, atms := range actionTimingsMap {
err = dbr.storDb.SetActionTimings(k, atms)
if err != nil {
return err
}
}
// write actions
for k, as := range acts {
err = dbr.storDb.SetActions(k, as)
if err != nil {
return err
}
}
activationPeriods := make(map[string]*ActivationPeriod)
resultRatingProfile := &RatingProfile{Id: tag}
rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag)
if err != nil {
return err
} else if len(rpm) == 0 {
return fmt.Errorf("No RateProfile with id: %s", tag)
}
for _, ratingProfile := range rpm {
resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key
at := time.Unix(ratingProfile.activationTime, 0)
drtm, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.destRatesTimingTag)
if err != nil {
return err
} else if len(drtm) == 0 {
return fmt.Errorf("No DestRateTimings profile with id: %s", ratingProfile.destRatesTimingTag)
}
for _, destrateTiming := range drtm {
tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag)
if err != nil {
return err
} else if len(tm) == 0 {
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.TimingsTag)
}
destrateTiming.timing = tm[destrateTiming.TimingsTag]
drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag)
if err != nil {
return err
} else if len(drm) == 0 {
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.DestinationRatesTag)
}
for _, drate := range drm[destrateTiming.DestinationRatesTag] {
rt, err := dbr.storDb.GetTpRates(dbr.tpid, drate.RateTag)
if err != nil {
return err
} else if len(rt) == 0 {
return fmt.Errorf("No Rates profile with id: %s", drate.RateTag)
}
drate.Rate = rt[drate.RateTag]
if _, exists := activationPeriods[destrateTiming.Tag]; !exists {
activationPeriods[destrateTiming.Tag] = &ActivationPeriod{}
}
activationPeriods[destrateTiming.Tag].AddIntervalIfNotPresent(destrateTiming.GetInterval(drate))
dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
if err != nil {
return err
}
for _, destination := range dm {
ap := activationPeriods[ratingProfile.destRatesTimingTag]
newAP := &ActivationPeriod{ActivationTime: at}
newAP.Intervals = append(newAP.Intervals, ap.Intervals...)
resultRatingProfile.AddActivationPeriodIfNotPresent(destination.Id, newAP)
dbr.dataDb.SetDestination(destination)
}
}
}
}
return dbr.dataDb.SetRatingProfile(resultRatingProfile)
}

View File

@@ -62,6 +62,10 @@ func (mb *MinuteBucket) Equal(o *MinuteBucket) bool {
mb.Percent == o.Percent
}
func (mb *MinuteBucket) IsExpired() bool {
return !mb.ExpirationDate.IsZero() && mb.ExpirationDate.Before(time.Now())
}
/*
Structure to store minute buckets according to weight, precision or price.
*/

View File

@@ -112,6 +112,7 @@ func (a *Action) Store() (result string, err error) {
result += a.ActionType + "|"
result += a.BalanceId + "|"
result += a.Direction + "|"
result += a.ExpirationDate.Format(time.RFC3339) + "|"
result += strconv.FormatFloat(a.Units, 'f', -1, 64) + "|"
result += strconv.FormatFloat(a.Weight, 'f', -1, 64)
if a.MinuteBucket != nil {
@@ -125,20 +126,24 @@ func (a *Action) Store() (result string, err error) {
return
}
func (a *Action) Restore(input string) error {
func (a *Action) Restore(input string) (err error) {
elements := strings.Split(input, "|")
if len(elements) < 6 {
if len(elements) < 7 {
return notEnoughElements
}
a.Id = elements[0]
a.ActionType = elements[1]
a.BalanceId = elements[2]
a.Direction = elements[3]
a.Units, _ = strconv.ParseFloat(elements[4], 64)
a.Weight, _ = strconv.ParseFloat(elements[5], 64)
if len(elements) == 7 {
a.ExpirationDate, err = time.Parse(time.RFC3339, elements[4])
if err != nil {
return err
}
a.Units, _ = strconv.ParseFloat(elements[5], 64)
a.Weight, _ = strconv.ParseFloat(elements[6], 64)
if len(elements) == 8 {
a.MinuteBucket = &MinuteBucket{}
if err := a.MinuteBucket.Restore(elements[6]); err != nil {
if err := a.MinuteBucket.Restore(elements[7]); err != nil {
return err
}
}

View File

@@ -23,6 +23,8 @@ import (
"encoding/json"
"fmt"
"github.com/cgrates/cgrates/utils"
"strconv"
"time"
)
type SQLStorage struct {
@@ -550,10 +552,10 @@ func (self *SQLStorage) GetTPActions(tpid, actsId string) (*utils.TPActions, err
i := 0
for rows.Next() {
i++ //Keep here a reference so we know we got at least one result
var action, balanceId, dir, destId, rateType string
var action, balanceId, dir, destId, rateType string
var expTime int64
var units, rate, minutesWeight, weight float64
if err = rows.Scan(&action, &balanceId, &dir, &units, &expTime, &destId, &rateType, &rate, &minutesWeight, &weight); err!= nil {
if err = rows.Scan(&action, &balanceId, &dir, &units, &expTime, &destId, &rateType, &rate, &minutesWeight, &weight); err != nil {
return nil, err
}
acts.Actions = append(acts.Actions, utils.Action{action, balanceId, dir, units, expTime, destId, rateType, rate, minutesWeight, weight})
@@ -632,7 +634,7 @@ func (self *SQLStorage) GetTPActionTimings(tpid, atId string) (map[string][]*uti
i++ //Keep here a reference so we know we got at least one result
var tag, actionsId, timingId string
var weight float64
if err = rows.Scan(&tag, &actionsId, &timingId, &weight); err!= nil {
if err = rows.Scan(&tag, &actionsId, &timingId, &weight); err != nil {
return nil, err
}
ats[tag] = append(ats[tag], &utils.TPActionTimingsRow{actionsId, timingId, weight})
@@ -969,17 +971,23 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er
for rows.Next() {
var id int
var units, rate, minutes_weight, weight float64
var tpid, tag, action, balance_tag, direction, destinations_tag, rate_type string
if err := rows.Scan(&id, &tpid, &tag, &action, &balance_tag, &direction, &units, &destinations_tag, &rate_type, &rate, &minutes_weight, &weight); err != nil {
var tpid, tag, action, balance_tag, direction, destinations_tag, rate_type, expirationDate string
if err := rows.Scan(&id, &tpid, &tag, &action, &balance_tag, &direction, &units, &expirationDate, &destinations_tag, &rate_type, &rate, &minutes_weight, &weight); err != nil {
return nil, err
}
unix, err := strconv.ParseInt(expirationDate, 10, 64)
if err != nil {
return nil, err
}
expDate := time.Unix(unix, 0)
var a *Action
if balance_tag != MINUTES {
a = &Action{
ActionType: action,
BalanceId: balance_tag,
Direction: direction,
Units: units,
ActionType: action,
BalanceId: balance_tag,
Direction: direction,
Units: units,
ExpirationDate: expDate,
}
} else {
var percent, price float64
@@ -990,17 +998,19 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er
price = rate
}
a = &Action{
Id: utils.GenUUID(),
ActionType: action,
BalanceId: balance_tag,
Direction: direction,
Weight: weight,
Id: utils.GenUUID(),
ActionType: action,
BalanceId: balance_tag,
Direction: direction,
Weight: weight,
ExpirationDate: expDate,
MinuteBucket: &MinuteBucket{
Seconds: units,
Weight: minutes_weight,
Price: price,
Percent: percent,
DestinationId: destinations_tag,
Seconds: units,
Weight: minutes_weight,
Price: price,
Percent: percent,
DestinationId: destinations_tag,
ExpirationDate: expDate,
},
}
}

View File

@@ -20,6 +20,7 @@ package rater
import (
"errors"
"github.com/cgrates/cgrates/utils"
"sort"
"strings"
"time"
@@ -62,8 +63,7 @@ type Balance struct {
}
func (b *Balance) Equal(o *Balance) bool {
return b.Value == o.Value ||
b.ExpirationDate.Equal(o.ExpirationDate) ||
return b.ExpirationDate.Equal(o.ExpirationDate) ||
b.Weight == o.Weight
}
@@ -150,6 +150,9 @@ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float
return
}
for _, mb := range ub.MinuteBuckets {
if mb.IsExpired() {
continue
}
d, err := GetDestination(mb.DestinationId)
if err != nil {
continue
@@ -177,6 +180,9 @@ func (ub *UserBalance) debitMinuteBucket(newMb *MinuteBucket) error {
}
found := false
for _, mb := range ub.MinuteBuckets {
if mb.IsExpired() {
continue
}
if mb.Equal(newMb) {
mb.Seconds -= newMb.Seconds
found = true
@@ -242,6 +248,29 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count
return nil
}
// Debits some amount of user's specified balance adding the balance if it does not exists.
// Returns the remaining credit in user's balance.
func (ub *UserBalance) debitBalanceAction(a *Action) float64 {
newBalance := &Balance{
Id: utils.GenUUID(),
ExpirationDate: a.ExpirationDate,
Weight: a.Weight,
}
found := false
id := a.BalanceId + a.Direction
for _, b := range ub.BalanceMap[id] {
if b.Equal(newBalance) {
b.Value -= a.Units
found = true
}
}
if !found {
newBalance.Value -= a.Units
ub.BalanceMap[id] = append(ub.BalanceMap[id], newBalance)
}
return ub.BalanceMap[a.BalanceId+OUTBOUND].GetTotalValue()
}
/*
Debits some amount of user's specified balance. Returns the remaining credit in user's balance.
*/
@@ -397,3 +426,21 @@ func (ub *UserBalance) initMinuteCounters() {
}
}
}
func (ub *UserBalance) CleanExpiredBalancesAndBuckets() {
for key, _ := range ub.BalanceMap {
bm := ub.BalanceMap[key]
for i := 0; i < len(bm); i++ {
if bm[i].IsExpired() {
// delete it
bm = append(bm[:i], bm[i+1:]...)
}
}
ub.BalanceMap[key] = bm
}
for i := 0; i < len(ub.MinuteBuckets); i++ {
if ub.MinuteBuckets[i].IsExpired() {
ub.MinuteBuckets = append(ub.MinuteBuckets[:i], ub.MinuteBuckets[i+1:]...)
}
}
}

View File

@@ -469,6 +469,27 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) {
}
}
func TestCleanExpired(t *testing.T) {
ub := &UserBalance{
Id: "TEST_UB_OREDER",
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{
&Balance{ExpirationDate: time.Now().Add(10 * time.Second)},
&Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)},
&Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}},
MinuteBuckets: []*MinuteBucket{
&MinuteBucket{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)},
&MinuteBucket{ExpirationDate: time.Now().Add(10 * time.Second)},
},
}
ub.CleanExpiredBalancesAndBuckets()
if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 2 {
t.Error("Error cleaning expired balances!")
}
if len(ub.MinuteBuckets) != 1 {
t.Error("Error cleaning expired minute buckets!")
}
}
func TestUserBalanceUnitCounting(t *testing.T) {
ub := &UserBalance{}
ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10})