mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
refactored debit credit method, now modular and better tested
This commit is contained in:
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user