From 20f1bf27c3696d4875354d4cc45e253b1dbcce57 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 28 Aug 2013 18:02:54 +0300 Subject: [PATCH] debit minutes even if balance goes negative --- docs/ratinglogic.rst | 13 ++++++++----- engine/userbalance.go | 24 +++++------------------- engine/userbalance_test.go | 5 ++++- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/docs/ratinglogic.rst b/docs/ratinglogic.rst index 711c3daf2..9470b55d3 100644 --- a/docs/ratinglogic.rst +++ b/docs/ratinglogic.rst @@ -94,7 +94,7 @@ The result of this splitting will be a list of *TimeSpan* structures each having 6.2.1 User balances ------------------- -The other functions relay on a user budget structure to manage the different quotas for postpaid and prepaid clients. The UserBudget keeps track of user monetary balance, free SMS and minutes for every destination, Internet traffic and offers the volume discount and received call bonus. +The user account contains a map of various balances like money, sms, internet traffic, internet time, etc. Each of these lists contains one or more Balance structure that have a wheight and a possible expiration date. :: @@ -112,14 +112,17 @@ The other functions relay on a user budget structure to manage the different quo Weight } +CGRateS treats special priced or free minutes different from the rest of balances. They will be called free minutes further on but they can have a special price. -Let's take them one by one. +The free minutes must be handled a little differently because usually they are grouped by specific destinations (e.g. national minutes, ore minutes in the same network). So they are grouped in buckets and when a call is made the engine checks all applicable buckets to consume minutes according to that call. + +When a call cost needs to be debited these minute buckets will be queried for call destination first. If the user has special minutes for the specific destination those minutes will be consumed according to call duration. + +A standard debit operation consist of selecting a certaing balance type and taking all balances from that list in the weight order to be debited till the total amount is consumed. CGRateS provide api for adding/substracting user's money credit. The prepaid and postpaid are uniformly treated except that the prepaid is checked to be always greater than zero and the postpaid can go bellow zero. -Both prepaid and postpaid can have a limited number of free SMS and Internet traffic per month and this budget is replenished at regular intervals based on the user tariff plan or as the user buys more free SMS (for example). - -The free (or special price) minutes must be handled a little differently because usually they are grouped by specific destinations (e.g. national minutes, ore minutes in the same network). So they are grouped in buckets and when a call is made the engine checks all applicable buckets to consume minutes according to that call. +Both prepaid and postpaid can have a limited number of free SMS and Internet traffic per month and this budget is replenished at regular intervals based on the user tariff plan or as the user buys more free SMSs (for example). Another special feature allows user to get a better price as the call volume increases each month. This can be added on one ore more thresholds so the more he/she talks the cheaper the calls. diff --git a/engine/userbalance.go b/engine/userbalance.go index 42f2a49ca..d65846fd0 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -40,6 +40,10 @@ const ( MINUTES = "*minutes" ) +var ( + AMOUNT_TOO_BIG = errors.New("Amount excedes balance!") +) + /* Structure containing information about user's credit (minutes, cents, sms...).' */ @@ -128,15 +132,6 @@ func (bc BalanceChain) Equal(o BalanceChain) bool { return true } -/* -Error type for overflowed debit methods. -*/ -type AmountTooBig struct{} - -func (a AmountTooBig) Error() string { - return "Amount excedes balance!" -} - /* Returns user's available minutes for the specified destination */ @@ -207,12 +202,9 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count } avaliableNbSeconds, _, bucketList := ub.getSecondsForPrefix(prefix) if avaliableNbSeconds < amount { - return new(AmountTooBig) + return AMOUNT_TOO_BIG } credit := ub.BalanceMap[CREDIT+OUTBOUND] - // calculating money debit - // this is needed because if the credit is less then the amount needed to be debited - // we need to keep everything in place and return an error for _, mb := range bucketList { if mb.Seconds < amount { if mb.Price > 0 { // debit the money if the bucket has price @@ -224,12 +216,6 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count } break } - if credit.GetTotalValue() < 0 { - break - } - } - if credit.GetTotalValue() < 0 { - return new(AmountTooBig) } ub.BalanceMap[CREDIT+OUTBOUND] = credit // credit is > 0 diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 80763cee8..661d8a279 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -254,7 +254,10 @@ func TestDebitPriceMoreMinuteBalance(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) - if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { + if b2.Seconds != 75 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { + t.Log(b2.Seconds) + t.Log(b1.Seconds) + t.Log(err) t.Errorf("Expected %v was %v", -4, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } }