diff --git a/engine/action.go b/engine/action.go index 719c7f916..4fb81d24a 100644 --- a/engine/action.go +++ b/engine/action.go @@ -85,6 +85,7 @@ const ( MetaPublishAccount = "*publish_account" MetaPublishBalance = "*publish_balance" MetaRemoveSessionCosts = "*remove_session_costs" + MetaRemoveExpired = "*remove_expired" ) func (a *Action) Clone() *Action { @@ -129,6 +130,7 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { utils.MetaAWSjsonMap: sendAWS, utils.MetaSQSjsonMap: sendSQS, MetaRemoveSessionCosts: removeSessionCosts, + MetaRemoveExpired: removeExpired, } f, exists := actionFuncMap[typ] return f, exists @@ -1000,3 +1002,28 @@ func removeSessionCosts(_ *Account, action *Action, _ Actions, _ interface{}) er } return cdrStorage.RemoveSMCosts(smcFilter) } + +func removeExpired(acc *Account, action *Action, _ Actions, extraData interface{}) error { + if acc == nil { + return fmt.Errorf("nil account for %s action", utils.ToJSON(action)) + } + if _, exists := acc.BalanceMap[action.Balance.GetType()]; !exists { + return utils.ErrNotFound + } + bChain := acc.BalanceMap[action.Balance.GetType()] + found := false + for i := 0; i < len(bChain); i++ { + if bChain[i].IsExpired() { + // delete without preserving order + bChain[i] = bChain[len(bChain)-1] + bChain = bChain[:len(bChain)-1] + i -= 1 + found = true + } + } + acc.BalanceMap[action.Balance.GetType()] = bChain + if !found { + return utils.ErrNotFound + } + return nil +} diff --git a/engine/actions_test.go b/engine/actions_test.go index d508f77b6..73dacccd5 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -1734,6 +1734,59 @@ func TestActionRemoveBalance(t *testing.T) { } } +func TestActionRemoveExpiredBalance(t *testing.T) { + err := dm.DataDB().SetAccount(&Account{ + ID: "cgrates.org:rembal2", + BalanceMap: map[string]Balances{ + utils.MONETARY: Balances{ + &Balance{ + Value: 10, + }, + &Balance{ + Value: 10, + DestinationIDs: utils.NewStringMap("NAT", "RET"), + ExpirationDate: time.Date(2025, time.November, 11, 22, 39, 0, 0, time.UTC), + }, + &Balance{ + Value: 10, + DestinationIDs: utils.NewStringMap("NAT", "RET"), + ExpirationDate: time.Date(2010, time.November, 11, 22, 39, 0, 0, time.UTC), + }, + &Balance{ + Value: 10, + DestinationIDs: utils.NewStringMap("NAT", "RET"), + ExpirationDate: time.Date(2012, time.November, 11, 22, 39, 0, 0, time.UTC), + }, + }, + }, + Disabled: true, + }) + if err != nil { + t.Error("Error setting account: ", err) + } + at := &ActionTiming{ + accountIDs: utils.StringMap{"cgrates.org:rembal2": true}, + Timing: &RateInterval{}, + actions: []*Action{ + &Action{ + ActionType: MetaRemoveExpired, + Balance: &BalanceFilter{ + Type: utils.StringPointer(utils.MONETARY), + }, + }, + }, + } + err = at.Execute(nil, nil) + acc, err := dm.DataDB().GetAccount("cgrates.org:rembal2") + if err != nil || acc == nil { + t.Errorf("Error getting account: %+v: %v", acc, err) + } + if len(acc.BalanceMap) != 1 || + len(acc.BalanceMap[utils.MONETARY]) != 2 { + t.Errorf("Error removing balance: %+v", utils.ToJSON(acc.BalanceMap[utils.MONETARY])) + } +} + func TestActionTransferMonetaryDefault(t *testing.T) { err := dm.DataDB().SetAccount( &Account{