refactored debit credit method, now modular and better tested

This commit is contained in:
Radu Ioan Fericean
2013-12-10 20:22:28 +02:00
parent 3611494936
commit 6ff0d17d5f
8 changed files with 346 additions and 252 deletions

View File

@@ -656,7 +656,7 @@ func TestActionTopupResetCredit(t *testing.T) {
len(ub.UnitCounters) != 1 ||
len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 ||
ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true {
t.Errorf("Topup reset action failed: %#v", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue())
t.Errorf("Topup reset action failed: %+v", ub.BalanceMap[CREDIT+OUTBOUND][0])
}
}
@@ -678,7 +678,7 @@ func TestActionTopupResetMinutes(t *testing.T) {
len(ub.UnitCounters) != 1 ||
len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 ||
ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true {
t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap[MINUTES+OUTBOUND][0])
t.Errorf("Topup reset minutes action failed: %+v", ub.BalanceMap[MINUTES+OUTBOUND][0])
}
}

View File

@@ -34,20 +34,29 @@ type Balance struct {
ExpirationDate time.Time
Weight float64
GroupIds []string
//SpecialPriceType string
//SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative)
DestinationId string
RateSubject string
precision int
DestinationId string
RateSubject string
precision int
}
func (b *Balance) Equal(o *Balance) bool {
if b.DestinationId == "" {
b.DestinationId = utils.ANY
}
if o.DestinationId == "" {
o.DestinationId = utils.ANY
}
return b.ExpirationDate.Equal(o.ExpirationDate) &&
b.Weight == o.Weight &&
b.DestinationId == o.DestinationId &&
b.RateSubject == o.RateSubject
}
// the default balance has no destinationid, Expirationdate or ratesubject
func (b *Balance) IsDefault() bool {
return (b.DestinationId == "" || b.DestinationId == utils.ANY) && b.RateSubject == "" && b.ExpirationDate.IsZero()
}
func (b *Balance) IsExpired() bool {
return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now())
}
@@ -100,6 +109,198 @@ func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) {
return cc, nil
}
func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *UserBalance, moneyBalances BalanceChain) error {
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
ts := cc.Timespans[tsIndex]
if ts.Increments == nil {
ts.createIncrementsSlice()
}
if paid, _ := ts.IsPaid(); paid {
continue
}
tsWasSplit := false
for incrementIndex, increment := range ts.Increments {
if tsWasSplit {
break
}
if increment.paid {
continue
}
if b.RateSubject == ZEROSECOND || b.RateSubject == "" {
amount := increment.Duration.Seconds()
if b.Value >= amount {
b.Value -= amount
increment.BalanceUuids = append(increment.BalanceUuids, b.Uuid)
increment.MinuteInfo = &MinuteInfo{cc.Destination, amount, 0}
increment.paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
}
continue
}
if b.RateSubject == ZEROMINUTE {
amount := time.Minute.Seconds()
if b.Value >= amount { // balance has at least 60 seconds
newTs := ts
if incrementIndex != 0 {
// if increment it's not at the begining we must split the timespan
newTs = ts.SplitByIncrement(incrementIndex)
}
newTs.RoundToDuration(time.Minute)
newTs.RateInterval = &RateInterval{
Rating: &RIRate{
Rates: RateGroups{
&Rate{
GroupIntervalStart: 0,
Value: 0,
RateIncrement: time.Minute,
RateUnit: time.Minute,
},
},
},
}
newTs.createIncrementsSlice()
// insert the new timespan
if newTs != ts {
tsIndex++
cc.Timespans = append(cc.Timespans, nil)
copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:])
cc.Timespans[tsIndex] = newTs
tsWasSplit = true
}
cc.Timespans.RemoveOverlapedFromIndex(tsIndex)
b.Value -= amount
newTs.Increments[0].BalanceUuids = append(newTs.Increments[0].BalanceUuids, b.Uuid)
newTs.Increments[0].MinuteInfo = &MinuteInfo{cc.Destination, amount, 0}
newTs.Increments[0].paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
}
continue
}
// get the new rate
cd := cc.CreateCallDescriptor()
cd.Subject = b.RateSubject
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
newCC, err := b.GetCost(cd)
if err != nil {
Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err))
continue
}
//debit new callcost
var paidTs []*TimeSpan
for _, nts := range newCC.Timespans {
nts.createIncrementsSlice()
paidTs = append(paidTs, nts)
for _, nInc := range nts.Increments {
// debit minutes and money
seconds := nInc.Duration.Seconds()
cost := nInc.Cost
var moneyBal *Balance
for _, mb := range moneyBalances {
if mb.Value >= cost {
moneyBal = mb
break
}
}
if moneyBal != nil && b.Value >= seconds {
b.Value -= seconds
moneyBal.Value -= cost
nInc.BalanceUuids = append(nInc.BalanceUuids, b.Uuid)
nInc.BalanceUuids = append(nInc.BalanceUuids, moneyBal.Uuid)
nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds, 0}
nInc.paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: newCC.Direction, Balance: &Balance{Value: seconds, DestinationId: newCC.Destination}})
ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}})
}
} else {
increment.paid = false
break
}
}
}
newTs := ts.SplitByIncrement(incrementIndex)
increment.paid = (&cc.Timespans).OverlapWithTimeSpans(paidTs, newTs, tsIndex)
tsWasSplit = increment.paid
}
}
return nil
}
func (b *Balance) DebitMoney(cc *CallCost, count bool, ub *UserBalance) error {
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
ts := cc.Timespans[tsIndex]
if ts.Increments == nil {
ts.createIncrementsSlice()
}
if paid, _ := ts.IsPaid(); paid {
continue
}
tsWasSplit := false
for incrementIndex, increment := range ts.Increments {
if tsWasSplit {
break
}
if increment.paid {
continue
}
// check standard subject tags
if b.RateSubject == "" {
amount := increment.Cost
if b.Value >= amount {
b.Value -= amount
increment.BalanceUuids = append(increment.BalanceUuids, b.Uuid)
increment.paid = true
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
}
} else {
// get the new rate
cd := cc.CreateCallDescriptor()
cd.Subject = b.RateSubject
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
newCC, err := b.GetCost(cd)
if err != nil {
Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err))
continue
}
//debit new callcost
var paidTs []*TimeSpan
for _, nts := range newCC.Timespans {
nts.createIncrementsSlice()
paidTs = append(paidTs, nts)
for nIdx, nInc := range nts.Increments {
// debit money
amount := nInc.Cost
if b.Value >= amount {
b.Value -= amount
nInc.BalanceUuids = append(nInc.BalanceUuids, b.Uuid)
nInc.paid = true
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}})
}
} else {
nts.SplitByIncrement(nIdx)
}
}
}
newTs := ts.SplitByIncrement(incrementIndex)
increment.paid = (&cc.Timespans).OverlapWithTimeSpans(paidTs, newTs, tsIndex)
tsWasSplit = increment.paid
}
}
}
return nil
}
/*
Structure to store minute buckets according to weight, precision or price.
*/

