diff --git a/engine/account.go b/engine/account.go index 0aa4e5445..961cd471c 100644 --- a/engine/account.go +++ b/engine/account.go @@ -355,27 +355,25 @@ func (ub *Account) GetDefaultMoneyBalance(direction string) *Balance { return defaultBalance } -func (ub *Account) refundIncrements(increments Increments, direction string, count bool) { - for _, increment := range increments { - var balance *Balance - if increment.BalanceInfo.MinuteBalanceUuid != "" { - if balance = ub.BalanceMap[MINUTES+direction].GetBalance(increment.BalanceInfo.MinuteBalanceUuid); balance == nil { - continue - } - balance.Value += increment.Duration.Seconds() - if count { - ub.countUnits(&Action{BalanceType: MINUTES, Direction: direction, Balance: &Balance{Value: -increment.Duration.Seconds()}}) - } +func (ub *Account) refundIncrement(increment *Increment, direction string, count bool) { + var balance *Balance + if increment.BalanceInfo.MinuteBalanceUuid != "" { + if balance = ub.BalanceMap[MINUTES+direction].GetBalance(increment.BalanceInfo.MinuteBalanceUuid); balance == nil { + return } - // check money too - if increment.BalanceInfo.MoneyBalanceUuid != "" { - if balance = ub.BalanceMap[CREDIT+direction].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil { - continue - } - balance.Value += increment.Cost - if count { - ub.countUnits(&Action{BalanceType: CREDIT, Direction: direction, Balance: &Balance{Value: -increment.Cost}}) - } + balance.Value += increment.Duration.Seconds() + if count { + ub.countUnits(&Action{BalanceType: MINUTES, Direction: direction, Balance: &Balance{Value: -increment.Duration.Seconds()}}) + } + } + // check money too + if increment.BalanceInfo.MoneyBalanceUuid != "" { + if balance = ub.BalanceMap[CREDIT+direction].GetBalance(increment.BalanceInfo.MoneyBalanceUuid); balance == nil { + return + } + balance.Value += increment.Cost + if count { + ub.countUnits(&Action{BalanceType: CREDIT, Direction: direction, Balance: &Balance{Value: -increment.Cost}}) } } } diff --git a/engine/account_test.go b/engine/account_test.go index 88b79e836..9894129ff 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -21,6 +21,8 @@ package engine import ( "testing" "time" + + "github.com/cgrates/cgrates/cache2go" ) var ( @@ -1023,8 +1025,9 @@ func TestAccountRefund(t *testing.T) { &Increment{Cost: 2, Duration: 3 * time.Second, BalanceInfo: &BalanceInfo{MinuteBalanceUuid: "minutea", MoneyBalanceUuid: "moneya"}}, &Increment{Duration: 4 * time.Second, BalanceInfo: &BalanceInfo{MinuteBalanceUuid: "minuteb", MoneyBalanceUuid: ""}}, } - - ub.refundIncrements(increments, OUTBOUND, false) + for _, increment := range increments { + ub.refundIncrement(increment, OUTBOUND, false) + } if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 104 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 13 || ub.BalanceMap[MINUTES+OUTBOUND][1].Value != 14 { @@ -1033,7 +1036,62 @@ func TestAccountRefund(t *testing.T) { } func TestDebitShared(t *testing.T) { + cc := &CallCost{ + Tenant: "vdf", + TOR: "0", + Direction: OUTBOUND, + Destination: "0723045326", + Timespans: []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 24, 10, 49, 0, 0, time.UTC), + CallDuration: 55 * time.Second, + RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 2, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, + }, + }, + deductConnectFee: true, + } + rif := &Account{Id: "rif", BalanceMap: map[string]BalanceChain{ + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 60, SharedGroup: "SG_TEST"}}, + }} + groupie := &Account{Id: "groupie", BalanceMap: map[string]BalanceChain{ + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneyc", Value: 70, SharedGroup: "SG_TEST"}}, + }} + sg := &SharedGroup{Id: "SG_TEST", Members: []string{rif.Id, groupie.Id}, AccountParameters: map[string]*SharingParameters{"*any": &SharingParameters{Strategy: STRATEGY_RANDOM}}} + + accountingStorage.SetAccount(groupie) + accountingStorage.SetSharedGroup("SG_TEST", sg) + cache2go.Cache(SHARED_GROUP_PREFIX+"SG_TEST", sg) + err := rif.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if rif.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { + t.Errorf("Error debiting from shared group: %+v", rif.BalanceMap[CREDIT+OUTBOUND][0]) + } + groupie, _ = accountingStorage.GetAccount("groupie") + if groupie.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { + t.Errorf("Error debiting from shared group: %+v", groupie.BalanceMap[CREDIT+OUTBOUND][0]) + } + + if len(cc.Timespans) != 1 { + t.Errorf("Wrong number of timespans: %v", cc.Timespans) + } + if len(cc.Timespans[0].Increments) != 6 { + t.Errorf("Wrong number of increments: %v", cc.Timespans[0].Increments) + for index, incr := range cc.Timespans[0].Increments { + t.Errorf("I%d: %+v (%+v)", index, incr, incr.BalanceInfo) + } + } + if cc.Timespans[0].Increments[0].BalanceInfo.AccountId != "rif" || + cc.Timespans[0].Increments[1].BalanceInfo.AccountId != "rif" || + cc.Timespans[0].Increments[2].BalanceInfo.AccountId != "rif" || + cc.Timespans[0].Increments[3].BalanceInfo.AccountId != "groupie" || + cc.Timespans[0].Increments[4].BalanceInfo.AccountId != "groupie" || + cc.Timespans[0].Increments[5].BalanceInfo.AccountId != "groupie" { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } } /*********************************** Benchmarks *******************************/ diff --git a/engine/balances.go b/engine/balances.go index 2d726e3a6..57f64b098 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -180,6 +180,7 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan b.Value -= amount b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE) inc.BalanceInfo.MinuteBalanceUuid = b.Uuid + inc.BalanceInfo.AccountId = ub.Id inc.MinuteInfo = &MinuteInfo{cc.Destination, amount} inc.Cost = 0 inc.paid = true @@ -222,6 +223,7 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *Account, moneyBalan moneyBal.Value -= cost nInc.BalanceInfo.MinuteBalanceUuid = b.Uuid nInc.BalanceInfo.MoneyBalanceUuid = moneyBal.Uuid + nInc.BalanceInfo.AccountId = ub.Id nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds} nInc.paid = true if count { @@ -287,6 +289,7 @@ func (b *Balance) DebitMoney(cc *CallCost, count bool, ub *Account) error { b.Value -= amount b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE) increment.BalanceInfo.MoneyBalanceUuid = b.Uuid + increment.BalanceInfo.AccountId = ub.Id increment.paid = true if count { ub.countUnits(&Action{BalanceType: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) @@ -316,6 +319,7 @@ func (b *Balance) DebitMoney(cc *CallCost, count bool, ub *Account) error { b.Value -= amount b.Value = utils.Round(b.Value, roundingDecimals, utils.ROUNDING_MIDDLE) nInc.BalanceInfo.MoneyBalanceUuid = b.Uuid + nInc.BalanceInfo.AccountId = ub.Id nInc.paid = true if count { ub.countUnits(&Action{BalanceType: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}}) diff --git a/engine/calldesc.go b/engine/calldesc.go index 31b60480b..fbb70a735 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -546,9 +546,21 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { } func (cd *CallDescriptor) RefundIncrements() (left float64, err error) { + accountsCache := make(map[string]*Account) + for _, increment := range cd.Increments { + account, found := accountsCache[increment.BalanceInfo.AccountId] + if !found { + if acc, err := accountingStorage.GetAccount(increment.BalanceInfo.AccountId); err == nil && acc != nil { + account = acc + accountsCache[increment.BalanceInfo.AccountId] = account + defer accountingStorage.SetAccount(account) + } + } + account.refundIncrement(increment, cd.Direction, true) + } + if userBalance, err := cd.getAccount(); err == nil && userBalance != nil { - defer accountingStorage.SetAccount(userBalance) - userBalance.refundIncrements(cd.Increments, cd.Direction, true) + } return 0.0, err } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index d2e27c23b..1b9344657 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -633,10 +633,11 @@ func TestLoadActions(t *testing.T) { } } -func TestSharedGroups(t *testing.T) { +func TestLoadSharedGroups(t *testing.T) { if len(csvr.sharedGroups) != 2 { t.Error("Failed to load actions: ", csvr.sharedGroups) } + sg1 := csvr.sharedGroups["SG1"] expected := &SharedGroup{ Id: "SG1", @@ -663,6 +664,20 @@ func TestSharedGroups(t *testing.T) { if !reflect.DeepEqual(sg2, expected) { t.Error("Error loading shared group: ", sg2.AccountParameters) } + sg, _ := accountingStorage.GetSharedGroup("SG1", false) + if len(sg.Members) != 0 { + t.Errorf("Memebers should be empty: %+v", sg) + } + + // execute action timings to fill memebers + atm := csvr.actionsTimings["MORE_MINUTES"][1] + atm.Execute() + atm.actions, atm.stCache = nil, time.Time{} + + sg, _ = accountingStorage.GetSharedGroup("SG1", false) + if len(sg.Members) != 1 { + t.Errorf("Memebers should not be empty: %+v", sg) + } } func TestLoadActionTimings(t *testing.T) { @@ -687,7 +702,7 @@ func TestLoadActionTimings(t *testing.T) { ActionsId: "MINI", } if !reflect.DeepEqual(atm, expected) { - t.Error("Error loading action timing: ", atm, expected) + t.Errorf("Error loading action timing:\n%+v\n%+v", atm, expected) } } diff --git a/engine/sharedgroup.go b/engine/sharedgroup.go index de4bf738a..5643d2c8e 100644 --- a/engine/sharedgroup.go +++ b/engine/sharedgroup.go @@ -22,6 +22,8 @@ import ( "math" "math/rand" "time" + + "github.com/cgrates/cgrates/utils" ) const ( @@ -59,8 +61,15 @@ func (sg *SharedGroup) PopBalanceByStrategy(account string, balanceChain *Balanc return } index := 0 - sharingParameters := sg.AccountParameters[account] - switch sharingParameters.Strategy { + sharingParameters := sg.AccountParameters[utils.ANY] + if sp, hasParamsForAccount := sg.AccountParameters[account]; hasParamsForAccount { + sharingParameters = sp + } + strategy := STRATEGY_RANDOM + if sharingParameters != nil { + strategy = sharingParameters.Strategy + } + switch strategy { case STRATEGY_RANDOM: rand.Seed(time.Now().Unix()) index = rand.Intn(len(bc)) diff --git a/engine/storage_map.go b/engine/storage_map.go index a40161021..834447619 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -278,7 +278,7 @@ func (ms *MapStorage) GetSharedGroup(key string, checkDb bool) (sg *SharedGroup, func (ms *MapStorage) SetSharedGroup(key string, sg *SharedGroup) (err error) { result, err := ms.ms.Marshal(sg) ms.dict[SHARED_GROUP_PREFIX+key] = result - //cache2go.Cache(ACTION_PREFIX+key, sg) + //cache2go.Cache(SHARED_GROUP_PREFIX+key, sg) return } diff --git a/engine/timespans.go b/engine/timespans.go index 2712fee9f..1c5d528ff 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -59,7 +59,7 @@ type MinuteInfo struct { type BalanceInfo struct { MinuteBalanceUuid string MoneyBalanceUuid string - UserBalanceId string // used when debited from shared balance + AccountId string // used when debited from shared balance } type TimeSpans []*TimeSpan