diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 1f5c2ef35..dcdfeda45 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -556,6 +556,11 @@ func (self *ApierV1) GetActions(actsId string, reply *[]*utils.TPAction) error { act.RatingSubject = engAct.Balance.RatingSubject act.SharedGroups = engAct.Balance.SharedGroups.String() act.BalanceWeight = engAct.Balance.Weight + act.TimingTags = engAct.Balance.TimingIDs.String() + act.BalanceId = engAct.Balance.Id + act.Categories = engAct.Balance.Categories.String() + act.BalanceBlocker = engAct.Balance.Blocker + act.BalanceDisabled = engAct.Balance.Disabled } acts = append(acts, act) } @@ -658,71 +663,6 @@ func (self *ApierV1) GetActionPlan(attr AttrGetActionPlan, reply *[]*engine.Acti return nil } -type AttrAddActionTrigger struct { - ActionTriggersId string - ActionTriggersUniqueId string - Tenant string - Account string - ThresholdType string - ThresholdValue float64 - BalanceId string - BalanceType string - BalanceDirection string - BalanceDestinationIds string - BalanceRatingSubject string //ToDo - BalanceWeight float64 - BalanceExpiryTime string - BalanceSharedGroup string //ToDo - Weight float64 - ActionsId string -} - -func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error { - if attr.BalanceDirection == "" { - attr.BalanceDirection = utils.OUT - } - balExpiryTime, err := utils.ParseTimeDetectLayout(attr.BalanceExpiryTime, self.Config.DefaultTimezone) - if err != nil { - return utils.NewErrServerError(err) - } - at := &engine.ActionTrigger{ - ID: attr.ActionTriggersId, - UniqueID: attr.ActionTriggersUniqueId, - ThresholdType: attr.ThresholdType, - ThresholdValue: attr.ThresholdValue, - BalanceId: attr.BalanceId, - BalanceType: attr.BalanceType, - BalanceDirections: utils.ParseStringMap(attr.BalanceDirection), - BalanceDestinationIds: utils.ParseStringMap(attr.BalanceDestinationIds), - BalanceWeight: attr.BalanceWeight, - BalanceExpirationDate: balExpiryTime, - Weight: attr.Weight, - ActionsId: attr.ActionsId, - Executed: false, - } - - tag := utils.AccountKey(attr.Tenant, attr.Account) - _, err = engine.Guardian.Guard(func() (interface{}, error) { - userBalance, err := self.AccountDb.GetAccount(tag) - if err != nil { - return 0, err - } - - userBalance.ActionTriggers = append(userBalance.ActionTriggers, at) - - if err = self.AccountDb.SetAccount(userBalance); err != nil { - return 0, err - } - return 0, nil - }, 0, tag) - if err != nil { - *reply = err.Error() - return err - } - *reply = OK - return nil -} - type AttrResetTriggeredAction struct { Id string Tenant string diff --git a/apier/v1/triggers.go b/apier/v1/triggers.go index 571f4f44c..1ea6a0fae 100644 --- a/apier/v1/triggers.go +++ b/apier/v1/triggers.go @@ -5,30 +5,95 @@ import ( "github.com/cgrates/cgrates/utils" ) -type AttrSetActionTriggers struct { +type AttrAddActionTrigger struct { + ActionTriggersId string + ActionTriggersUniqueId string Tenant string Account string - ActionTriggersIds *[]string + ThresholdType string + ThresholdValue float64 + BalanceId string + BalanceType string + BalanceDirection string + BalanceDestinationIds string + BalanceRatingSubject string //ToDo + BalanceWeight float64 + BalanceExpiryTime string + BalanceSharedGroup string //ToDo + Weight float64 + ActionsId string +} + +func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error { + if attr.BalanceDirection == "" { + attr.BalanceDirection = utils.OUT + } + balExpiryTime, err := utils.ParseTimeDetectLayout(attr.BalanceExpiryTime, self.Config.DefaultTimezone) + if err != nil { + return utils.NewErrServerError(err) + } + at := &engine.ActionTrigger{ + ID: attr.ActionTriggersId, + UniqueID: attr.ActionTriggersUniqueId, + ThresholdType: attr.ThresholdType, + ThresholdValue: attr.ThresholdValue, + BalanceId: attr.BalanceId, + BalanceType: attr.BalanceType, + BalanceDirections: utils.ParseStringMap(attr.BalanceDirection), + BalanceDestinationIds: utils.ParseStringMap(attr.BalanceDestinationIds), + BalanceWeight: attr.BalanceWeight, + BalanceExpirationDate: balExpiryTime, + Weight: attr.Weight, + ActionsId: attr.ActionsId, + Executed: false, + } + + tag := utils.AccountKey(attr.Tenant, attr.Account) + _, err = engine.Guardian.Guard(func() (interface{}, error) { + userBalance, err := self.AccountDb.GetAccount(tag) + if err != nil { + return 0, err + } + + userBalance.ActionTriggers = append(userBalance.ActionTriggers, at) + + if err = self.AccountDb.SetAccount(userBalance); err != nil { + return 0, err + } + return 0, nil + }, 0, tag) + if err != nil { + *reply = err.Error() + return err + } + *reply = OK + return nil +} + +type AttrSetAccountActionTriggers struct { + Tenant string + Account string + ActionTriggersIDs *[]string ActionTriggerOverwrite bool } -func (self *ApierV1) SetActionTriggers(attr AttrSetActionTriggers, reply *string) error { +func (self *ApierV1) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } accID := utils.AccountKey(attr.Tenant, attr.Account) var account *engine.Account _, err := engine.Guardian.Guard(func() (interface{}, error) { - if acc, err := self.AccountDb.GetAccount(accID); err != nil { + if acc, err := self.AccountDb.GetAccount(accID); err == nil { account = acc } else { return 0, err } - if attr.ActionTriggersIds != nil { + if attr.ActionTriggersIDs != nil { if attr.ActionTriggerOverwrite { account.ActionTriggers = make(engine.ActionTriggers, 0) } - for _, actionTriggerID := range *attr.ActionTriggersIds { + for _, actionTriggerID := range *attr.ActionTriggersIDs { atrs, err := self.RatingDb.GetActionTriggers(actionTriggerID) if err != nil { @@ -49,6 +114,9 @@ func (self *ApierV1) SetActionTriggers(attr AttrSetActionTriggers, reply *string } } account.InitCounters() + if err := self.AccountDb.SetAccount(account); err != nil { + return 0, err + } return 0, nil }, 0, accID) if err != nil { @@ -59,21 +127,21 @@ func (self *ApierV1) SetActionTriggers(attr AttrSetActionTriggers, reply *string return nil } -type AttrRemoveActionTriggers struct { +type AttrRemoveAccountActionTriggers struct { Tenant string Account string GroupID string UniqueID string } -func (self *ApierV1) RemoveActionTriggers(attr AttrRemoveActionTriggers, reply *string) error { +func (self *ApierV1) RemoveAccountActionTriggers(attr AttrRemoveAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } accID := utils.AccountKey(attr.Tenant, attr.Account) - var account *engine.Account _, err := engine.Guardian.Guard(func() (interface{}, error) { - if acc, err := self.AccountDb.GetAccount(accID); err != nil { + var account *engine.Account + if acc, err := self.AccountDb.GetAccount(accID); err == nil { account = acc } else { return 0, err @@ -88,8 +156,10 @@ func (self *ApierV1) RemoveActionTriggers(attr AttrRemoveActionTriggers, reply * newActionTriggers = append(newActionTriggers, at) } account.ActionTriggers = newActionTriggers - account.InitCounters() + if err := self.AccountDb.SetAccount(account); err != nil { + return 0, err + } return 0, nil }, 0, accID) if err != nil { @@ -100,14 +170,7 @@ func (self *ApierV1) RemoveActionTriggers(attr AttrRemoveActionTriggers, reply * return nil } -type AttrResetActionTriggers struct { - Tenant string - Account string - GroupID string - UniqueID string -} - -func (self *ApierV1) ResetActionTriggers(attr AttrResetActionTriggers, reply *string) error { +func (self *ApierV1) ResetAccountActionTriggers(attr AttrRemoveAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) @@ -115,7 +178,7 @@ func (self *ApierV1) ResetActionTriggers(attr AttrResetActionTriggers, reply *st accID := utils.AccountKey(attr.Tenant, attr.Account) var account *engine.Account _, err := engine.Guardian.Guard(func() (interface{}, error) { - if acc, err := self.AccountDb.GetAccount(accID); err != nil { + if acc, err := self.AccountDb.GetAccount(accID); err == nil { account = acc } else { return 0, err @@ -129,6 +192,9 @@ func (self *ApierV1) ResetActionTriggers(attr AttrResetActionTriggers, reply *st } account.ExecuteActionTriggers(nil) + if err := self.AccountDb.SetAccount(account); err != nil { + return 0, err + } return 0, nil }, 0, accID) if err != nil { diff --git a/apier/v2/accounts.go b/apier/v2/accounts.go index e18c90eeb..b852b212d 100644 --- a/apier/v2/accounts.go +++ b/apier/v2/accounts.go @@ -81,9 +81,9 @@ func (self *ApierV2) GetAccount(attr *utils.AttrGetAccount, reply *engine.Accoun type AttrSetAccount struct { Tenant string Account string - ActionPlanIds *[]string + ActionPlanIDs *[]string ActionPlansOverwrite bool - ActionTriggersIds *[]string + ActionTriggerIDs *[]string ActionTriggerOverwrite bool AllowNegative *bool Disabled *bool @@ -105,7 +105,7 @@ func (self *ApierV2) SetAccount(attr AttrSetAccount, reply *string) error { Id: accID, } } - if attr.ActionPlanIds != nil { + if attr.ActionPlanIDs != nil { _, err := engine.Guardian.Guard(func() (interface{}, error) { actionPlansMap, err := self.RatingDb.GetAllActionPlans() if err != nil { @@ -121,7 +121,7 @@ func (self *ApierV2) SetAccount(attr AttrSetAccount, reply *string) error { } } - for _, actionPlanID := range *attr.ActionPlanIds { + for _, actionPlanID := range *attr.ActionPlanIDs { ap, ok := actionPlansMap[actionPlanID] if !ok { return 0, utils.ErrNotFound @@ -162,11 +162,11 @@ func (self *ApierV2) SetAccount(attr AttrSetAccount, reply *string) error { } } - if attr.ActionTriggersIds != nil { + if attr.ActionTriggerIDs != nil { if attr.ActionTriggerOverwrite { ub.ActionTriggers = make(engine.ActionTriggers, 0) } - for _, actionTriggerID := range *attr.ActionTriggersIds { + for _, actionTriggerID := range *attr.ActionTriggerIDs { atrs, err := self.RatingDb.GetActionTriggers(actionTriggerID) if err != nil { return 0, err diff --git a/console/trigger_remove.go b/console/trigger_remove.go new file mode 100644 index 000000000..a460d8528 --- /dev/null +++ b/console/trigger_remove.go @@ -0,0 +1,63 @@ +/* +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" + +func init() { + c := &CmdRemoveTriggers{ + name: "triggers_remove", + rpcMethod: "ApierV1.RemoveAccountActionTriggers", + rpcParams: &v1.AttrRemoveAccountActionTriggers{}, + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdRemoveTriggers struct { + name string + rpcMethod string + rpcParams *v1.AttrRemoveAccountActionTriggers + *CommandExecuter +} + +func (self *CmdRemoveTriggers) Name() string { + return self.name +} + +func (self *CmdRemoveTriggers) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdRemoveTriggers) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &v1.AttrRemoveAccountActionTriggers{} + } + return self.rpcParams +} + +func (self *CmdRemoveTriggers) PostprocessRpcParams() error { + return nil +} + +func (self *CmdRemoveTriggers) RpcResult() interface{} { + var s string + return &s +} diff --git a/console/trigger_reset.go b/console/trigger_reset.go new file mode 100644 index 000000000..0d5be9f24 --- /dev/null +++ b/console/trigger_reset.go @@ -0,0 +1,63 @@ +/* +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" + +func init() { + c := &CmdResetTriggers{ + name: "triggers_reset", + rpcMethod: "ApierV1.ResetAccountActionTriggers", + rpcParams: &v1.AttrRemoveAccountActionTriggers{}, + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdResetTriggers struct { + name string + rpcMethod string + rpcParams *v1.AttrRemoveAccountActionTriggers + *CommandExecuter +} + +func (self *CmdResetTriggers) Name() string { + return self.name +} + +func (self *CmdResetTriggers) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdResetTriggers) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &v1.AttrRemoveAccountActionTriggers{} + } + return self.rpcParams +} + +func (self *CmdResetTriggers) PostprocessRpcParams() error { + return nil +} + +func (self *CmdResetTriggers) RpcResult() interface{} { + var s string + return &s +} diff --git a/console/trigger_set.go b/console/trigger_set.go new file mode 100644 index 000000000..2e9095d95 --- /dev/null +++ b/console/trigger_set.go @@ -0,0 +1,63 @@ +/* +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" + +func init() { + c := &CmdSetTriggers{ + name: "triggers_set", + rpcMethod: "ApierV1.SetAccountActionTriggers", + rpcParams: &v1.AttrSetAccountActionTriggers{}, + } + commands[c.Name()] = c + c.CommandExecuter = &CommandExecuter{c} +} + +// Commander implementation +type CmdSetTriggers struct { + name string + rpcMethod string + rpcParams *v1.AttrSetAccountActionTriggers + *CommandExecuter +} + +func (self *CmdSetTriggers) Name() string { + return self.name +} + +func (self *CmdSetTriggers) RpcMethod() string { + return self.rpcMethod +} + +func (self *CmdSetTriggers) RpcParams(reset bool) interface{} { + if reset || self.rpcParams == nil { + self.rpcParams = &v1.AttrSetAccountActionTriggers{} + } + return self.rpcParams +} + +func (self *CmdSetTriggers) PostprocessRpcParams() error { + return nil +} + +func (self *CmdSetTriggers) RpcResult() interface{} { + var s string + return &s +} diff --git a/engine/action_plan.go b/engine/action_plan.go index a633c8386..f3d2b4563 100644 --- a/engine/action_plan.go +++ b/engine/action_plan.go @@ -294,6 +294,7 @@ func (at *ActionTiming) Execute() (err error) { // check action filter if len(a.Filter) > 0 { matched, err := ub.matchActionFilter(a.Filter) + //log.Print("Checkng: ", a.Filter, matched) if err != nil { return 0, err } diff --git a/engine/actions_test.go b/engine/actions_test.go index 1c6da1a7d..1eb5367d6 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -1759,6 +1759,144 @@ func TestActionConditionalTopupExistingBalance(t *testing.T) { } } +func TestActionConditionalDisabledIfNegative(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:af", + BalanceMap: map[string]BalanceChain{ + "*data": BalanceChain{ + &Balance{ + Uuid: "fc927edb-1bd6-425e-a2a3-9fd8bafaa524", + Id: "for_v3hsillmilld500m_data_500_m", + Value: 5.242, + Weight: 10, + RatingSubject: "for_v3hsillmilld500m_data_forfait", + Categories: utils.StringMap{ + "data_france": true, + }, + }, + }, + "*monetary": BalanceChain{ + &Balance{ + Uuid: "9fa1847a-f36a-41a7-8ec0-dfaab370141e", + Id: "*default", + Value: -1.95001, + }, + }, + "*sms": BalanceChain{ + &Balance{ + Uuid: "d348d15d-2988-4ee4-b847-6a552f94e2ec", + Id: "for_v3hsillmilld500m_mms_ill", + Value: 20000, + Weight: 10, + DestinationIds: utils.StringMap{ + "FRANCE_NATIONAL": true, + }, + Categories: utils.StringMap{ + "mms_france": true, + "tmms_france": true, + "vmms_france": true, + }, + }, + &Balance{ + Uuid: "f4643517-31f6-4199-980f-04cf535471ed", + Id: "for_v3hsillmilld500m_sms_ill", + Value: 20000, + Weight: 10, + DestinationIds: utils.StringMap{ + "FRANCE_NATIONAL": true, + }, + Categories: utils.StringMap{ + "ms_france": true, + }, + }, + }, + "*voice": BalanceChain{ + &Balance{ + Uuid: "079ab190-77f4-44f3-9c6f-3a0dd1a59dfd", + Id: "for_v3hsillmilld500m_voice_3_h", + Value: 10800, + Weight: 10, + DestinationIds: utils.StringMap{ + "FRANCE_NATIONAL": true, + }, + Categories: utils.StringMap{ + "call_france": true, + }, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a1 := &Action{ + ActionType: "*enable_disable_balance", + BalanceType: "*sms", + Filter: "{\"*and\":[{\"Value\":{\"*lt\":0}},{\"Id\":{\"*eq\":\"*default\"}}]}", + Balance: &Balance{ + Weight: 10, + Disabled: true, + }, + Weight: 9, + } + a2 := &Action{ + ActionType: "*enable_disable_balance", + BalanceType: "*sms", + Filter: "{\"*and\":[{\"Value\":{\"*lt\":0}},{\"Id\":{\"*eq\":\"*default\"}}]}", + Balance: &Balance{ + DestinationIds: utils.NewStringMap("FRANCE_NATIONAL"), + Weight: 10, + Disabled: true, + }, + Weight: 8, + } + a3 := &Action{ + ActionType: "*enable_disable_balance", + BalanceType: "*data", + Filter: "{\"*and\":[{\"Value\":{\"*lt\":0}},{\"Id\":{\"*eq\":\"*default\"}}]}", + Balance: &Balance{ + RatingSubject: "for_v3hsillmilld500m_data_forfait", + Weight: 10, + Disabled: true, + }, + Weight: 7, + } + a4 := &Action{ + ActionType: "*enable_disable_balance", + BalanceType: "*voice", + Filter: "{\"*and\":[{\"Value\":{\"*lt\":0}},{\"Id\":{\"*eq\":\"*default\"}}]}", + Balance: &Balance{ + DestinationIds: utils.NewStringMap("FRANCE_NATIONAL"), + Weight: 10, + Disabled: true, + }, + Weight: 6, + } + + at := &ActionTiming{ + accountIDs: utils.StringMap{"cgrates.org:af": true}, + actions: Actions{a1, a2, a3, a4}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:af") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + + for btype, chain := range afterUb.BalanceMap { + if btype != utils.MONETARY { + for _, b := range chain { + if b.Disabled != true { + t.Errorf("Failed to disabled balance (%s): %+v", btype, b) + } + } + } + } +} + func TestActionSetBalance(t *testing.T) { err := accountingStorage.SetAccount( &Account{