diff --git a/engine/account.go b/engine/account.go index 20f5b9d33..5a6a06729 100644 --- a/engine/account.go +++ b/engine/account.go @@ -197,7 +197,6 @@ func (ub *Account) getBalancesForPrefix(prefix string, balances BalanceChain, sh func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) { usefulMinuteBalances := ub.getBalancesForPrefix(cc.Destination, ub.BalanceMap[MINUTES+cc.Direction], "") usefulMoneyBalances := ub.getBalancesForPrefix(cc.Destination, ub.BalanceMap[CREDIT+cc.Direction], "") - defaultMoneyBalance := ub.GetDefaultMoneyBalance(cc.Direction) // debit minutes for _, balance := range usefulMinuteBalances { if balance.SharedGroup != "" { @@ -221,7 +220,6 @@ func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) { } } } - // debit money for _, balance := range usefulMoneyBalances { if balance.SharedGroup != "" { @@ -233,7 +231,7 @@ func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) { goto CONNECT_FEE } } - // get the highest priority money balanance + // get the default money balanance // and go negative on it with the amount still unpaid for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ { ts := cc.Timespans[tsIndex] @@ -251,7 +249,7 @@ func (ub *Account) debitCreditBalance(cc *CallCost, count bool) (err error) { } for _, increment := range ts.Increments { cost := increment.Cost - defaultMoneyBalance.Value -= cost + ub.GetDefaultMoneyBalance(cc.Direction).Value -= cost if count { ub.countUnits(&Action{BalanceType: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: cost, DestinationId: cc.Destination}}) } @@ -278,7 +276,7 @@ CONNECT_FEE: // debit connect fee if connectFee > 0 && !connectFeePaid { // there are no money for the connect fee; go negative - defaultMoneyBalance.Value -= connectFee + ub.GetDefaultMoneyBalance(cc.Direction).Value -= connectFee // the conect fee is not refundable! if count { ub.countUnits(&Action{BalanceType: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: connectFee, DestinationId: cc.Destination}}) @@ -336,9 +334,8 @@ func (ub *Account) GetDefaultMoneyBalance(direction string) *Balance { return balance } } - // create default balance - defaultBalance := &Balance{Weight: 999} + defaultBalance := &Balance{Weight: 0} // minimum weight ub.BalanceMap[CREDIT+direction] = append(ub.BalanceMap[CREDIT+direction], defaultBalance) return defaultBalance } diff --git a/engine/accountlock.go b/engine/accountlock.go index 3a7da3d6f..ea786132d 100644 --- a/engine/accountlock.go +++ b/engine/accountlock.go @@ -81,7 +81,6 @@ func (cm *AccountLock) GuardMany(names []string, handler func() (float64, error) cm.Unlock() } lock <- true - reply, err = handler() } reply, err = handler() for _, name := range names { diff --git a/engine/action.go b/engine/action.go index 95d54f5cf..c0dc08373 100644 --- a/engine/action.go +++ b/engine/action.go @@ -166,7 +166,7 @@ func resetCountersAction(ub *Account, a *Action) (err error) { } func genericMakeNegative(a *Action) { - if a.Balance != nil && a.Balance.Value > 0 { // only apply if not allready negative + if a.Balance != nil && a.Balance.Value >= 0 { // only apply if not allready negative a.Balance.Value = -a.Balance.Value } } diff --git a/engine/balances.go b/engine/balances.go index ccdadfdd8..28944600b 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -393,8 +393,9 @@ func (bc BalanceChain) Swap(i, j int) { } func (bc BalanceChain) Less(j, i int) bool { - return bc[i].Weight < bc[j].Weight || - bc[i].precision < bc[j].precision + return bc[i].precision < bc[j].precision || + (bc[i].precision == bc[j].precision && bc[i].Weight < bc[j].Weight) + } func (bc BalanceChain) Sort() { diff --git a/engine/balances_test.go b/engine/balances_test.go index f929834a5..8423652d1 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -23,7 +23,7 @@ import ( "testing" ) -func TestBalanceSortWeight(t *testing.T) { +func TestBalanceSortPrecision(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 2} mb2 := &Balance{Weight: 2, precision: 1} var bs BalanceChain @@ -34,7 +34,7 @@ func TestBalanceSortWeight(t *testing.T) { } } -func TestBalanceSortPrecision(t *testing.T) { +func TestBalanceSortPrecisionWeightEqual(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 2} mb2 := &Balance{Weight: 1, precision: 1} var bs BalanceChain @@ -45,6 +45,39 @@ func TestBalanceSortPrecision(t *testing.T) { } } +func TestBalanceSortPrecisionWeightGreater(t *testing.T) { + mb1 := &Balance{Weight: 2, precision: 2} + mb2 := &Balance{Weight: 1, precision: 1} + var bs BalanceChain + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb1 || bs[1] != mb2 { + t.Error("Buckets not sorted by precision!") + } +} + +func TestBalanceSortWeight(t *testing.T) { + mb1 := &Balance{Weight: 2, precision: 1} + mb2 := &Balance{Weight: 1, precision: 1} + var bs BalanceChain + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb1 || bs[1] != mb2 { + t.Error("Buckets not sorted by precision!") + } +} + +func TestBalanceSortWeightLess(t *testing.T) { + mb1 := &Balance{Weight: 1, precision: 1} + mb2 := &Balance{Weight: 2, precision: 1} + var bs BalanceChain + bs = append(bs, mb2, mb1) + bs.Sort() + if bs[0] != mb2 || bs[1] != mb1 { + t.Error("Buckets not sorted by precision!") + } +} + func TestBalanceEqual(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} mb2 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} diff --git a/engine/calldesc.go b/engine/calldesc.go index 1d3d00bea..b06738222 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -21,6 +21,7 @@ package engine import ( "errors" "fmt" + "log" "log/syslog" "time" //"encoding/json" @@ -581,9 +582,11 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { cd.TimeEnd = cd.TimeStart.Add(remainingDuration) } cc, err = cd.debit(account) + //log.Print(balanceMap[0].Value, balanceMap[1].Value) return 0, err }) } else { + log.Print("cxcxxc") return nil, err } } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 1c9fd8280..37820d67e 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -374,8 +374,8 @@ func TestMaxSessionTimeWithAccountShared(t *testing.T) { result0, err := cd0.GetMaxSessionDuration() result1, err := cd1.GetMaxSessionDuration() - if result0 != result1 || err != nil { - t.Errorf("Expected %v was %v, %v", result1, result0, err) + if result0 != result1/2 || err != nil { + t.Errorf("Expected %v was %v, %v", result1/2, result0, err) } } @@ -475,6 +475,62 @@ func TestDebitAndMaxDebit(t *testing.T) { } } +func TestDebitFromShareAndNormal(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP_SHARED10_AT") + for _, at := range ap { + at.Execute() + } + + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "rif", + Account: "empty10", + Destination: "0723", + } + acc, _ := cd.getAccount() + balanceMap := acc.BalanceMap[CREDIT+OUTBOUND] + cc, err := cd.MaxDebit() + if err != nil || cc.Cost != 2.5 { + t.Errorf("Debit from share and normal error: %+v, %v", cc, err) + } + + if balanceMap[0].Value != 10 || balanceMap[1].Value != 7.5 { + t.Errorf("Error debiting from right balance: %v %v", balanceMap[0].Value, balanceMap[1].Value) + } +} + +func TestDebitFromEmptyShare(t *testing.T) { + ap, _ := accountingStorage.GetActionTimings("TOPUP_EMPTY_AT") + for _, at := range ap { + at.Execute() + } + + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC), + Direction: "*out", + TOR: "0", + Tenant: "vdf", + Subject: "rif", + Account: "emptyX", + Destination: "0723", + } + + cc, err := cd.MaxDebit() + if err != nil || cc.Cost != 2.5 { + t.Errorf("Debit from empty share error: %+v, %v", cc, err) + } + acc, _ := cd.getAccount() + balanceMap := acc.BalanceMap[CREDIT+OUTBOUND] + if len(balanceMap) != 2 || balanceMap[0].Value != 0 || balanceMap[1].Value != -2.5 { + t.Errorf("Error debiting from empty share: %+v %+v", balanceMap[0], balanceMap[1]) + } +} + func TestMaxDebitZeroDefinedRate(t *testing.T) { ap, _ := accountingStorage.GetActionTimings("TOPUP10_AT") for _, at := range ap { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 05b6e01b0..10d291bee 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -124,6 +124,7 @@ cgrates.directvoip.co.uk,call,*out,discounted_minutes,2013-01-06T00:00:00Z,RP_UK sharedGroups = ` SG1,*any,*lowest_first, SG2,*any,*lowest_first,EVENING +SG3,*any,*lowest_first, ` actions = ` @@ -133,7 +134,10 @@ SHARED,*topup,*monetary,*out,100,*unlimited,,,10,SG1,,10 TOPUP10_AC,*topup_reset,*monetary,*out,1,*unlimited,*any,,10,,,10 TOPUP10_AC1,*topup_reset,*minutes,*out,40,*unlimited,DST_UK_Mobile_BIG5,discounted_minutes,10,,,10 SE0,*topup_reset,*monetary,*out,0,*unlimited,,,10,SG2,,10 -SE10,*topup_reset,*monetary,*out,10,*unlimited,,,10,SG2,,10 +SE10,*topup_reset,*monetary,*out,10,*unlimited,,,5,SG2,,10 +SE10,*topup,*monetary,*out,10,*unlimited,,,10,,,10 +EE0,*topup_reset,*monetary,*out,0,*unlimited,,,10,SG3,,10 +EE0,*allow_negative,*monetary,*out,0,*unlimited,,,10,,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 @@ -142,6 +146,7 @@ TOPUP10_AT,TOPUP10_AC,ASAP,10 TOPUP10_AT,TOPUP10_AC1,ASAP,10 TOPUP_SHARED0_AT,SE0,ASAP,10 TOPUP_SHARED10_AT,SE10,ASAP,10 +TOPUP_EMPTY_AT,EE0,ASAP,10 ` actionTriggers = ` STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 @@ -155,6 +160,8 @@ vdf,minitsboy;a1;a2,*out,MORE_MINUTES,STANDARD_TRIGGER cgrates.directvoip.co.uk,12345,*out,TOPUP10_AT,STANDARD_TRIGGERS vdf,empty0,*out,TOPUP_SHARED0_AT, vdf,empty10,*out,TOPUP_SHARED10_AT, +vdf,emptyX,*out,TOPUP_EMPTY_AT, +vdf,emptyY,*out,TOPUP_EMPTY_AT, ` ) @@ -598,7 +605,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 6 { + if len(csvr.actions) != 7 { t.Error("Failed to load actions: ", csvr.actions) } as1 := csvr.actions["MINI"] @@ -660,7 +667,7 @@ func TestLoadActions(t *testing.T) { } func TestLoadSharedGroups(t *testing.T) { - if len(csvr.sharedGroups) != 2 { + if len(csvr.sharedGroups) != 3 { t.Error("Failed to load actions: ", csvr.sharedGroups) } @@ -707,7 +714,7 @@ func TestLoadSharedGroups(t *testing.T) { } func TestLoadActionTimings(t *testing.T) { - if len(csvr.actionsTimings) != 4 { + if len(csvr.actionsTimings) != 5 { t.Error("Failed to load action timings: ", csvr.actionsTimings) } atm := csvr.actionsTimings["MORE_MINUTES"][0] @@ -769,7 +776,7 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 4 { + if len(csvr.accountActions) != 6 { t.Error("Failed to load account actions: ", csvr.accountActions) } aa := csvr.accountActions["*out:vdf:minitsboy"]