View File

@@ -347,9 +347,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti
func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans TimeSpans) []*TimeSpan {
for i := 0; i < len(timespans); i++ {
ts := timespans[i]
if ts.overlapped {
continue
}
if ts.RateInterval != nil {
_, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart())
// if the timespan duration is larger than the rate increment make sure it is a multiple of it

View File

@@ -49,7 +49,7 @@ var balanceInsufficient = `{"Id":"*out:192.168.56.66:dan","Type":"*prepaid","Bal
var costInsufficient = `{"Direction":"*out","TOR":"call","Tenant":"192.168.56.66","Subject":"dan","Account":"dan","Destination":"+4986517174963","Cost":1,"ConnectFee":3,"Timespans":[{"TimeStart":"2013-12-05T09:52:17+01:00","TimeEnd":"2013-12-05T09:53:17+01:00","Cost":1,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":3,"Rates":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":60000000000,"RateUnit":60000000000}],"RoundingMethod":"*up","RoundingDecimals":2},"Weight":10},"CallDuration":60000000000,"Increments":null,"MatchedSubject":"*out:192.168.56.66:call:*any","MatchedPrefix":"+49"}]}`
func FIXMETestDebitInsufficientBalance(t *testing.T) {
func TestDebitInsufficientBalance(t *testing.T) {
b1 := new(UserBalance)
if err := json.Unmarshal([]byte(balanceInsufficient), b1); err != nil {
t.Error("Error restoring balance1: ", err)
@@ -59,10 +59,11 @@ func FIXMETestDebitInsufficientBalance(t *testing.T) {
t.Error("Error restoring callCost1: ", err)
}
err := b1.debitCreditBalance(cc1, false)
if err != nil {
t.Error("Error debiting balance: ", err)
if err == nil {
t.Error("Error showing debiting balance error: ", err)
}
if b1.BalanceMap[CREDIT+OUTBOUND][0].Value != -3 {
t.Error("Error debiting from balance: ", b1.BalanceMap[CREDIT+OUTBOUND][0])
if b1.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != -3 {
t.Logf("CC: %+v", cc1.Cost)
t.Errorf("Error debiting from balance: %+v", b1.BalanceMap[CREDIT+OUTBOUND])
}
}

View File

@@ -36,7 +36,6 @@ type TimeSpan struct {
RateInterval *RateInterval
CallDuration time.Duration // the call duration so far till TimeEnd
Increments Increments
overlapped bool
MatchedSubject, MatchedPrefix string
}
@@ -46,6 +45,7 @@ type Increment struct {
BalanceUuids []string // need more than one for minutes with cost
BalanceRateInterval *RateInterval
MinuteInfo *MinuteInfo
paid bool
}
// Holds the minute information related to a specified timespan
@@ -77,7 +77,9 @@ func (timespans *TimeSpans) RemoveOverlapedFromIndex(index int) {
tss[i] = nil
}
*timespans = tss[:newSliceEnd]
return
}
*timespans = tss
}
// The paidTs will replace the timespans that are exactly under them from the reciver list
@@ -208,6 +210,21 @@ func (ts *TimeSpan) createIncrementsSlice() {
ts.Increments = append(ts.Increments, &Increment{Duration: rateIncrement, Cost: incrementCost})
totalCost += incrementCost
}
ts.Cost = totalCost
}
// returns whether the timespan has all increments marked as paid and if not
// it also returns the first unpaied increment
func (ts *TimeSpan) IsPaid() (bool, int) {
if len(ts.Increments) == 0 {
return false, 0
}
for incrementIndex, increment := range ts.Increments {
if !increment.paid {
return false, incrementIndex
}
}
return true, len(ts.Increments)
}
/*
@@ -218,11 +235,9 @@ The interval will attach itself to the timespan that overlaps the interval.
*/
func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
//Logger.Debug("here: ", ts, " +++ ", i)
//log.Printf("TS: %+v", ts)
// if the span is not in interval return nil
if !(i.Contains(ts.TimeStart, false) || i.Contains(ts.TimeEnd, true)) {
//Logger.Debug("Not in interval")
//log.Printf("NOT in interval: %+v", i)
return
}
//Logger.Debug(fmt.Sprintf("TS: %+v", ts))
@@ -235,7 +250,6 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
// Logger.Debug(fmt.Sprintf("Splitting"))
ts.SetRateInterval(i)
splitTime := ts.TimeStart.Add(rate.GroupIntervalStart - ts.GetGroupStart())
//log.Print("SPLIT: ", splitTime)
nts = &TimeSpan{
TimeStart: splitTime,
TimeEnd: ts.TimeEnd,
@@ -246,12 +260,10 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("Group splitting: %+v %+v", ts, nts))
//log.Printf("Group splitting: %+v %+v", ts, nts)
return
}
}
}
//log.Printf("*************TS: %+v", ts)
// if the span is enclosed in the interval try to set as new interval and return nil
if i.Contains(ts.TimeStart, false) && i.Contains(ts.TimeEnd, true) {
//Logger.Debug("All in interval")
@@ -276,7 +288,6 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("right: %+v %+v", ts, nts))
//log.Printf("right: %+v %+v", ts, nts)
return
}
// if only the end time is in the interval split the interval to the left
@@ -299,7 +310,6 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("left: %+v %+v", ts, nts))
//log.Printf("left: %+v %+v", ts, nts)
return
}
return
@@ -374,7 +384,6 @@ func (ts *TimeSpan) SplitByRatingPlan(rp *RatingInfo) (newTs *TimeSpan) {
ts.TimeEnd = rp.ActivationTime
ts.SetNewCallDuration(newTs)
// Logger.Debug(fmt.Sprintf("RP SPLITTING: %+v %+v", ts, newTs))
//log.Printf("RP SPLITTING: %+v %+v", ts, newTs)
return
}

View File

@@ -561,7 +561,7 @@ func TestTimespanExpandingRoundingPastEnd(t *testing.T) {
cd := &CallDescriptor{}
timespans = cd.roundTimeSpansToIncrement(timespans)
if len(timespans) != 2 {
t.Error("Error removing overlaped intervals: ", timespans)
t.Error("Error removing overlaped intervals: ", timespans[0])
}
if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC)) {
t.Error("Error expanding timespan: ", timespans[0])
@@ -1456,3 +1456,27 @@ func TestOverlapWithTimeSpansAllPast(t *testing.T) {
t.Error("Error overlaping with timespans timespans: ", tss)
}
}
func TestOverlapWithTimeSpansOne(t *testing.T) {
tss := TimeSpans{
&TimeSpan{
TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC),
TimeEnd: time.Date(2013, 12, 5, 15, 49, 0, 0, time.UTC),
},
}
newTss := TimeSpans{
&TimeSpan{
TimeStart: time.Date(2013, 12, 5, 15, 45, 0, 0, time.UTC),
TimeEnd: time.Date(2013, 12, 5, 15, 47, 30, 0, time.UTC),
},
}
(&tss).OverlapWithTimeSpans(newTss, nil, 0)
if len(tss) != 2 ||
tss[0].TimeEnd != time.Date(2013, 12, 5, 15, 47, 30, 0, time.UTC) ||
tss[1].TimeEnd != time.Date(2013, 12, 5, 15, 49, 0, 0, time.UTC) {
for _, ts := range tss {
t.Logf("TS: %v", ts)
}
t.Error("Error overlaping with timespans timespans: ", tss)
}
}

View File

@@ -21,12 +21,10 @@ package engine
import (
"errors"
"fmt"
"log"
"github.com/cgrates/cgrates/utils"
"strings"
"time"
)
const (
@@ -152,14 +150,9 @@ func (ub *UserBalance) getBalancesForPrefix(prefix string, balances BalanceChain
return usefulBalances
}
/*
This method is the core of userbalance debiting: don't panic just follow the branches
*/
func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
minuteBalances := ub.BalanceMap[MINUTES+cc.Direction]
moneyBalances := ub.BalanceMap[CREDIT+cc.Direction]
usefulMinuteBalances := ub.getBalancesForPrefix(cc.Destination, minuteBalances)
usefulMoneyBalances := ub.getBalancesForPrefix(cc.Destination, moneyBalances)
usefulMinuteBalances := ub.getBalancesForPrefix(cc.Destination, ub.BalanceMap[MINUTES+cc.Direction])
usefulMoneyBalances := ub.getBalancesForPrefix(cc.Destination, ub.BalanceMap[CREDIT+cc.Direction])
// debit connect fee
if cc.ConnectFee > 0 {
amount := cc.ConnectFee
@@ -176,218 +169,84 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
}
}
if !paid {
// there are no money for the connect fee; abort mission
cc.Timespans = make([]*TimeSpan, 0)
return nil
// there are no money for the connect fee; go negative
moneyBalance := ub.GetDefaultMoneyBalance(cc.Direction)
moneyBalance.Value -= amount
// the conect fee is not refoundable!
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
}
}
// debit minutes
for _, balance := range usefulMinuteBalances {
balance.DebitMinutes(cc, count, ub, usefulMoneyBalances)
}
allPaidWithMinutes := true
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
ts := cc.Timespans[tsIndex]
ts.createIncrementsSlice()
tsWasSplit := false
log.Print("TS: ", ts)
for incrementIndex, increment := range ts.Increments {
if tsWasSplit {
break
}
paid := false
// debit minutes
log.Print("Debit minutes")
for _, b := range usefulMinuteBalances {
// check standard subject tags
if b.RateSubject == ZEROSECOND || b.RateSubject == "" {
amount := increment.Duration.Seconds()
if b.Value >= amount {
b.Value -= amount
increment.BalanceUuids = append(increment.BalanceUuids, b.Uuid)
increment.MinuteInfo = &MinuteInfo{cc.Destination, amount, 0}
paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
break
}
continue
}
if b.RateSubject == ZEROMINUTE {
amount := time.Minute.Seconds()
if b.Value >= amount { // balance has at least 60 seconds
newTs := ts
if incrementIndex != 0 {
// if increment it's not at the begining we must split the timespan
newTs = ts.SplitByIncrement(incrementIndex)
}
newTs.RoundToDuration(time.Minute)
newTs.RateInterval = &RateInterval{
Rating: &RIRate{
Rates: RateGroups{
&Rate{
GroupIntervalStart: 0,
Value: 0,
RateIncrement: time.Minute,
RateUnit: time.Minute,
},
},
},
}
newTs.createIncrementsSlice()
// insert the new timespan
if newTs != ts {
tsIndex++
cc.Timespans = append(cc.Timespans, nil)
copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:])
cc.Timespans[tsIndex] = newTs
tsWasSplit = true
}
cc.Timespans.RemoveOverlapedFromIndex(tsIndex)
b.Value -= amount
newTs.Increments[0].BalanceUuids = append(newTs.Increments[0].BalanceUuids, b.Uuid)
newTs.Increments[0].MinuteInfo = &MinuteInfo{cc.Destination, amount, 0}
paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
break
}
continue
}
// get the new rate
cd := cc.CreateCallDescriptor()
cd.Subject = b.RateSubject
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
newCC, err := b.GetCost(cd)
if err != nil {
Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err))
continue
}
//debit new callcost
var paidTs []*TimeSpan
for _, nts := range newCC.Timespans {
nts.createIncrementsSlice()
log.Printf("XXX: %+v", nts)
paidTs = append(paidTs, nts)
for nIdx, nInc := range nts.Increments {
// debit minutes and money
seconds := nInc.Duration.Seconds()
cost := nInc.Cost
var moneyBal *Balance
for _, mb := range moneyBalances {
if mb.Value >= cost {
mb.Value -= cost
moneyBal = mb
}
}
if moneyBal != nil && b.Value >= seconds {
b.Value -= seconds
nInc.BalanceUuids = append(nInc.BalanceUuids, b.Uuid)
nInc.BalanceUuids = append(nInc.BalanceUuids, moneyBal.Uuid)
nInc.MinuteInfo = &MinuteInfo{newCC.Destination, seconds, 0}
paid = true
if count {
ub.countUnits(&Action{BalanceId: MINUTES, Direction: newCC.Direction, Balance: &Balance{Value: seconds, DestinationId: newCC.Destination}})
ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: cost, DestinationId: newCC.Destination}})
}
} else {
paid = false
nts.SplitByIncrement(nIdx)
}
}
}
newTs := ts.SplitByIncrement(incrementIndex)
overlapped := (&cc.Timespans).OverlapWithTimeSpans(paidTs, newTs, tsIndex)
paid, tsWasSplit = overlapped, overlapped
}
if paid {
continue
} else {
// Split if some increments were processed by minutes
if incrementIndex > 0 && ts.Increments[incrementIndex-1].MinuteInfo != nil {
newTs := ts.SplitByIncrement(incrementIndex)
if newTs != nil {
idx := tsIndex + 1
cc.Timespans = append(cc.Timespans, nil)
copy(cc.Timespans[idx+1:], cc.Timespans[idx:])
cc.Timespans[idx] = newTs
newTs.createIncrementsSlice()
tsWasSplit = true
}
log.Print("BREAK ON MINUTES")
break
}
}
log.Print("Debit money")
// debit monetary
for _, b := range usefulMoneyBalances {
// check standard subject tags
if b.RateSubject == "" {
amount := increment.Cost
if b.Value >= amount {
b.Value -= amount
increment.BalanceUuids = append(increment.BalanceUuids, b.Uuid)
paid = true
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}})
}
break
}
} else {
// get the new rate
cd := cc.CreateCallDescriptor()
cd.Subject = b.RateSubject
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
newCC, err := b.GetCost(cd)
if err != nil {
Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err))
continue
}
//debit new callcost
var paidTs []*TimeSpan
for _, nts := range newCC.Timespans {
nts.createIncrementsSlice()
log.Printf("COST: %+v", nts)
paidTs = append(paidTs, nts)
for nIdx, nInc := range nts.Increments {
// debit money
amount := nInc.Cost
if b.Value >= amount {
b.Value -= amount
nInc.BalanceUuids = append(nInc.BalanceUuids, b.Uuid)
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}})
}
} else {
nts.SplitByIncrement(nIdx)
}
}
}
newTs := ts.SplitByIncrement(incrementIndex)
overlapped := (&cc.Timespans).OverlapWithTimeSpans(paidTs, newTs, tsIndex)
paid, tsWasSplit = overlapped, overlapped
}
}
if !paid {
// FIXME: must debit all from the first monetary balance (go negative)
// no balance was attached to this increment: cut the rest of increments/timespans
if incrementIndex == 0 {
// if we are right at the begining in the ts leave it out
cc.Timespans = cc.Timespans[:tsIndex]
} else {
ts.SplitByIncrement(incrementIndex)
cc.Timespans = cc.Timespans[:tsIndex+1]
}
return errors.New("Not enough credit")
//return nil
if paid, incrementIndex := ts.IsPaid(); !paid {
allPaidWithMinutes = false
newTs := ts.SplitByIncrement(incrementIndex)
if newTs != nil {
idx := tsIndex + 1
cc.Timespans = append(cc.Timespans, nil)
copy(cc.Timespans[idx+1:], cc.Timespans[idx:])
cc.Timespans[idx] = newTs
}
}
}
if allPaidWithMinutes {
return nil
}
// debit money
for _, balance := range usefulMoneyBalances {
balance.DebitMoney(cc, count, ub)
}
var returnError error
insuficientCreditError := errors.New("not enough credit")
// get the highest priority money balanance
// and go negative on it with the amount still unpaid
moneyBalance := ub.GetDefaultMoneyBalance(cc.Direction)
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
ts := cc.Timespans[tsIndex]
if ts.Increments == nil {
ts.createIncrementsSlice()
}
if paid, incrementIndex := ts.IsPaid(); !paid {
newTs := ts.SplitByIncrement(incrementIndex)
if newTs != nil {
idx := tsIndex + 1
cc.Timespans = append(cc.Timespans, nil)
copy(cc.Timespans[idx+1:], cc.Timespans[idx:])
cc.Timespans[idx] = newTs
continue
}
for _, increment := range ts.Increments {
cost := increment.Cost
moneyBalance.Value -= cost
if count {
ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: cost, DestinationId: cc.Destination}})
}
returnError = insuficientCreditError
}
}
}
return returnError
}
return nil
func (ub *UserBalance) GetDefaultMoneyBalance(direction string) *Balance {
for _, balance := range ub.BalanceMap[CREDIT+direction] {
if balance.IsDefault() {
return balance
}
}
// create default balance
defaultBalance := &Balance{Weight: 999}
ub.BalanceMap[CREDIT+direction] = append(ub.BalanceMap[CREDIT+direction], defaultBalance)
return defaultBalance
}
func (ub *UserBalance) refoundIncrements(increments Increments, count bool) {

View File

@@ -227,6 +227,7 @@ func TestDebitCreditZeroSecond(t *testing.T) {
if err != nil {
t.Error("Error debiting balance: ", err)
}
t.Logf("%+v", cc.Timespans[0])
if cc.Timespans[0].Increments[0].BalanceUuids[0] != "testb" {
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
}
@@ -258,6 +259,7 @@ func TestDebitCreditZeroMinute(t *testing.T) {
if err != nil {
t.Error("Error debiting balance: ", err)
}
t.Logf("%+v", cc.Timespans)
if cc.Timespans[0].Increments[0].BalanceUuids[0] != "testb" ||
cc.Timespans[0].Increments[0].Duration != time.Minute {
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
@@ -338,7 +340,7 @@ func TestDebitCreditNoCredit(t *testing.T) {
t.Error("Error extracting minutes from balance: ",
rifsBalance.BalanceMap[MINUTES+OUTBOUND][0])
}
if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != time.Minute {
if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != time.Minute {
t.Error("Error truncating extra timespans: ", cc.Timespans)
}
}
@@ -523,12 +525,12 @@ func TestDebitCreditNoConectFeeCredit(t *testing.T) {
MINUTES + OUTBOUND: BalanceChain{b1},
}}
err := rifsBalance.debitCreditBalance(cc, false)
if err != nil {
t.Error("Error debiting balance: ", err)
if err == nil {
t.Error("Error showing debiting balance error: ", err)
}
if len(cc.Timespans) != 0 || cc.GetDuration() != 0 {
t.Error("Error cutting at no connect fee: ", cc.Timespans)
if len(cc.Timespans) != 2 || rifsBalance.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != -30 {
t.Error("Error cutting at no connect fee: ", rifsBalance.BalanceMap[CREDIT+OUTBOUND].GetTotalValue())
}
}
@@ -559,19 +561,20 @@ func TestDebitCreditMoneyOnly(t *testing.T) {
if err == nil {
t.Error("Missing noy enough credit error ")
}
t.Logf("%+v", cc.Timespans[0].Increments)
if cc.Timespans[0].Increments[0].BalanceUuids[0] != "money" ||
cc.Timespans[0].Increments[0].Duration != 10*time.Second {
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0].Duration)
}
if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 {
if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -30 {
t.Error("Error extracting minutes from balance: ",
rifsBalance.BalanceMap[CREDIT+OUTBOUND][0])
}
if len(cc.Timespans) != 2 ||
if len(cc.Timespans) != 3 ||
cc.Timespans[0].GetDuration() != 10*time.Second ||
cc.Timespans[1].GetDuration() != 40*time.Second {
t.Error("Error truncating extra timespans: ", cc.Timespans[1].Increments[0])
cc.Timespans[1].GetDuration() != 40*time.Second ||
cc.Timespans[2].GetDuration() != 30*time.Second {
t.Error("Error truncating extra timespans: ", cc.Timespans[2].GetDuration())
}
}
@@ -679,13 +682,13 @@ func TestDebitCreditSubjectMixed(t *testing.T) {
t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0])
}
if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 ||
rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 20 {
rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 95 {
t.Errorf("Error extracting minutes from balance: %+v, %+v",
rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value)
}
if len(cc.Timespans) != 9 || cc.Timespans[0].GetDuration() != 40*time.Second {
if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != 40*time.Second {
for _, ts := range cc.Timespans {
t.Logf("%+v %+v", ts, ts.Increments[0])
t.Log(ts)
}
t.Error("Error truncating extra timespans: ", len(cc.Timespans), cc.Timespans[0].GetDuration())
}