From c182ca65f59b887c64d5be1b844e66360cfb864b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 24 Sep 2021 14:59:59 +0300 Subject: [PATCH] Updated balance debit --- engine/account.go | 63 ++++----- engine/account_test.go | 2 +- engine/balances.go | 307 +++++++++++++++++++++++++++++++++++++--- engine/callcost.go | 10 -- engine/callcost_test.go | 55 ------- engine/calldesc_test.go | 1 - engine/datacost.go | 1 - engine/timespans.go | 40 ++---- utils/coreutils.go | 12 +- utils/coreutils_test.go | 12 +- 10 files changed, 348 insertions(+), 155 deletions(-) diff --git a/engine/account.go b/engine/account.go index 087052442..737fa1295 100644 --- a/engine/account.go +++ b/engine/account.go @@ -392,8 +392,8 @@ func (acc *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bo // try every balance multiple times in case one becomes active or ratig changes unitBalanceChecker = false for _, balance := range usefulUnitBalances { - partCC, debitErr := balance.debitUnits(cd, balance.account, - usefulMoneyBalances, count, dryRun, len(cc.Timespans) == 0) + partCC, debitErr := balance.debit(cd, balance.account, + usefulMoneyBalances, count, dryRun, len(cc.Timespans) == 0, true) if debitErr != nil { return nil, debitErr } @@ -429,8 +429,8 @@ func (acc *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bo // try every balance multiple times in case one becomes active or ratig changes moneyBalanceChecker = false for _, balance := range usefulMoneyBalances { - partCC, debitErr := balance.debitMoney(cd, balance.account, - usefulMoneyBalances, count, dryRun, len(cc.Timespans) == 0) + partCC, debitErr := balance.debit(cd, balance.account, + usefulMoneyBalances, count, dryRun, len(cc.Timespans) == 0,false) if debitErr != nil { return nil, debitErr } @@ -533,7 +533,6 @@ func (acc *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bo Value: defaultBalance.Value, } increment.BalanceInfo.AccountID = acc.ID - increment.paid = true if count { acc.countUnits( cost, @@ -859,39 +858,39 @@ func (acc *Account) Clone() *Account { } // DebitConnectionFee debits the connection fee -func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balances, count bool, block bool) (bool, Balance) { +func (acc *Account) DebitConnectionFee(cc *CallCost, ufMoneyBalances Balances, count bool, block bool) (bool, Balance) { var debitedBalance Balance - - if cc.deductConnectFee { - connectFee := cc.GetConnectFee() - //log.Print("CONNECT FEE: %f", connectFee) - connectFeePaid := false - for _, b := range usefulMoneyBalances { - if b.GetValue() >= connectFee { - b.SubstractValue(connectFee) - // the conect fee is not refundable! - if count { - acc.countUnits(connectFee, utils.MetaMonetary, cc, b) - } - connectFeePaid = true - debitedBalance = *b - break - } - if b.Blocker && block { // stop here - return false, debitedBalance - } - } - // debit connect fee - if connectFee > 0 && !connectFeePaid { - cc.negativeConnectFee = true - // there are no money for the connect fee; go negative - b := acc.GetDefaultMoneyBalance() + if !cc.deductConnectFee { + return true, debitedBalance + } + connectFee := cc.GetConnectFee() + //log.Print("CONNECT FEE: %f", connectFee) + var connectFeePaid bool + for _, b := range ufMoneyBalances { + if b.GetValue() >= connectFee { b.SubstractValue(connectFee) - debitedBalance = *b // the conect fee is not refundable! if count { acc.countUnits(connectFee, utils.MetaMonetary, cc, b) } + connectFeePaid = true + debitedBalance = *b + break + } + if b.Blocker && block { // stop here + return false, debitedBalance + } + } + // debit connect fee + if connectFee > 0 && !connectFeePaid { + cc.negativeConnectFee = true + // there are no money for the connect fee; go negative + b := acc.GetDefaultMoneyBalance() + b.SubstractValue(connectFee) + debitedBalance = *b + // the conect fee is not refundable! + if count { + acc.countUnits(connectFee, utils.MetaMonetary, cc, b) } } return true, debitedBalance diff --git a/engine/account_test.go b/engine/account_test.go index bb1cc612a..d03c6bf86 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -533,7 +533,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.MetaVoice: {b1}, - utils.MetaMonetary: {&Balance{Uuid: "moneya", Value: 110}}, + utils.MetaMonetary: {{Uuid: "moneya", Value: 110}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) diff --git a/engine/balances.go b/engine/balances.go index 297dff015..dd1e7dc83 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -267,11 +267,10 @@ func (b *Balance) GetCost(cd *CallDescriptor, getStandardIfEmpty bool) (*CallCos if getStandardIfEmpty { cd.RatingInfos = nil return cd.getCost() - } else { - cc := cd.CreateCallCost() - cc.Cost = 0 - return cc, nil } + cc := cd.CreateCallCost() + cc.Cost = 0 + return cc, nil } func (b *Balance) GetValue() float64 { @@ -302,7 +301,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala if !b.IsActiveAt(cd.TimeStart) || b.GetValue() <= 0 { return } - if duration, err := utils.ParseZeroRatingSubject(cd.ToR, b.RatingSubject, config.CgrConfig().RalsCfg().BalanceRatingSubject); err == nil { + if duration, err := utils.ParseZeroRatingSubject(cd.ToR, b.RatingSubject, config.CgrConfig().RalsCfg().BalanceRatingSubject, true); err == nil { // we have *zero based units cc = cd.CreateCallCost() cc.Timespans = append(cc.Timespans, &TimeSpan{ @@ -359,12 +358,10 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala } inc.BalanceInfo.AccountID = ub.ID inc.Cost = 0 - inc.paid = true if count { ub.countUnits(amount, cc.ToR, cc, b) } } else { - inc.paid = false // delete the rest of the unpiad increments/timespans if incIndex == 0 { // cut the entire current timespan @@ -438,7 +435,6 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala amount = utils.Round(amount/b.Factor.GetValue(cd.ToR), globalRoundingDecimals, utils.MetaRoundingUp) } cost := inc.Cost - inc.paid = false if strategy == utils.MetaMaxCostDisconnect && cd.MaxCostSoFar >= maxCost { // cut the entire current timespan cc.maxCostDisconect = true @@ -462,7 +458,6 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala RateInterval: ts.RateInterval, } inc.BalanceInfo.AccountID = ub.ID - inc.paid = true if count { ub.countUnits(cost, utils.MetaMonetary, cc, b) } @@ -501,7 +496,6 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala } cd.MaxCostSoFar += cost } - inc.paid = true if count { ub.countUnits(amount, cc.ToR, cc, b) if cost != 0 { @@ -509,7 +503,6 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala } } } else { - inc.paid = false // delete the rest of the unpaid increments/timespans if incIndex == 0 { // cut the entire current timespan @@ -544,7 +537,6 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala var ok bool //log.Print("cc: " + utils.ToJSON(cc)) if debitConnectFee { - // this is the first add, debit the connect fee if ok, debitedConnectFeeBalance = ub.DebitConnectionFee(cc, moneyBalances, count, true); !ok { // balance is blocker @@ -567,7 +559,11 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala return nil, errors.New("timespan with no rate interval assigned") } - if tsIndex == 0 && ts.RateInterval.Rating.ConnectFee > 0 && debitConnectFee && cc.deductConnectFee && ok { + if tsIndex == 0 && + ts.RateInterval.Rating.ConnectFee > 0 && + debitConnectFee && + cc.deductConnectFee && + ok { inc := &Increment{ Duration: 0, @@ -593,13 +589,16 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala // check standard subject tags //log.Printf("INC: %+v", inc) - if tsIndex == 0 && incIndex == 0 && ts.RateInterval.Rating.ConnectFee > 0 && cc.deductConnectFee && ok { + if tsIndex == 0 && + incIndex == 0 && + ts.RateInterval.Rating.ConnectFee > 0 && + cc.deductConnectFee && + ok { // go to nextincrement continue } amount := inc.Cost - inc.paid = false if strategy == utils.MetaMaxCostDisconnect && cd.MaxCostSoFar >= maxCost { // cut the entire current timespan cc.maxCostDisconect = true @@ -625,7 +624,6 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala if b.RatingSubject != "" { inc.BalanceInfo.Monetary.RateInterval = ts.RateInterval } - inc.paid = true if count { ub.countUnits(amount, utils.MetaMonetary, cc, b) } @@ -647,12 +645,10 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala if b.RatingSubject != "" { inc.BalanceInfo.Monetary.RateInterval = ts.RateInterval } - inc.paid = true if count { ub.countUnits(amount, utils.MetaMonetary, cc, b) } } else { - inc.paid = false // delete the rest of the unpiad increments/timespans if incIndex == 0 { // cut the entire current timespan @@ -826,3 +822,278 @@ func (bl *BalanceSummary) FieldAsInterface(fldPath []string) (val interface{}, e return bl.Initial, nil } } + +// debitUnits will debit units for call descriptor. +// returns the amount debited within cc +func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances, + count, dryRun, debitConnectFee, isUnitBal bool) (cc *CallCost, err error) { + if !b.IsActiveAt(cd.TimeStart) || b.GetValue() <= 0 { + return + } + tor := cd.ToR + if !isUnitBal { + tor = utils.MetaMonetary + } + if duration, err_ := utils.ParseZeroRatingSubject(tor, b.RatingSubject, + config.CgrConfig().RalsCfg().BalanceRatingSubject, isUnitBal); err_ == nil { + // we have *zero based units + cc = cd.CreateCallCost() + ts := &TimeSpan{ + TimeStart: cd.TimeStart, + TimeEnd: cd.TimeEnd, + } + cc.Timespans = TimeSpans{ts} + ts.RoundToDuration(duration) + ts.RateInterval = &RateInterval{ + Rating: &RIRate{ + Rates: RateGroups{ + &RGRate{ + GroupIntervalStart: 0, + Value: 0, + RateIncrement: duration, + RateUnit: duration, + }, + }, + }, + } + prefix, destid := b.getMatchingPrefixAndDestID(cd.Destination) + if prefix == utils.EmptyString { + prefix = cd.Destination + } + if destid == utils.EmptyString { + destid = utils.MetaAny + } + ts.setRatingInfo(&RatingInfo{ + MatchedSubject: b.Uuid, + MatchedPrefix: prefix, + MatchedDestId: destid, + RatingPlanId: utils.MetaNone, + }) + ts.createIncrementsSlice() + //log.Printf("CC: %+v", ts) + for incIndex, inc := range ts.Increments { + //log.Printf("INCREMENET: %+v", inc) + amount := float64(inc.Duration) + if b.Factor != nil { + amount = utils.Round(amount/b.Factor.GetValue(tor), + globalRoundingDecimals, utils.MetaRoundingUp) + } + if b.GetValue() >= amount { + b.SubstractValue(amount) + inc.BalanceInfo.Unit = &UnitInfo{ + UUID: b.Uuid, + ID: b.ID, + Value: b.Value, + DestinationID: cc.Destination, + Consumed: amount, + ToR: tor, //aici + RateInterval: nil, + } + inc.BalanceInfo.AccountID = ub.ID + inc.Cost = 0 + if count { + ub.countUnits(amount, tor, cc, b) + } + continue + } + // delete the rest of the unpiad increments/timespans + if incIndex == 0 { + // cut the entire current timespan + return nil, nil + } + ts.SplitByIncrement(incIndex) + if len(cc.Timespans) == 0 { + cc = nil + } + return + } + return + } + // no rating subject + //log.Print("B: ", utils.ToJSON(b)) + //log.Printf("}}}}}}} %+v", cd.testCallcost) + cc, err = b.GetCost(cd, true) + if err != nil { + return nil, err + } + + var debitedConnectFeeBalance Balance + var connectFeeDebited bool + //log.Print("cc: " + utils.ToJSON(cc)) + if debitConnectFee { + // this is the first add, debit the connect fee + if connectFeeDebited, debitedConnectFeeBalance = ub.DebitConnectionFee(cc, moneyBalances, count, true); !connectFeeDebited { + // balance is blocker + return nil, nil + } + } + + cc.Timespans.Decompress() + //log.Printf("CallCost In Debit: %+v", cc) + //for _, ts := range cc.Timespans { + // log.Printf("CC_TS: %+v", ts.RateInterval.Rating.Rates[0]) + //} + for tsIndex, ts := range cc.Timespans { + if ts.RateInterval == nil { + utils.Logger.Err(fmt.Sprintf("Nil RateInterval ERROR on TS: %+v, CC: %+v, from CD: %+v", ts, cc, cd)) + return nil, errors.New("timespan with no rate interval assigned") + } + if ts.Increments == nil { + ts.createIncrementsSlice() + } + //log.Printf("TS: %+v", ts) + + if tsIndex == 0 && + ts.RateInterval.Rating.ConnectFee > 0 && + debitConnectFee && + cc.deductConnectFee && + connectFeeDebited { + + ts.Increments = append([]*Increment{{ + Duration: 0, + Cost: ts.RateInterval.Rating.ConnectFee, + BalanceInfo: &DebitInfo{ + Monetary: &MonetaryInfo{ + UUID: debitedConnectFeeBalance.Uuid, + ID: debitedConnectFeeBalance.ID, + Value: debitedConnectFeeBalance.Value, + }, + AccountID: ub.ID, + }, + }}, ts.Increments...) + } + + maxCost, strategy := ts.RateInterval.GetMaxCost() + //log.Printf("Timing: %+v", ts.RateInterval.Timing) + //log.Printf("RGRate: %+v", ts.RateInterval.Rating) + for incIndex, inc := range ts.Increments { + // check standard subject tags + //log.Printf("INC: %+v", inc) + + if tsIndex == 0 && + incIndex == 0 && + ts.RateInterval.Rating.ConnectFee > 0 && + cc.deductConnectFee && + connectFeeDebited { + // go to nextincrement + continue + } + if cd.MaxCostSoFar >= maxCost { + if strategy == utils.MetaMaxCostFree { + inc.Cost = 0.0 + inc.BalanceInfo.Monetary = &MonetaryInfo{ + UUID: b.Uuid, + ID: b.ID, + Value: b.Value, + } + inc.BalanceInfo.AccountID = ub.ID + if b.RatingSubject != utils.EmptyString || isUnitBal { + inc.BalanceInfo.Monetary.RateInterval = ts.RateInterval + } + if count { + ub.countUnits(inc.Cost, utils.MetaMonetary, cc, b) + } + + //log.Printf("TS: %+v", cc.Cost) + // go to nextincrement + continue + } else if cc.maxCostDisconect = strategy == utils.MetaMaxCostDisconnect; cc.maxCostDisconect && dryRun { + // cut the entire current timespan + if incIndex == 0 { + // cut the entire current timespan + cc.Timespans = cc.Timespans[:tsIndex] + } else { + ts.SplitByIncrement(incIndex) + cc.Timespans = cc.Timespans[:tsIndex+1] + } + return + } + } + + // debit minutes and money + amount := float64(inc.Duration) + cost := inc.Cost + + canDebitCost := b.GetValue() >= cost + var moneyBal *Balance + if isUnitBal { + if b.Factor != nil { + amount = utils.Round(amount/b.Factor.GetValue(cd.ToR), globalRoundingDecimals, utils.MetaRoundingUp) + } + for _, mb := range moneyBalances { + if mb.GetValue() >= cost { + moneyBal = mb + break + } + } + if cost != 0 && moneyBal == nil && (!dryRun || ub.AllowNegative) { // Fix for issue #685 + utils.Logger.Warning(fmt.Sprintf(" Going negative on account %s with AllowNegative: false", cd.GetAccountKey())) + moneyBal = ub.GetDefaultMoneyBalance() + } + canDebitCost = b.GetValue() >= amount && (moneyBal != nil || cost == 0) + } + if !canDebitCost { + // delete the rest of the unpaid increments/timespans + if incIndex == 0 { + // cut the entire current timespan + cc.Timespans = cc.Timespans[:tsIndex] + } else { + ts.SplitByIncrement(incIndex) + cc.Timespans = cc.Timespans[:tsIndex+1] + } + if len(cc.Timespans) == 0 { + cc = nil + } + return + } + + if isUnitBal { // unit balance + b.SubstractValue(amount) + inc.BalanceInfo.Unit = &UnitInfo{ + UUID: b.Uuid, + ID: b.ID, + Value: b.Value, + DestinationID: cc.Destination, + Consumed: amount, + ToR: cc.ToR, + RateInterval: ts.RateInterval, + } + inc.BalanceInfo.AccountID = ub.ID + if cost != 0 { + moneyBal.SubstractValue(cost) + inc.BalanceInfo.Monetary = &MonetaryInfo{ + UUID: moneyBal.Uuid, + ID: moneyBal.ID, + Value: moneyBal.Value, + } + cd.MaxCostSoFar += cost + } + if count { + ub.countUnits(amount, cc.ToR, cc, b) + if cost != 0 { + ub.countUnits(cost, utils.MetaMonetary, cc, moneyBal) + } + } + } else { // monetary balance + b.SubstractValue(cost) + cd.MaxCostSoFar += cost + inc.BalanceInfo.Monetary = &MonetaryInfo{ + UUID: b.Uuid, + ID: b.ID, + Value: b.Value, + } + inc.BalanceInfo.AccountID = ub.ID + if b.RatingSubject != "" { + inc.BalanceInfo.Monetary.RateInterval = ts.RateInterval + } + if count { + ub.countUnits(cost, utils.MetaMonetary, cc, b) + } + } + } + } + if !isUnitBal && len(cc.Timespans) == 0 { + cc = nil + } + return +} diff --git a/engine/callcost.go b/engine/callcost.go index 72e655dd2..fcd7c9d39 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -99,15 +99,6 @@ func (cc *CallCost) CreateCallDescriptor() *CallDescriptor { } } -func (cc *CallCost) IsPaid() bool { - for _, ts := range cc.Timespans { - if paid, _ := ts.IsPaid(); !paid { - return false - } - } - return true -} - func (cc *CallCost) ToDataCost() (*DataCost, error) { if cc.ToR == utils.MetaVoice { return nil, errors.New("Not a data call!") @@ -145,7 +136,6 @@ func (cc *CallCost) ToDataCost() (*DataCost, error) { Cost: incr.Cost, BalanceInfo: incr.BalanceInfo, CompressFactor: incr.CompressFactor, - paid: incr.paid, } } } diff --git a/engine/callcost_test.go b/engine/callcost_test.go index 7d48daa07..712fbf558 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -302,7 +302,6 @@ func TestCallcostCallCostToDataCost(t *testing.T) { }, }, }, - paid: false, Duration: 12 * time.Second, CompressFactor: 2, }, @@ -324,7 +323,6 @@ func TestCallcostCallCostToDataCost(t *testing.T) { }, }, }, - paid: true, Duration: 24 * time.Second, CompressFactor: 2, }, @@ -429,55 +427,6 @@ func TestCallcostUpdateRatedUsage(t *testing.T) { } } -func TestCallcostIsPaidFalse(t *testing.T) { - cc := &CallCost{ - Timespans: TimeSpans{ - { - Increments: Increments{ - &Increment{ - paid: false, - }, - }, - }, - }, - } - - rcv := cc.IsPaid() - - if rcv != false { - t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", false, rcv) - } -} - -func TestCallcostIsPaidTrue(t *testing.T) { - cc := &CallCost{ - Timespans: TimeSpans{ - { - MatchedSubject: "1001", - Increments: Increments{ - &Increment{ - paid: true, - }, - }, - }, - { - MatchedSubject: "1002", - Increments: Increments{ - &Increment{ - paid: true, - }, - }, - }, - }, - } - - rcv := cc.IsPaid() - - if rcv != true { - t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", true, rcv) - } -} - func TestCallcostUpdateCost(t *testing.T) { cc := &CallCost{ deductConnectFee: false, @@ -573,7 +522,6 @@ func TestCallcostUpdateCost(t *testing.T) { }, }, }, - paid: false, Duration: 12 * time.Second, CompressFactor: 2, }, @@ -595,7 +543,6 @@ func TestCallcostUpdateCost(t *testing.T) { }, }, }, - paid: true, Duration: 24 * time.Second, CompressFactor: 2, }, @@ -720,7 +667,6 @@ func TestCallcostGetStartTime(t *testing.T) { }, }, }, - paid: false, Duration: 12 * time.Second, CompressFactor: 2, }, @@ -742,7 +688,6 @@ func TestCallcostGetStartTime(t *testing.T) { }, }, }, - paid: true, Duration: 24 * time.Second, CompressFactor: 2, }, diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 670d522f8..a650703c8 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -1957,7 +1957,6 @@ func TestCalldescRefundIncrementsNoBalanceInfo(t *testing.T) { Increments: Increments{ { Duration: time.Second, - paid: true, Cost: 5, }, }, diff --git a/engine/datacost.go b/engine/datacost.go index a88d92308..9f30f082b 100644 --- a/engine/datacost.go +++ b/engine/datacost.go @@ -42,5 +42,4 @@ type DataIncrement struct { Cost float64 BalanceInfo *DebitInfo // need more than one for units with cost CompressFactor int - paid bool } diff --git a/engine/timespans.go b/engine/timespans.go index f7881df63..838577da3 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -51,7 +51,6 @@ type Increment struct { Cost float64 BalanceInfo *DebitInfo // need more than one for units with cost CompressFactor int - paid bool } // Holds information about the balance that made a specific payment @@ -466,7 +465,6 @@ func (ts *TimeSpan) createIncrementsSlice() { if ts.RateInterval == nil { return } - ts.Increments = make([]*Increment, 0) // create rated units series _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) // we will use the calculated cost and devide by nb of increments @@ -475,36 +473,23 @@ func (ts *TimeSpan) createIncrementsSlice() { nbIncrements := int(ts.GetDuration() / rateIncrement) if nbIncrements > config.CgrConfig().RalsCfg().MaxIncrements { utils.Logger.Warning(fmt.Sprintf("error: <%s with %+v>, when creating increments slice, TimeSpan: %s", utils.ErrMaxIncrementsExceeded, nbIncrements, utils.ToJSON(ts))) + ts.Increments = make([]*Increment, 0) return } incrementCost := ts.CalculateCost() / float64(nbIncrements) incrementCost = utils.Round(incrementCost, globalRoundingDecimals, utils.MetaRoundingMiddle) - for s := 0; s < nbIncrements; s++ { - inc := &Increment{ + ts.Increments = make([]*Increment, nbIncrements) + for i := range ts.Increments { + ts.Increments[i] = &Increment{ Duration: rateIncrement, Cost: incrementCost, BalanceInfo: &DebitInfo{}, } - ts.Increments = append(ts.Increments, inc) } // put the rounded cost back in timespan ts.Cost = incrementCost * float64(nbIncrements) } -// 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 ts.Increments.Length() == 0 { - return false, 0 - } - for incrementIndex, increment := range ts.Increments { - if !increment.paid { - return false, incrementIndex - } - } - return true, len(ts.Increments) -} - /* Splits the given timespan according to how it relates to the interval. It will modify the endtime of the received timespan and it will return @@ -592,12 +577,12 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval, data bool) (nts *TimeSp } // Split the timespan at the given increment start -func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan { +func (ts *TimeSpan) SplitByIncrement(index int) (newTs *TimeSpan) { if index <= 0 || index >= len(ts.Increments) { - return nil + return } timeStart := ts.GetTimeStartForIncrement(index) - newTs := &TimeSpan{ + newTs = &TimeSpan{ RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd, @@ -608,7 +593,7 @@ func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan { newTs.Increments = ts.Increments[index:] ts.Increments = ts.Increments[:index] ts.SetNewDurationIndex(newTs) - return newTs + return } // Split the timespan at the given second @@ -725,11 +710,12 @@ func (ts *TimeSpan) GetTimeStartForIncrement(index int) time.Time { } func (ts *TimeSpan) RoundToDuration(duration time.Duration) { - if duration < ts.GetDuration() { - duration = utils.RoundDuration(duration, ts.GetDuration()) + tsDur := ts.GetDuration() + if duration < tsDur { + duration = utils.RoundDuration(duration, tsDur) } - if duration > ts.GetDuration() { - initialDuration := ts.GetDuration() + if duration > tsDur { + initialDuration := tsDur ts.TimeEnd = ts.TimeStart.Add(duration) ts.DurationIndex = ts.DurationIndex + (duration - initialDuration) } diff --git a/utils/coreutils.go b/utils/coreutils.go index 8640ea568..0632b6f09 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -391,9 +391,13 @@ func MinDuration(d1, d2 time.Duration) time.Duration { // ParseZeroRatingSubject will parse the subject in the balance // returns duration if able to extract it from subject // returns error if not able to parse duration (ie: if ratingSubject is standard one) -func ParseZeroRatingSubject(tor, rateSubj string, defaultRateSubj map[string]string) (time.Duration, error) { +func ParseZeroRatingSubject(tor, rateSubj string, defaultRateSubj map[string]string, isUnitBal bool) (time.Duration, error) { rateSubj = strings.TrimSpace(rateSubj) - if rateSubj == "" || rateSubj == MetaAny { + if !isUnitBal && rateSubj == EmptyString { + return 0, errors.New("no rating subject for monetary") + } + if rateSubj == EmptyString || + rateSubj == MetaAny { var hasToR bool if rateSubj, hasToR = defaultRateSubj[tor]; !hasToR { rateSubj = defaultRateSubj[MetaAny] @@ -403,8 +407,8 @@ func ParseZeroRatingSubject(tor, rateSubj string, defaultRateSubj map[string]str return 0, errors.New("malformed rating subject: " + rateSubj) } durStr := rateSubj[len(MetaRatingSubjectPrefix):] - if _, err := strconv.ParseFloat(durStr, 64); err == nil { // No time unit, postpend - durStr += "ns" + if val, err := strconv.ParseFloat(durStr, 64); err == nil { // No time unit, postpend + return time.Duration(val), nil // just return the float value converted(this should be faster than reparsing the string) } return time.ParseDuration(durStr) } diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go index a387973a6..6eb435203 100644 --- a/utils/coreutils_test.go +++ b/utils/coreutils_test.go @@ -748,24 +748,24 @@ func TestParseZeroRatingSubject(t *testing.T) { MetaVoice: "*zero1s", } for i, s := range subj { - if d, err := ParseZeroRatingSubject(MetaVoice, s, dfltRatingSubject); err != nil || d != dur[i] { + if d, err := ParseZeroRatingSubject(MetaVoice, s, dfltRatingSubject, true); err != nil || d != dur[i] { t.Error("Error parsing rating subject: ", s, d, err) } } - if d, err := ParseZeroRatingSubject(MetaData, EmptyString, dfltRatingSubject); err != nil || d != time.Nanosecond { + if d, err := ParseZeroRatingSubject(MetaData, EmptyString, dfltRatingSubject, true); err != nil || d != time.Nanosecond { t.Error("Error parsing rating subject: ", EmptyString, d, err) } - if d, err := ParseZeroRatingSubject(MetaSMS, EmptyString, dfltRatingSubject); err != nil || d != time.Nanosecond { + if d, err := ParseZeroRatingSubject(MetaSMS, EmptyString, dfltRatingSubject, true); err != nil || d != time.Nanosecond { t.Error("Error parsing rating subject: ", EmptyString, d, err) } - if d, err := ParseZeroRatingSubject(MetaMMS, EmptyString, dfltRatingSubject); err != nil || d != time.Nanosecond { + if d, err := ParseZeroRatingSubject(MetaMMS, EmptyString, dfltRatingSubject, true); err != nil || d != time.Nanosecond { t.Error("Error parsing rating subject: ", EmptyString, d, err) } - if d, err := ParseZeroRatingSubject(MetaMonetary, EmptyString, dfltRatingSubject); err != nil || d != time.Nanosecond { + if d, err := ParseZeroRatingSubject(MetaMonetary, EmptyString, dfltRatingSubject, true); err != nil || d != time.Nanosecond { t.Error("Error parsing rating subject: ", EmptyString, d, err) } expecting := "malformed rating subject: test" - if _, err := ParseZeroRatingSubject(MetaMonetary, "test", dfltRatingSubject); err == nil || err.Error() != expecting { + if _, err := ParseZeroRatingSubject(MetaMonetary, "test", dfltRatingSubject, true); err == nil || err.Error() != expecting { t.Errorf("Expecting: %+v, received: %+v ", expecting, err) } }