AccountSv1.RefundCharges API

This commit is contained in:
DanB
2022-01-17 17:45:18 +01:00
parent b229d5861b
commit 285b45ef8e
5 changed files with 117 additions and 4 deletions

View File

@@ -30,7 +30,8 @@ import (
)
// NewAccountS instantiates the AccountS
func NewAccountS(cfg *config.CGRConfig, fltrS *engine.FilterS, connMgr *engine.ConnManager, dm *engine.DataManager) *AccountS {
func NewAccountS(cfg *config.CGRConfig, fltrS *engine.FilterS,
connMgr *engine.ConnManager, dm *engine.DataManager) *AccountS {
return &AccountS{cfg, fltrS, connMgr, dm}
}
@@ -90,7 +91,7 @@ func (aS *AccountS) matchingAccountsForEvent(ctx *context.Context, tnt string, c
for _, acntID := range acntIDs {
var refID string
if lked {
refID = guardian.Guardian.GuardIDs("",
refID = guardian.Guardian.GuardIDs(utils.EmptyString,
aS.cfg.GeneralCfg().LockingTimeout,
utils.ConcatenatedKey(utils.CacheAccounts, tnt, acntID)) // RPC caching needs to be atomic
}
@@ -132,14 +133,16 @@ func (aS *AccountS) matchingAccountsForEvent(ctx *context.Context, tnt string, c
}
// accountsDebit will debit an usage out of multiple accounts
// concretes parameter limits the debits to concrete only balances
// store is used for simulate only or complete debit
func (aS *AccountS) accountsDebit(ctx *context.Context, acnts []*utils.AccountWithWeight,
cgrEv *utils.CGREvent, concretes, store bool) (ec *utils.EventCharges, err error) {
var usage *decimal.Big
var usage *decimal.Big // total event usage
if usage, err = engine.GetDecimalBigOpts(ctx, cgrEv.Tenant, cgrEv, aS.fltrS, aS.cfg.AccountSCfg().Opts.Usage,
config.AccountsUsageDftOpt, utils.OptsAccountsUsage, utils.MetaUsage); err != nil {
return
}
dbted := decimal.New(0, 0)
dbted := decimal.New(0, 0) // amount debited so far
acntBkps := make([]utils.AccountBalancesBackup, len(acnts))
for i, acnt := range acnts {
if usage.Cmp(decimal.New(0, 0)) == 0 {
@@ -235,6 +238,59 @@ func (aS *AccountS) accountDebit(ctx *context.Context, acnt *utils.Account, usag
return
}
// refundCharges implements the mechanism of refunding the charges into accounts
func (aS *AccountS) refundCharges(ctx *context.Context, tnt string, ecs *utils.EventCharges) (err error) {
acnts := make(utils.AccountsWithWeight, 0, len(ecs.Accounts))
acntsIdxed := make(map[string]*utils.Account) // so we can access Account easier
alteredAcnts := make(utils.StringSet) // hold here the list of modified accounts
for acntID := range ecs.Accounts {
refID := guardian.Guardian.GuardIDs(utils.EmptyString,
aS.cfg.GeneralCfg().LockingTimeout,
utils.ConcatenatedKey(utils.CacheAccounts, tnt, acntID))
var qAcnt *utils.Account
if qAcnt, err = aS.dm.GetAccount(ctx, tnt, acntID); err != nil {
guardian.Guardian.UnguardIDs(refID)
if err == utils.ErrNotFound { // Account was removed in the mean time
err = nil
continue
}
unlockAccounts(acnts) // in case of errors will not have unlocks in upper layers
return
}
acnts = append(acnts, &utils.AccountWithWeight{qAcnt, 0, refID})
acntsIdxed[acntID] = qAcnt
}
acntBkps := make([]utils.AccountBalancesBackup, len(acnts)) // so we can restore in case of issues
for i, acnt := range acnts {
acntBkps[i] = acnt.AccountBalancesBackup()
}
for _, chrg := range ecs.Charges {
acntChrg := ecs.Accounting[chrg.ChargingID]
refundUnitsOnAccount(
acntsIdxed[acntChrg.AccountID],
uncompressUnits(acntChrg.Units, chrg.CompressFactor),
ecs.Accounts[acntChrg.AccountID].Balances[acntChrg.BalanceID])
alteredAcnts.Add(acntChrg.AccountID)
for _, chrgID := range acntChrg.JoinedChargeIDs { // refund extra charges
extraChrg := ecs.Accounting[chrgID]
refundUnitsOnAccount(
acntsIdxed[extraChrg.AccountID],
uncompressUnits(extraChrg.Units, chrg.CompressFactor),
ecs.Accounts[acntChrg.AccountID].Balances[extraChrg.BalanceID])
alteredAcnts.Add(extraChrg.AccountID)
}
}
for acntID := range alteredAcnts {
if err = aS.dm.SetAccount(ctx, acntsIdxed[acntID], false); err != nil {
restoreAccounts(ctx, aS.dm, acnts, acntBkps)
return
}
}
unlockAccounts(acnts) // in case of errors will not have unlocks in upper layers
return
}
// V1AccountsForEvent returns the matching Accounts for Event
func (aS *AccountS) V1AccountsForEvent(ctx *context.Context, args *utils.CGREvent, aps *[]*utils.Account) (err error) {
var accIDs []string
@@ -379,6 +435,15 @@ func (aS *AccountS) V1DebitConcretes(ctx *context.Context, args *utils.CGREvent,
return
}
// V1RefundCharges will refund charges recorded inside EventCharges
func (aS *AccountS) V1RefundCharges(ctx *context.Context, args *utils.APIEventCharges, rply *string) (err error) {
if err = aS.refundCharges(ctx, args.Tenant, args.EventCharges); err != nil {
return
}
*rply = utils.OK
return
}
// V1ActionSetBalance performs an update for a specific balance in account
func (aS *AccountS) V1ActionSetBalance(ctx *context.Context, args *utils.ArgsActSetBalance, rply *string) (err error) {
if args.AccountID == utils.EmptyString {

View File

@@ -352,3 +352,27 @@ func unlockAccounts(acnts utils.AccountsWithWeight) {
guardian.Guardian.UnguardIDs(lkID)
}
}
// uncompressUnits returns the uncompressed value of the units if compressFactor is provided
func uncompressUnits(units *utils.Decimal, cmprsFctr int) (tU *utils.Decimal) {
tU = units
if cmprsFctr > 1 {
tU = &utils.Decimal{utils.MultiplyBig(tU.Big,
decimal.New(int64(cmprsFctr), 0))}
}
return
}
// refundUnitsOnAccount is responsible for returning the units back to the balance
// origBlnc is used for both it's ID as well as as a configuration backup in case when the balance is not longer present
func refundUnitsOnAccount(acnt *utils.Account, units *utils.Decimal, origBlnc *utils.Balance) {
if _, has := acnt.Balances[origBlnc.ID]; has {
acnt.Balances[origBlnc.ID].Units = &utils.Decimal{
utils.SumBig(
acnt.Balances[origBlnc.ID].Units.Big,
units.Big)}
} else {
acnt.Balances[origBlnc.ID] = origBlnc.Clone()
acnt.Balances[origBlnc.ID].Units = &utils.Decimal{utils.CloneDecimalBig(units.Big)}
}
}

View File

@@ -213,6 +213,12 @@ func (aSv1 *AccountSv1) DebitConcretes(ctx *context.Context, args *utils.CGREven
return aSv1.aS.V1DebitConcretes(ctx, args, eEc)
}
// RefundCharges will refund charges recorded inside EventCharges
func (aSv1 *AccountSv1) RefundCharges(ctx *context.Context,
args *utils.APIEventCharges, rply *string) (err error) {
return aSv1.aS.V1RefundCharges(ctx, args, rply)
}
// ActionSetBalance performs a set balance action
func (aSv1 *AccountSv1) ActionSetBalance(ctx *context.Context, args *utils.ArgsActSetBalance,
eEc *string) (err error) {

View File

@@ -462,6 +462,17 @@ func (apWws AccountsWithWeight) LockIDs() (lkIDs []string) {
return
}
// Account returns the Account object with ID
func (apWws AccountsWithWeight) Account(acntID string) (acnt *Account) {
for _, aWw := range apWws {
if aWw.Account.ID == acntID {
acnt = aWw.Account
break
}
}
return
}
// BalanceWithWeight attaches static Weight to Balance
type BalanceWithWeight struct {
*Balance

View File

@@ -287,3 +287,10 @@ func (ac *AccountCharge) equals(nAc *AccountCharge) (eq bool) {
}
return true
}
// APIEventCharges is used in APIs, ie: refundCharges
type APIEventCharges struct {
Tenant string
APIOpts map[string]interface{}
*EventCharges
}