Update *transfer_balance action

Now it creates the destination balance if it doesn't exist.
This commit is contained in:
ionutboangiu
2024-03-01 15:42:59 -05:00
committed by Dan Christian Bogos
parent f1ad73b902
commit 221f6e2c91
4 changed files with 118 additions and 31 deletions

View File

@@ -1205,16 +1205,17 @@ func (acc *Account) GetBalanceWithID(blcType, blcID string) (blc *Balance) {
return nil
}
// FindBalanceByID searches through all balance types for a balance with the specified ID and returns it.
func (acc *Account) FindBalanceByID(balanceID string) *Balance {
for _, balances := range acc.BalanceMap {
// FindBalanceByID searches through all balance types for a balance with the
// specified ID and returns it alongside its type.
func (acc *Account) FindBalanceByID(balanceID string) (blnc *Balance, blncType string) {
for balanceType, balances := range acc.BalanceMap {
for _, balance := range balances {
if balance.ID == balanceID {
return balance
return balance, balanceType
}
}
}
return nil
return nil, ""
}
// FieldAsInterface func to help EventCost FieldAsInterface

View File

@@ -159,7 +159,7 @@ func transferBalanceAction(srcAcc *Account, act *Action, _ Actions, fltrS *Filte
return fmt.Errorf("account %s has no balances to transfer from", srcAcc.ID)
}
srcBalance := srcAcc.FindBalanceByID(*act.Balance.ID)
srcBalance, srcBalanceType := srcAcc.FindBalanceByID(*act.Balance.ID)
if srcBalance == nil || srcBalance.IsExpiredAt(time.Now()) {
return errors.New("source balance not found or expired")
}
@@ -180,16 +180,34 @@ func transferBalanceAction(srcAcc *Account, act *Action, _ Actions, fltrS *Filte
return err
}
// This guard is meant to lock the destination account as we are making changes to it. It was not needed
// for the source account due to it being locked from outside this function.
// This guard is meant to lock the destination account as we are making changes
// to it. It was not needed for the source account due to it being locked from
// outside this function.
guardErr := guardian.Guardian.Guard(func() error {
destAcc, err := dm.GetAccount(accDestInfo.DestinationAccountID)
if err != nil {
return fmt.Errorf("retrieving destination account failed: %w", err)
}
destBalance := destAcc.FindBalanceByID(accDestInfo.DestinationBalanceID)
if destBalance == nil || destBalance.IsExpiredAt(time.Now()) {
return errors.New("destination balance not found or expired")
if destAcc.BalanceMap == nil {
destAcc.BalanceMap = make(map[string]Balances)
}
// We look for the destination balance only through balances of the same
// type as the source balance.
destBalance := destAcc.GetBalanceWithID(srcBalanceType, accDestInfo.DestinationBalanceID)
if destBalance != nil && destBalance.IsExpiredAt(time.Now()) {
return errors.New("destination balance expired")
}
if destBalance == nil {
// Destination Balance was not found. It will be
// created and added to the balance map.
destBalance = &Balance{
ID: accDestInfo.DestinationBalanceID,
Uuid: utils.GenUUID(),
}
destAcc.BalanceMap[srcBalanceType] = append(destAcc.BalanceMap[srcBalanceType], destBalance)
}
srcBalance.SubtractValue(transferUnits)
@@ -204,12 +222,14 @@ func transferBalanceAction(srcAcc *Account, act *Action, _ Actions, fltrS *Filte
return fmt.Errorf("updating destination account failed: %w", err)
}
return nil
}, config.CgrConfig().GeneralCfg().LockingTimeout, utils.AccountPrefix+accDestInfo.DestinationAccountID)
}, config.CgrConfig().GeneralCfg().LockingTimeout,
utils.AccountPrefix+accDestInfo.DestinationAccountID)
if guardErr != nil {
return guardErr
}
// Execute action triggers for the source account. This account will be updated in the parent function.
// Execute action triggers for the source account.
// This account will be updated in the parent function.
srcAcc.InitCounters()
srcAcc.ExecuteActionTriggers(act, fltrS)
return nil

View File

@@ -4424,7 +4424,8 @@ func TestActionsTransferBalance(t *testing.T) {
},
},
},
expectedErr: "destination balance not found or expired",
expectedSrcBalance: 7,
expectedDestBalance: 3,
},
{
name: "TransferUnitsNotSpecifiedOr0",

View File

@@ -181,7 +181,7 @@ ACT_TRANSFER,*transfer_balance,"{""DestinationAccountID"":""cgrates.org:ACC_DEST
}
})
t.Run("CheckBalancesAfterTransferBalanceAPI", func(t *testing.T) {
t.Run("CheckBalancesAfterTransferBalanceAPI1", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
@@ -211,6 +211,51 @@ ACT_TRANSFER,*transfer_balance,"{""DestinationAccountID"":""cgrates.org:ACC_DEST
}
})
t.Run("TransferBalanceByAPINonexistentDestBalance", func(t *testing.T) {
var reply string
if err := client.Call(context.Background(), utils.APIerSv1TransferBalance, utils.AttrTransferBalance{
Tenant: "cgrates.org",
SourceAccountID: "ACC_SRC",
SourceBalanceID: "balance_src",
DestinationAccountID: "ACC_DEST",
DestinationBalanceID: "nonexistent_balance",
Units: 3,
Cdrlog: true,
}, &reply); err != nil {
t.Error(err)
}
})
t.Run("CheckBalancesAfterTransferBalanceAPI2", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Error(err)
}
if len(acnts) != 2 {
t.Fatal("expecting 2 accounts to be retrieved")
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
if len(acnts[0].BalanceMap) != 1 || len(acnts[0].BalanceMap[utils.MetaMonetary]) != 1 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[0])
}
balance := acnts[0].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_src" || balance.Value != 1 {
t.Errorf("received account with unexpected balance: %v", balance)
}
if len(acnts[1].BalanceMap) != 1 || len(acnts[1].BalanceMap[utils.MetaMonetary]) != 2 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[1])
}
balance = acnts[1].BalanceMap[utils.MetaMonetary][1]
if balance.ID != "nonexistent_balance" || balance.Value != 3 {
t.Errorf("received account with unexpected balance: %v", balance)
}
})
t.Run("CheckTransferBalanceCDRs", func(t *testing.T) {
var cdrs []*engine.CDR
if err := client.Call(context.Background(), utils.CDRsV1GetCDRs, &utils.RPCCDRsFilterWithAPIOpts{
@@ -220,25 +265,45 @@ ACT_TRANSFER,*transfer_balance,"{""DestinationAccountID"":""cgrates.org:ACC_DEST
t.Fatal(err)
}
if len(cdrs) != 2 {
if len(cdrs) != 3 {
t.Errorf("expected to receive 2 cdrs: %v", utils.ToJSON(cdrs))
}
expectedCost := 2. // expected cost of first cdr
for _, cdr := range cdrs {
if cdr.Account != "ACC_SRC" ||
cdr.Destination != "ACC_DEST" ||
cdr.RunID != utils.MetaTransferBalance ||
cdr.Source != utils.CDRLog ||
cdr.ToR != utils.MetaVoice ||
cdr.ExtraFields["DestinationBalanceID"] != "balance_dest" ||
cdr.ExtraFields["SourceBalanceID"] != "balance_src" {
t.Errorf("unexpected cdr received: %v", utils.ToJSON(cdr))
}
if cdr.Cost != expectedCost {
t.Errorf("cost expected to be %v, received %v", expectedCost, cdr.Cost)
}
expectedCost = 4 // expected cost of second cdr
if cdrs[0].Account != "ACC_SRC" ||
cdrs[0].Destination != "ACC_DEST" ||
cdrs[0].RunID != utils.MetaTransferBalance ||
cdrs[0].Source != utils.CDRLog ||
cdrs[0].ToR != utils.MetaVoice ||
cdrs[0].ExtraFields["DestinationBalanceID"] != "balance_dest" ||
cdrs[0].ExtraFields["SourceBalanceID"] != "balance_src" {
t.Errorf("unexpected cdr received: %v", utils.ToJSON(cdrs[0]))
}
if cdrs[0].Cost != 2 {
t.Errorf("cost expected to be %v, received %v", 2, cdrs[0].Cost)
}
if cdrs[1].Account != "ACC_SRC" ||
cdrs[1].Destination != "ACC_DEST" ||
cdrs[1].RunID != utils.MetaTransferBalance ||
cdrs[1].Source != utils.CDRLog ||
cdrs[1].ToR != utils.MetaVoice ||
cdrs[1].ExtraFields["DestinationBalanceID"] != "nonexistent_balance" ||
cdrs[1].ExtraFields["SourceBalanceID"] != "balance_src" {
t.Errorf("unexpected cdr received: %v", utils.ToJSON(cdrs[1]))
}
if cdrs[1].Cost != 3 {
t.Errorf("cost expected to be %v, received %v", 2, cdrs[1].Cost)
}
if cdrs[2].Account != "ACC_SRC" ||
cdrs[2].Destination != "ACC_DEST" ||
cdrs[2].RunID != utils.MetaTransferBalance ||
cdrs[2].Source != utils.CDRLog ||
cdrs[2].ToR != utils.MetaVoice ||
cdrs[2].ExtraFields["DestinationBalanceID"] != "balance_dest" ||
cdrs[2].ExtraFields["SourceBalanceID"] != "balance_src" {
t.Errorf("unexpected cdr received: %v", utils.ToJSON(cdrs[2]))
}
if cdrs[2].Cost != 4 {
t.Errorf("cost expected to be %v, received %v", 2, cdrs[2].Cost)
}
})
}