diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go
index bc527df95..f4b1a1fff 100644
--- a/apier/v1/accounts.go
+++ b/apier/v1/accounts.go
@@ -388,10 +388,14 @@ type AttrAddBalance struct {
Weight float64
SharedGroups string
Overwrite bool // When true it will reset if the balance is already there
+ Blocker bool
Disabled bool
}
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
+ if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 {
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
expTime, err := utils.ParseTimeDetectLayout(attr.ExpiryTime, self.Config.DefaultTimezone)
if err != nil {
*reply = err.Error()
@@ -411,12 +415,9 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
at := &engine.ActionTiming{}
at.SetAccountIDs(utils.StringMap{accID: true})
- aType := engine.DEBIT
- // reverse the sign as it is a debit
- attr.Value = -attr.Value
-
+ aType := engine.TOPUP
if attr.Overwrite {
- aType = engine.DEBIT_RESET
+ aType = engine.TOPUP_RESET
}
at.SetActions(engine.Actions{
&engine.Action{
@@ -433,6 +434,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
Categories: utils.ParseStringMap(attr.Categories),
Weight: attr.Weight,
SharedGroups: utils.ParseStringMap(attr.SharedGroups),
+ Blocker: attr.Blocker,
Disabled: attr.Disabled,
},
},
@@ -446,7 +448,10 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
}
func (self *ApierV1) EnableDisableBalance(attr *AttrAddBalance, reply *string) error {
- expTime, err := utils.ParseDate(attr.ExpiryTime)
+ if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 {
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ expTime, err := utils.ParseTimeDetectLayout(attr.ExpiryTime, self.Config.DefaultTimezone)
if err != nil {
*reply = err.Error()
return err
@@ -468,10 +473,12 @@ func (self *ApierV1) EnableDisableBalance(attr *AttrAddBalance, reply *string) e
Value: attr.Value,
ExpirationDate: expTime,
RatingSubject: attr.RatingSubject,
+ Categories: utils.ParseStringMap(attr.Categories),
Directions: utils.ParseStringMap(attr.Directions),
DestinationIds: utils.ParseStringMap(attr.DestinationIds),
Weight: attr.Weight,
SharedGroups: utils.ParseStringMap(attr.SharedGroups),
+ Blocker: attr.Blocker,
Disabled: attr.Disabled,
},
},
@@ -485,7 +492,10 @@ func (self *ApierV1) EnableDisableBalance(attr *AttrAddBalance, reply *string) e
}
func (self *ApierV1) RemoveBalances(attr *AttrAddBalance, reply *string) error {
- expTime, err := utils.ParseDate(attr.ExpiryTime)
+ if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 {
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ expTime, err := utils.ParseTimeDetectLayout(attr.ExpiryTime, self.Config.DefaultTimezone)
if err != nil {
*reply = err.Error()
return err
@@ -509,8 +519,57 @@ func (self *ApierV1) RemoveBalances(attr *AttrAddBalance, reply *string) error {
RatingSubject: attr.RatingSubject,
Directions: utils.ParseStringMap(attr.Directions),
DestinationIds: utils.ParseStringMap(attr.DestinationIds),
+ Categories: utils.ParseStringMap(attr.Categories),
Weight: attr.Weight,
SharedGroups: utils.ParseStringMap(attr.SharedGroups),
+ Blocker: attr.Blocker,
+ Disabled: attr.Disabled,
+ },
+ },
+ })
+ if err := at.Execute(); err != nil {
+ *reply = err.Error()
+ return err
+ }
+ *reply = OK
+ return nil
+}
+
+func (self *ApierV1) SetBalance(attr *AttrAddBalance, reply *string) error {
+ if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 {
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ if attr.BalanceId == "" && attr.BalanceUuid == "" {
+ return utils.NewErrMandatoryIeMissing("BalanceId", "or", "BalanceUuid")
+ }
+ expTime, err := utils.ParseTimeDetectLayout(attr.ExpiryTime, self.Config.DefaultTimezone)
+ if err != nil {
+ *reply = err.Error()
+ return err
+ }
+ accID := utils.ConcatenatedKey(attr.Tenant, attr.Account)
+ if _, err := self.AccountDb.GetAccount(accID); err != nil {
+ return utils.ErrNotFound
+ }
+ at := &engine.ActionTiming{}
+ at.SetAccountIDs(utils.StringMap{accID: true})
+
+ at.SetActions(engine.Actions{
+ &engine.Action{
+ ActionType: engine.SET_BALANCE,
+ BalanceType: attr.BalanceType,
+ Balance: &engine.Balance{
+ Uuid: attr.BalanceUuid,
+ Id: attr.BalanceId,
+ Value: attr.Value,
+ ExpirationDate: expTime,
+ RatingSubject: attr.RatingSubject,
+ Directions: utils.ParseStringMap(attr.Directions),
+ DestinationIds: utils.ParseStringMap(attr.DestinationIds),
+ Categories: utils.ParseStringMap(attr.Categories),
+ Weight: attr.Weight,
+ SharedGroups: utils.ParseStringMap(attr.SharedGroups),
+ Blocker: true,
Disabled: attr.Disabled,
},
},
diff --git a/console/balance_debit.go b/console/balance_add.go
similarity index 51%
rename from console/balance_debit.go
rename to console/balance_add.go
index ab6f45ec8..b60a59380 100644
--- a/console/balance_debit.go
+++ b/console/balance_add.go
@@ -18,51 +18,48 @@ along with this program. If not, see
package console
-import "github.com/cgrates/cgrates/engine"
+import (
+ "github.com/cgrates/cgrates/apier/v1"
+ "github.com/cgrates/cgrates/utils"
+)
func init() {
- c := &CmdDebitBalance{
- name: "balance_debit",
- rpcMethod: "Responder.Debit",
- rpcParams: &engine.CallDescriptor{Direction: "*out"},
- clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
+ c := &CmdAddBalance{
+ name: "balance_add",
+ rpcMethod: "ApierV1.AddBalance",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
-type CmdDebitBalance struct {
- name string
- rpcMethod string
- rpcParams *engine.CallDescriptor
- clientArgs []string
+type CmdAddBalance struct {
+ name string
+ rpcMethod string
+ rpcParams *v1.AttrAddBalance
*CommandExecuter
}
-func (self *CmdDebitBalance) Name() string {
+func (self *CmdAddBalance) Name() string {
return self.name
}
-func (self *CmdDebitBalance) RpcMethod() string {
+func (self *CmdAddBalance) RpcMethod() string {
return self.rpcMethod
}
-func (self *CmdDebitBalance) RpcParams(reset bool) interface{} {
+func (self *CmdAddBalance) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
- self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
+ self.rpcParams = &v1.AttrAddBalance{BalanceType: utils.MONETARY, Overwrite: false}
}
return self.rpcParams
}
-func (self *CmdDebitBalance) PostprocessRpcParams() error {
+func (self *CmdAddBalance) PostprocessRpcParams() error {
return nil
}
-func (self *CmdDebitBalance) RpcResult() interface{} {
- return &engine.CallCost{}
-}
-
-func (self *CmdDebitBalance) ClientArgs() []string {
- return self.clientArgs
+func (self *CmdAddBalance) RpcResult() interface{} {
+ var s string
+ return &s
}
diff --git a/console/balance_remove.go b/console/balance_remove.go
new file mode 100644
index 000000000..b01324f6f
--- /dev/null
+++ b/console/balance_remove.go
@@ -0,0 +1,65 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2012-2015 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 console
+
+import (
+ "github.com/cgrates/cgrates/apier/v1"
+ "github.com/cgrates/cgrates/utils"
+)
+
+func init() {
+ c := &CmdRemoveBalance{
+ name: "balance_remove",
+ rpcMethod: "ApierV1.RemoveBalances",
+ }
+ commands[c.Name()] = c
+ c.CommandExecuter = &CommandExecuter{c}
+}
+
+// Commander implementation
+type CmdRemoveBalance struct {
+ name string
+ rpcMethod string
+ rpcParams *v1.AttrAddBalance
+ *CommandExecuter
+}
+
+func (self *CmdRemoveBalance) Name() string {
+ return self.name
+}
+
+func (self *CmdRemoveBalance) RpcMethod() string {
+ return self.rpcMethod
+}
+
+func (self *CmdRemoveBalance) RpcParams(reset bool) interface{} {
+ if reset || self.rpcParams == nil {
+ self.rpcParams = &v1.AttrAddBalance{BalanceType: utils.MONETARY, Overwrite: false}
+ }
+ return self.rpcParams
+}
+
+func (self *CmdRemoveBalance) PostprocessRpcParams() error {
+ return nil
+}
+
+func (self *CmdRemoveBalance) RpcResult() interface{} {
+ var s string
+ return &s
+}
diff --git a/console/balance_set.go b/console/balance_set.go
index 89f6a70c5..eee047382 100644
--- a/console/balance_set.go
+++ b/console/balance_set.go
@@ -24,42 +24,42 @@ import (
)
func init() {
- c := &CmdAddBalance{
+ c := &CmdSetBalance{
name: "balance_set",
- rpcMethod: "ApierV1.AddBalance",
+ rpcMethod: "ApierV1.SetBalance",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
-type CmdAddBalance struct {
+type CmdSetBalance struct {
name string
rpcMethod string
rpcParams *v1.AttrAddBalance
*CommandExecuter
}
-func (self *CmdAddBalance) Name() string {
+func (self *CmdSetBalance) Name() string {
return self.name
}
-func (self *CmdAddBalance) RpcMethod() string {
+func (self *CmdSetBalance) RpcMethod() string {
return self.rpcMethod
}
-func (self *CmdAddBalance) RpcParams(reset bool) interface{} {
+func (self *CmdSetBalance) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
self.rpcParams = &v1.AttrAddBalance{BalanceType: utils.MONETARY, Overwrite: false}
}
return self.rpcParams
}
-func (self *CmdAddBalance) PostprocessRpcParams() error {
+func (self *CmdSetBalance) PostprocessRpcParams() error {
return nil
}
-func (self *CmdAddBalance) RpcResult() interface{} {
+func (self *CmdSetBalance) RpcResult() interface{} {
var s string
return &s
}
diff --git a/engine/account.go b/engine/account.go
index 7ae6b8083..9044639b1 100644
--- a/engine/account.go
+++ b/engine/account.go
@@ -88,6 +88,83 @@ func (ub *Account) getCreditForPrefix(cd *CallDescriptor) (duration time.Duratio
return
}
+// sets all the fields of the balance
+func (ub *Account) setBalanceAction(a *Action) error {
+ if a == nil {
+ return errors.New("nil action")
+ }
+ bClone := a.Balance.Clone()
+ if bClone == nil {
+ return errors.New("nil balance")
+ }
+ // load ValueFactor if defined in extra parametrs
+ if a.ExtraParameters != "" {
+ vf := ValueFactor{}
+ err := json.Unmarshal([]byte(a.ExtraParameters), &vf)
+ if err == nil {
+ bClone.Factor = vf
+ } else {
+ utils.Logger.Warning(fmt.Sprintf("Could load value factor from actions: extra parametrs: %s", a.ExtraParameters))
+ }
+ }
+ if ub.BalanceMap == nil {
+ ub.BalanceMap = make(map[string]BalanceChain, 1)
+ }
+ found := false
+ balanceType := a.BalanceType
+ var previousSharedGroups utils.StringMap // kept for comparison
+ for _, b := range ub.BalanceMap[balanceType] {
+ if b.IsExpired() {
+ continue // just to be safe (cleaned expired balances above)
+ }
+ b.account = ub
+ if b.MatchFilter(a.Balance, false) { // for Id or Uuid only
+ bClone.Id = b.Id
+ bClone.Uuid = b.Uuid
+ previousSharedGroups = b.SharedGroups
+ *b = *bClone
+ found = true
+ break // only set one balance
+ }
+ }
+ // if it is not found then we add it to the list
+ if !found {
+ // check if the Id is *default (user trying to create the default balance)
+ // use only it's value value
+ if bClone.Id == utils.META_DEFAULT {
+ bClone = &Balance{
+ Id: utils.META_DEFAULT,
+ Value: bClone.GetValue(),
+ }
+ }
+ bClone.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers
+ bClone.Uuid = utils.GenUUID() // alway overwrite the uuid for consistency
+ ub.BalanceMap[balanceType] = append(ub.BalanceMap[balanceType], bClone)
+ }
+ if !found || !previousSharedGroups.Equal(bClone.SharedGroups) {
+ for sgId := range a.Balance.SharedGroups {
+ // add shared group member
+ sg, err := ratingStorage.GetSharedGroup(sgId, false)
+ if err != nil || sg == nil {
+ //than is problem
+ utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId))
+ } else {
+ if _, found := sg.MemberIds[ub.Id]; !found {
+ // add member and save
+ if sg.MemberIds == nil {
+ sg.MemberIds = make(utils.StringMap)
+ }
+ sg.MemberIds[ub.Id] = true
+ ratingStorage.SetSharedGroup(sg)
+ }
+ }
+ }
+ }
+ ub.InitCounters()
+ ub.executeActionTriggers(nil)
+ 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 *Account) debitBalanceAction(a *Action, reset bool) error {
@@ -102,8 +179,8 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
ub.BalanceMap = make(map[string]BalanceChain, 1)
}
found := false
- id := a.BalanceType
- for _, b := range ub.BalanceMap[id] {
+ balanceType := a.BalanceType
+ for _, b := range ub.BalanceMap[balanceType] {
if b.IsExpired() {
continue // just to be safe (cleaned expired balances above)
}
@@ -113,6 +190,7 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
b.SetValue(0)
}
b.SubstractValue(bClone.GetValue())
+ b.dirty = true
found = true
}
}
@@ -131,9 +209,8 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
}
}
bClone.dirty = true // Mark the balance as dirty since we have modified and it should be checked by action triggers
- if bClone.Uuid == "" {
- bClone.Uuid = utils.GenUUID()
- }
+
+ bClone.Uuid = utils.GenUUID() // alway overwrite the uuid for consistency
// load ValueFactor if defined in extra parametrs
if a.ExtraParameters != "" {
vf := ValueFactor{}
@@ -144,27 +221,28 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
utils.Logger.Warning(fmt.Sprintf("Could load value factor from actions: extra parametrs: %s", a.ExtraParameters))
}
}
- ub.BalanceMap[id] = append(ub.BalanceMap[id], bClone)
- }
- for sgId := range a.Balance.SharedGroups {
- // add shared group member
- sg, err := ratingStorage.GetSharedGroup(sgId, false)
- if err != nil || sg == nil {
- //than is problem
- utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId))
- } else {
- if _, found := sg.MemberIds[ub.Id]; !found {
- // add member and save
- if sg.MemberIds == nil {
- sg.MemberIds = make(utils.StringMap)
+ ub.BalanceMap[balanceType] = append(ub.BalanceMap[balanceType], bClone)
+
+ for sgId := range a.Balance.SharedGroups {
+ // add shared group member
+ sg, err := ratingStorage.GetSharedGroup(sgId, false)
+ if err != nil || sg == nil {
+ //than is problem
+ utils.Logger.Warning(fmt.Sprintf("Could not get shared group: %v", sgId))
+ } else {
+ if _, found := sg.MemberIds[ub.Id]; !found {
+ // add member and save
+ if sg.MemberIds == nil {
+ sg.MemberIds = make(utils.StringMap)
+ }
+ sg.MemberIds[ub.Id] = true
+ ratingStorage.SetSharedGroup(sg)
}
- sg.MemberIds[ub.Id] = true
- ratingStorage.SetSharedGroup(sg)
}
}
}
ub.executeActionTriggers(nil)
- return nil //ub.BalanceMap[id].GetTotalValue()
+ return nil
}
func (ub *Account) enableDisableBalanceAction(a *Action) error {
diff --git a/engine/action.go b/engine/action.go
index 147b176a7..b579482fc 100644
--- a/engine/action.go
+++ b/engine/action.go
@@ -57,6 +57,7 @@ const (
DENY_NEGATIVE = "*deny_negative"
RESET_ACCOUNT = "*reset_account"
REMOVE_ACCOUNT = "*remove_account"
+ SET_BALANCE = "*set_balance"
REMOVE_BALANCE = "*remove_balance"
TOPUP_RESET = "*topup_reset"
TOPUP = "*topup"
@@ -135,8 +136,10 @@ func getActionFunc(typ string) (actionTypeFunc, bool) {
return removeAccountAction, true
case REMOVE_BALANCE:
return removeBalanceAction, true
+ case SET_BALANCE:
+ return setBalanceAction, true
case TRANSFER_MONETARY_DEFAULT:
- return transferMonetaryDefault, true
+ return transferMonetaryDefaultAction, true
}
return nil, false
}
@@ -588,11 +591,14 @@ func removeBalanceAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Ac
if !found {
return utils.ErrNotFound
}
- // update account in storage
- return accountingStorage.SetAccount(ub)
+ return nil
}
-func transferMonetaryDefault(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
+func setBalanceAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
+ return acc.setBalanceAction(a)
+}
+
+func transferMonetaryDefaultAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
if acc == nil {
utils.Logger.Err("*transfer_monetary_default called without account")
return utils.ErrAccountNotFound
@@ -612,8 +618,7 @@ func transferMonetaryDefault(acc *Account, sq *StatsQueueTriggered, a *Action, a
}
}
}
- // update account in storage
- return accountingStorage.SetAccount(acc)
+ return nil
}
// Structure to store actions according to weight
diff --git a/engine/actions_test.go b/engine/actions_test.go
index d2cd20538..662da3d65 100644
--- a/engine/actions_test.go
+++ b/engine/actions_test.go
@@ -1759,6 +1759,75 @@ func TestActionConditionalTopupExistingBalance(t *testing.T) {
}
}
+func TestActionSetBalance(t *testing.T) {
+ err := accountingStorage.SetAccount(
+ &Account{
+ Id: "cgrates.org:setb",
+ BalanceMap: map[string]BalanceChain{
+ utils.MONETARY: BalanceChain{
+ &Balance{
+ Id: "m1",
+ Uuid: utils.GenUUID(),
+ Value: 1,
+ Weight: 10,
+ },
+ &Balance{
+ Id: "m2",
+ Uuid: utils.GenUUID(),
+ Value: 6,
+ Weight: 20,
+ },
+ },
+ utils.VOICE: BalanceChain{
+ &Balance{
+ Id: "v1",
+ Uuid: utils.GenUUID(),
+ Value: 10,
+ Weight: 10,
+ },
+ &Balance{
+ Id: "v2",
+ Uuid: utils.GenUUID(),
+ Value: 100,
+ Weight: 20,
+ },
+ },
+ },
+ })
+ if err != nil {
+ t.Errorf("error setting account: %v", err)
+ }
+
+ a := &Action{
+ ActionType: SET_BALANCE,
+ BalanceType: utils.MONETARY,
+ Balance: &Balance{
+ Id: "m2",
+ Value: 11,
+ Weight: 10,
+ },
+ }
+
+ at := &ActionTiming{
+ accountIDs: utils.StringMap{"cgrates.org:setb": true},
+ actions: Actions{a},
+ }
+ at.Execute()
+
+ afterUb, err := accountingStorage.GetAccount("cgrates.org:setb")
+ if err != nil {
+ t.Error("account not found: ", err, afterUb)
+ }
+ if len(afterUb.BalanceMap[utils.MONETARY]) != 2 ||
+ afterUb.BalanceMap[utils.MONETARY][1].Value != 11 ||
+ afterUb.BalanceMap[utils.MONETARY][1].Weight != 10 {
+ for _, b := range afterUb.BalanceMap[utils.MONETARY] {
+ t.Logf("B: %+v", b)
+ }
+ t.Errorf("Balance: %+v", afterUb.BalanceMap[utils.MONETARY][1])
+ }
+}
+
/**************** Benchmarks ********************************/
func BenchmarkUUID(b *testing.B) {