first version of set balance api

This commit is contained in:
Radu Ioan Fericean
2016-01-22 20:50:01 +02:00
parent ccda93af48
commit fa1b866931
7 changed files with 337 additions and 64 deletions

View File

@@ -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,
},
},

View File

@@ -18,51 +18,48 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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
}

65
console/balance_remove.go Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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) {