diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index f4b1a1fff..c80e6ff00 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -385,6 +385,7 @@ type AttrAddBalance struct { RatingSubject string Categories string DestinationIds string + TimingIds string Weight float64 SharedGroups string Overwrite bool // When true it will reset if the balance is already there @@ -434,6 +435,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { Categories: utils.ParseStringMap(attr.Categories), Weight: attr.Weight, SharedGroups: utils.ParseStringMap(attr.SharedGroups), + TimingIDs: utils.ParseStringMap(attr.TimingIds), Blocker: attr.Blocker, Disabled: attr.Disabled, }, @@ -478,6 +480,7 @@ func (self *ApierV1) EnableDisableBalance(attr *AttrAddBalance, reply *string) e DestinationIds: utils.ParseStringMap(attr.DestinationIds), Weight: attr.Weight, SharedGroups: utils.ParseStringMap(attr.SharedGroups), + TimingIDs: utils.ParseStringMap(attr.TimingIds), Blocker: attr.Blocker, Disabled: attr.Disabled, }, @@ -522,6 +525,7 @@ func (self *ApierV1) RemoveBalances(attr *AttrAddBalance, reply *string) error { Categories: utils.ParseStringMap(attr.Categories), Weight: attr.Weight, SharedGroups: utils.ParseStringMap(attr.SharedGroups), + TimingIDs: utils.ParseStringMap(attr.TimingIds), Blocker: attr.Blocker, Disabled: attr.Disabled, }, @@ -535,6 +539,65 @@ func (self *ApierV1) RemoveBalances(attr *AttrAddBalance, reply *string) error { return nil } +type AttrSetBalance struct { + Tenant string + Account string + BalanceType string + BalanceUuid *string + BalanceId *string + Directions *string + Value *float64 + ExpiryTime *string + RatingSubject *string + Categories *string + DestinationIds *string + SharedGroups *string + TimingIds *string + Weight *float64 + Blocker *bool + Disabled *bool + expTime time.Time +} + +func (attr *AttrSetBalance) SetBalance(b *engine.Balance) { + if attr.Directions != nil { + b.Directions = utils.ParseStringMap(*attr.Directions) + } + if attr.Value != nil { + b.Value = *attr.Value + } + if attr.ExpiryTime != nil { + b.ExpirationDate = attr.expTime + } + if attr.RatingSubject != nil { + b.RatingSubject = *attr.RatingSubject + } + if attr.Categories != nil { + b.Categories = utils.ParseStringMap(*attr.Categories) + } + if attr.DestinationIds != nil { + b.DestinationIds = utils.ParseStringMap(*attr.DestinationIds) + } + if attr.SharedGroups != nil { + b.SharedGroups = utils.ParseStringMap(*attr.SharedGroups) + } + if attr.TimingIds != nil { + b.TimingIDs = utils.ParseStringMap(*attr.TimingIds) + } + if attr.Weight != nil { + b.Weight = *attr.Weight + } + if attr.Blocker != nil { + b.Blocker = *attr.Blocker + } + if attr.Disabled != nil { + b.Disabled = *attr.Disabled + } + b.SetDirty() // Mark the balance as dirty since we have modified and it should be checked by action triggers +} + +/* // SetAccount api using action and action timing to set balance, +//to be uncommented when using pointers in action.balance 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...) @@ -551,6 +614,7 @@ func (self *ApierV1) SetBalance(attr *AttrAddBalance, reply *string) error { if _, err := self.AccountDb.GetAccount(accID); err != nil { return utils.ErrNotFound } + at := &engine.ActionTiming{} at.SetAccountIDs(utils.StringMap{accID: true}) @@ -569,6 +633,7 @@ func (self *ApierV1) SetBalance(attr *AttrAddBalance, reply *string) error { Categories: utils.ParseStringMap(attr.Categories), Weight: attr.Weight, SharedGroups: utils.ParseStringMap(attr.SharedGroups), + TimingIDs: utils.ParseStringMap(attr.TimingIds), Blocker: true, Disabled: attr.Disabled, }, @@ -581,3 +646,98 @@ func (self *ApierV1) SetBalance(attr *AttrAddBalance, reply *string) error { *reply = OK return nil } +*/ + +func (self *ApierV1) SetBalance(attr *AttrSetBalance, reply *string) error { + if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + if (attr.BalanceId == nil || *attr.BalanceId == "") && + (attr.BalanceUuid == nil || *attr.BalanceUuid == "") { + return utils.NewErrMandatoryIeMissing("BalanceId", "or", "BalanceUuid") + } + var err error + if attr.ExpiryTime != nil { + attr.expTime, err = utils.ParseTimeDetectLayout(*attr.ExpiryTime, self.Config.DefaultTimezone) + if err != nil { + *reply = err.Error() + return err + } + } + accID := utils.ConcatenatedKey(attr.Tenant, attr.Account) + _, err = engine.Guardian.Guard(func() (interface{}, error) { + account, err := self.AccountDb.GetAccount(accID) + if err != nil { + return 0, utils.ErrNotFound + } + + if account.BalanceMap == nil { + account.BalanceMap = make(map[string]engine.BalanceChain, 1) + } + var previousSharedGroups utils.StringMap // kept for comparison + var balance *engine.Balance + var found bool + for _, b := range account.BalanceMap[attr.BalanceType] { + if b.IsExpired() { + continue + } + if (attr.BalanceUuid != nil && b.Uuid == *attr.BalanceUuid) || + (attr.BalanceId != nil && b.Id == *attr.BalanceId) { + previousSharedGroups = b.SharedGroups + balance = b + found = true + break // only set one balance + } + } + + // if it is not found then we add it to the list + if balance == nil { + balance := &engine.Balance{} + balance.Uuid = utils.GenUUID() // alway overwrite the uuid for consistency + account.BalanceMap[attr.BalanceType] = append(account.BalanceMap[attr.BalanceType], balance) + } + + if attr.BalanceId != nil && *attr.BalanceId == utils.META_DEFAULT { + balance.Id = utils.META_DEFAULT + if attr.Value != nil { + balance.Value = *attr.Value + } + } else { + attr.SetBalance(balance) + } + + if !found || !previousSharedGroups.Equal(balance.SharedGroups) { + _, err = engine.Guardian.Guard(func() (interface{}, error) { + for sgId := range balance.SharedGroups { + // add shared group member + sg, err := self.RatingDb.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[account.Id]; !found { + // add member and save + if sg.MemberIds == nil { + sg.MemberIds = make(utils.StringMap) + } + sg.MemberIds[account.Id] = true + self.RatingDb.SetSharedGroup(sg) + } + } + } + return 0, nil + }, 0, balance.SharedGroups.Slice()...) + } + + account.InitCounters() + account.ExecuteActionTriggers(nil) + self.AccountDb.SetAccount(account) + return 0, nil + }, 0, accID) + if err != nil { + *reply = err.Error() + return err + } + *reply = utils.OK + return nil +} diff --git a/console/balance_set.go b/console/balance_set.go index eee047382..bc52e8489 100644 --- a/console/balance_set.go +++ b/console/balance_set.go @@ -36,7 +36,7 @@ func init() { type CmdSetBalance struct { name string rpcMethod string - rpcParams *v1.AttrAddBalance + rpcParams *v1.AttrSetBalance *CommandExecuter } @@ -50,7 +50,7 @@ func (self *CmdSetBalance) RpcMethod() string { func (self *CmdSetBalance) RpcParams(reset bool) interface{} { if reset || self.rpcParams == nil { - self.rpcParams = &v1.AttrAddBalance{BalanceType: utils.MONETARY, Overwrite: false} + self.rpcParams = &v1.AttrSetBalance{BalanceType: utils.MONETARY} } return self.rpcParams } diff --git a/engine/account.go b/engine/account.go index 9044639b1..339ca6c4c 100644 --- a/engine/account.go +++ b/engine/account.go @@ -122,7 +122,11 @@ func (ub *Account) setBalanceAction(a *Action) error { bClone.Id = b.Id bClone.Uuid = b.Uuid previousSharedGroups = b.SharedGroups - *b = *bClone + if bClone.Id != utils.META_DEFAULT { + *b = *bClone + } else { + b.Value = bClone.GetValue() + } found = true break // only set one balance } @@ -141,27 +145,34 @@ func (ub *Account) setBalanceAction(a *Action) error { 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) + _, err := Guardian.Guard(func() (interface{}, error) { + for sgId := range bClone.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) } } + return 0, nil + }, 0, bClone.SharedGroups.Slice()...) + if err != nil { + return err } } ub.InitCounters() - ub.executeActionTriggers(nil) + ub.ExecuteActionTriggers(nil) return nil } @@ -222,26 +233,31 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { } } 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) + _, err := Guardian.Guard(func() (interface{}, error) { + for sgId := range bClone.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) } } + return 0, nil + }, 0, bClone.SharedGroups.Slice()...) + if err != nil { + return err } } - ub.executeActionTriggers(nil) + ub.ExecuteActionTriggers(nil) return nil } @@ -552,7 +568,7 @@ func (ub *Account) refundIncrement(increment *Increment, cd *CallDescriptor, cou } // Scans the action trigers and execute the actions for which trigger is met -func (ub *Account) executeActionTriggers(a *Action) { +func (ub *Account) ExecuteActionTriggers(a *Action) { ub.ActionTriggers.Sort() for _, at := range ub.ActionTriggers { // sanity check @@ -618,7 +634,7 @@ func (acc *Account) ResetActionTriggers(a *Action) { } at.Executed = false } - acc.executeActionTriggers(a) + acc.ExecuteActionTriggers(a) } // Sets/Unsets recurrent flag for action triggers @@ -634,7 +650,7 @@ func (acc *Account) SetRecurrent(a *Action, recurrent bool) { // Increments the counter for the type func (acc *Account) countUnits(amount float64, kind string, cc *CallCost, b *Balance) { acc.UnitCounters.addUnits(amount, kind, cc, b) - acc.executeActionTriggers(nil) + acc.ExecuteActionTriggers(nil) } // Create counters for all triggered actions diff --git a/engine/account_test.go b/engine/account_test.go index 334836999..577dfc0b8 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -976,7 +976,7 @@ func TestAccountExpActionTrigger(t *testing.T) { &ActionTrigger{ID: "check expired balances", BalanceType: utils.MONETARY, BalanceDirections: utils.StringMap{utils.OUT: true}, ThresholdValue: 10, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsId: "TEST_ACTIONS"}, }, } - ub.executeActionTriggers(nil) + ub.ExecuteActionTriggers(nil) if ub.BalanceMap[utils.MONETARY][0].IsExpired() || ub.BalanceMap[utils.MONETARY][0].GetValue() != 10 || // expired was cleaned ub.BalanceMap[utils.VOICE][0].GetValue() != 20 || diff --git a/engine/action.go b/engine/action.go index b579482fc..d5af34654 100644 --- a/engine/action.go +++ b/engine/action.go @@ -57,7 +57,7 @@ const ( DENY_NEGATIVE = "*deny_negative" RESET_ACCOUNT = "*reset_account" REMOVE_ACCOUNT = "*remove_account" - SET_BALANCE = "*set_balance" + SET_BALANCE = "*set_balance" // not ready for production until switching to pointers REMOVE_BALANCE = "*remove_balance" TOPUP_RESET = "*topup_reset" TOPUP = "*topup" diff --git a/engine/balances.go b/engine/balances.go index bc0ad33f8..e1aa16777 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -359,6 +359,10 @@ func (b *Balance) SetValue(amount float64) { b.dirty = true } +func (b *Balance) SetDirty() { + b.dirty = true +} + func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances BalanceChain, count bool, dryRun, debitConnectFee bool) (cc *CallCost, err error) { if !b.IsActiveAt(cd.TimeStart) || b.GetValue() <= 0 { return diff --git a/utils/map.go b/utils/map.go index 6fa89fe0f..6756488b8 100644 --- a/utils/map.go +++ b/utils/map.go @@ -86,6 +86,9 @@ func ParseStringMap(s string) StringMap { } func (sm StringMap) Equal(om StringMap) bool { + if sm == nil && om != nil { + return false + } if len(sm) != len(om) { return false }