diff --git a/engine/action_trigger.go b/engine/action_trigger.go
index 5fae1ca24..3a8aaa281 100644
--- a/engine/action_trigger.go
+++ b/engine/action_trigger.go
@@ -64,6 +64,22 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) {
return
}
+// returns true if the field of the action timing are equeal to the non empty
+// fields of the action
+func (at *ActionTrigger) Match(a *Action) bool {
+ if a == nil {
+ return true
+ }
+ id := a.BalanceId == "" || at.BalanceId == a.BalanceId
+ direction := a.Direction == "" || at.Direction == a.Direction
+ thresholdType, thresholdValue := true, true
+ if a.MinuteBucket != nil {
+ thresholdType = a.MinuteBucket.PriceType == "" || at.ThresholdType == a.MinuteBucket.PriceType
+ thresholdValue = a.MinuteBucket.Price == 0 || at.ThresholdValue == a.MinuteBucket.Price
+ }
+ return id && direction && thresholdType && thresholdValue
+}
+
// Structure to store actions according to weight
type ActionTriggerPriotityList []*ActionTrigger
diff --git a/engine/actions_test.go b/engine/actions_test.go
index 23a5301ff..3c9d2480c 100644
--- a/engine/actions_test.go
+++ b/engine/actions_test.go
@@ -400,6 +400,110 @@ func TestActionTimingPriotityListWeight(t *testing.T) {
}
}
+func TestActionTriggerMatchNil(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ var a *Action
+ if !at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatchAllBlank(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{}
+ if !at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatchMinuteBucketBlank(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{Direction: OUTBOUND, BalanceId: CREDIT}
+ if !at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatchMinuteBucketFull(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
+ if !at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatchAllFull(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
+ if !at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatchSomeFalse(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{Direction: INBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}}
+ if at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatcMinuteBucketFalse(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 3}}
+ if at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
+func TestActionTriggerMatcAllFalse(t *testing.T) {
+ at := &ActionTrigger{
+ Direction: OUTBOUND,
+ BalanceId: CREDIT,
+ ThresholdType: TRIGGER_MAX_BALANCE,
+ ThresholdValue: 2,
+ }
+ a := &Action{Direction: INBOUND, BalanceId: MINUTES, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_COUNTER, Price: 3}}
+ if at.Match(a) {
+ t.Errorf("Action trigger [%v] does not match action [%v]", at, a)
+ }
+}
+
func TestActionTriggerPriotityList(t *testing.T) {
at1 := &ActionTrigger{Weight: 10}
at2 := &ActionTrigger{Weight: 20}
diff --git a/engine/balances.go b/engine/balances.go
new file mode 100644
index 000000000..cffdf95a1
--- /dev/null
+++ b/engine/balances.go
@@ -0,0 +1,119 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2013 ITsysCOM
+
+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
+*/
+
+package engine
+
+import (
+ "sort"
+ "time"
+)
+
+type Balance struct {
+ Id string
+ Value float64
+ ExpirationDate time.Time
+ Weight float64
+ GroupIds []string
+ SpecialPercent float64
+}
+
+func (b *Balance) Equal(o *Balance) bool {
+ return b.ExpirationDate.Equal(o.ExpirationDate) ||
+ b.Weight == o.Weight
+}
+
+func (b *Balance) IsExpired() bool {
+ return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now())
+}
+
+func (b *Balance) Clone() *Balance {
+ return &Balance{
+ Id: b.Id,
+ Value: b.Value,
+ ExpirationDate: b.ExpirationDate,
+ Weight: b.Weight,
+ }
+}
+
+/*
+Structure to store minute buckets according to weight, precision or price.
+*/
+type BalanceChain []*Balance
+
+func (bc BalanceChain) Len() int {
+ return len(bc)
+}
+
+func (bc BalanceChain) Swap(i, j int) {
+ bc[i], bc[j] = bc[j], bc[i]
+}
+
+func (bc BalanceChain) Less(j, i int) bool {
+ return bc[i].Weight < bc[j].Weight
+}
+
+func (bc BalanceChain) Sort() {
+ sort.Sort(bc)
+}
+
+func (bc BalanceChain) GetTotalValue() (total float64) {
+ for _, b := range bc {
+ if !b.IsExpired() {
+ total += b.Value
+ }
+ }
+ return
+}
+
+func (bc BalanceChain) Debit(amount float64) float64 {
+ bc.Sort()
+ for i, b := range bc {
+ if b.IsExpired() {
+ continue
+ }
+ if b.Value >= amount || i == len(bc)-1 { // if last one go negative
+ b.Value -= amount
+ break
+ }
+ b.Value = 0
+ amount -= b.Value
+ }
+ return bc.GetTotalValue()
+}
+
+func (bc BalanceChain) Equal(o BalanceChain) bool {
+ if len(bc) != len(o) {
+ return false
+ }
+ bc.Sort()
+ o.Sort()
+ for i := 0; i < len(bc); i++ {
+ if !bc[i].Equal(o[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+func (bc BalanceChain) Clone() BalanceChain {
+ var newChain BalanceChain
+ for _, b := range bc {
+ newChain = append(newChain, b.Clone())
+ }
+ return newChain
+}
diff --git a/engine/calldesc.go b/engine/calldesc.go
index 26904dc62..7b7551698 100644
--- a/engine/calldesc.go
+++ b/engine/calldesc.go
@@ -369,7 +369,9 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) {
}
for _, ts := range cc.Timespans {
if ts.MinuteInfo != nil {
- userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true)
+ if err = userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true); err != nil {
+ return cc, err
+ }
}
}
}
@@ -387,8 +389,7 @@ func (cd *CallDescriptor) MaxDebit(startTime time.Time) (cc *CallCost, err error
return new(CallCost), errors.New("no more credit")
}
if remainingSeconds > 0 { // for postpaying client returns -1
- rs, _ := time.ParseDuration(fmt.Sprintf("%vs", remainingSeconds))
- cd.TimeEnd = cd.TimeStart.Add(rs)
+ cd.TimeEnd = cd.TimeStart.Add(time.Duration(remainingSeconds) * time.Second)
}
return cd.Debit()
}
diff --git a/engine/groups.go b/engine/groups.go
new file mode 100644
index 000000000..baa650fc5
--- /dev/null
+++ b/engine/groups.go
@@ -0,0 +1,46 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2013 ITsysCOM
+
+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
+*/
+
+package engine
+
+import (
+ "sort"
+)
+
+type GroupLink struct {
+ Id string
+ Weight float64
+}
+
+type GroupLinks []*GroupLink
+
+func (gls GroupLinks) Len() int {
+ return len(gls)
+}
+
+func (gls GroupLinks) Swap(i, j int) {
+ gls[i], gls[j] = gls[j], gls[i]
+}
+
+func (gls GroupLinks) Less(j, i int) bool {
+ return gls[i].Weight < gls[j].Weight
+}
+
+func (gls GroupLinks) Sort() {
+ sort.Sort(gls)
+}
diff --git a/engine/userbalance.go b/engine/userbalance.go
index 9aa25b2df..d43f7ca50 100644
--- a/engine/userbalance.go
+++ b/engine/userbalance.go
@@ -21,9 +21,7 @@ package engine
import (
"errors"
"github.com/cgrates/cgrates/utils"
- "sort"
"strings"
- "time"
)
const (
@@ -41,6 +39,11 @@ const (
// action price type
PRICE_PERCENT = "*percent"
PRICE_ABSOLUTE = "*absolute"
+ // action trigger threshold types
+ TRIGGER_MIN_COUNTER = "*min_counter"
+ TRIGGER_MAX_COUNTER = "*max_counter"
+ TRIGGER_MIN_BALANCE = "*min_balance"
+ TRIGGER_MAX_BALANCE = "*max_balance"
)
var (
@@ -49,6 +52,7 @@ var (
/*
Structure containing information about user's credit (minutes, cents, sms...).'
+This can represent a user or a shared group.
*/
type UserBalance struct {
Id string
@@ -57,104 +61,10 @@ type UserBalance struct {
MinuteBuckets []*MinuteBucket
UnitCounters []*UnitsCounter
ActionTriggers ActionTriggerPriotityList
+
+ Groups GroupLinks // user info about groups
// group information
- GroupIds []string
- UserIds []string
-}
-
-type Balance struct {
- Id string
- Value float64
- ExpirationDate time.Time
- Weight float64
- GroupIds []string
- Percent float64
-}
-
-func (b *Balance) Equal(o *Balance) bool {
- return b.ExpirationDate.Equal(o.ExpirationDate) ||
- b.Weight == o.Weight
-}
-
-func (b *Balance) IsExpired() bool {
- return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now())
-}
-
-func (b *Balance) Clone() *Balance {
- return &Balance{
- Id: b.Id,
- Value: b.Value,
- ExpirationDate: b.ExpirationDate,
- Weight: b.Weight,
- }
-}
-
-/*
-Structure to store minute buckets according to weight, precision or price.
-*/
-type BalanceChain []*Balance
-
-func (bc BalanceChain) Len() int {
- return len(bc)
-}
-
-func (bc BalanceChain) Swap(i, j int) {
- bc[i], bc[j] = bc[j], bc[i]
-}
-
-func (bc BalanceChain) Less(j, i int) bool {
- return bc[i].Weight < bc[j].Weight
-}
-
-func (bc BalanceChain) Sort() {
- sort.Sort(bc)
-}
-
-func (bc BalanceChain) GetTotalValue() (total float64) {
- for _, b := range bc {
- if !b.IsExpired() {
- total += b.Value
- }
- }
- return
-}
-
-func (bc BalanceChain) Debit(amount float64) float64 {
- bc.Sort()
- for i, b := range bc {
- if b.IsExpired() {
- continue
- }
- if b.Value >= amount || i == len(bc)-1 { // if last one go negative
- b.Value -= amount
- break
- }
- b.Value = 0
- amount -= b.Value
- }
- return bc.GetTotalValue()
-}
-
-func (bc BalanceChain) Equal(o BalanceChain) bool {
- if len(bc) != len(o) {
- return false
- }
- bc.Sort()
- o.Sort()
- for i := 0; i < len(bc); i++ {
- if !bc[i].Equal(o[i]) {
- return false
- }
- }
- return true
-}
-
-func (bc BalanceChain) Clone() BalanceChain {
- var newChain BalanceChain
- for _, b := range bc {
- newChain = append(newChain, b.Clone())
- }
- return newChain
+ UserIds []string // group info about users
}
/*
@@ -309,11 +219,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
// the next reset (see RESET_TRIGGERS action type)
continue
}
- if a != nil && (at.BalanceId != a.BalanceId ||
- at.Direction != a.Direction ||
- (a.MinuteBucket != nil &&
- (at.ThresholdType != a.MinuteBucket.PriceType ||
- at.ThresholdValue != a.MinuteBucket.Price))) {
+ if !at.Match(a) {
continue
}
if strings.Contains(at.ThresholdType, "counter") {
@@ -386,11 +292,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) {
// If the action is not nil it acts like a filter
func (ub *UserBalance) resetActionTriggers(a *Action) {
for _, at := range ub.ActionTriggers {
- if a != nil && (at.BalanceId != a.BalanceId ||
- at.Direction != a.Direction ||
- (a.MinuteBucket != nil &&
- (at.ThresholdType != a.MinuteBucket.PriceType ||
- at.ThresholdValue != a.MinuteBucket.Price))) {
+ if !at.Match(a) {
continue
}
at.Executed = false