diff --git a/engine/callcost.go b/engine/callcost.go index c692fe963..178bc2af5 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -153,13 +153,14 @@ func (cc *CallCost) AsJSON() string { return string(ccJson) } -func (cc *CallCost) UpdateCost() { +func (cc *CallCost) updateCost() { cost := 0.0 if cc.deductConnectFee { // add back the connectFee cost += cc.GetConnectFee() } for _, ts := range cc.Timespans { - cost += ts.getCost() + ts.Cost = ts.calculateCost() + cost += ts.Cost cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals } cc.Cost = cost diff --git a/engine/calldesc.go b/engine/calldesc.go index fe9db8c9f..46371998f 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -450,7 +450,8 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { // handle max cost maxCost, strategy := ts.RateInterval.GetMaxCost() - cost += ts.getCost() + ts.Cost = ts.calculateCost() + cost += ts.Cost cd.MaxCostSoFar += cost //log.Print("Before: ", cost) if strategy != "" && maxCost > 0 { @@ -496,7 +497,7 @@ func (cd *CallDescriptor) getCost() (*CallCost, error) { if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } - cost += ts.getCost() + cost += ts.calculateCost() } //startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.Category)) @@ -636,7 +637,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) utils.Logger.Err(fmt.Sprintf(" Error getting cost for account key <%s>: %s", cd.GetAccountKey(), err.Error())) return nil, err } - cc.UpdateCost() + cc.updateCost() cc.Timespans.Compress() //log.Printf("OUT CC: ", cc) return diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 6e31a701e..8d540d6f9 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -576,10 +576,43 @@ func TestGetCostRoundingIssue(t *testing.T) { cc, err := cd.GetCost() expected := 0.17 if cc.Cost != expected || err != nil { + t.Log(utils.ToIJSON(cc)) t.Errorf("Expected %v was %+v", expected, cc) } } +func TestGetCostMaxDebitRoundingIssue(t *testing.T) { + ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "dy", + Account: "dy", + Destination: "0723123113", + TimeStart: time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC), + TimeEnd: time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC), + MaxCostSoFar: 0, + } + acc, err := accountingStorage.GetAccount("cgrates.org:dy") + if err != nil || acc.BalanceMap[utils.MONETARY][0].Value != 1 { + t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err) + } + cc, err := cd.MaxDebit() + expected := 0.39 + if cc.Cost != expected || err != nil { + t.Log(utils.ToIJSON(cc)) + t.Errorf("Expected %v was %+v", expected, cc) + } + acc, err = accountingStorage.GetAccount("cgrates.org:dy") + if err != nil || acc.BalanceMap[utils.MONETARY][0].Value != 1-expected { + t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err) + } +} + func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") for _, at := range ap { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 9be5972ed..5fef65033 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -197,6 +197,7 @@ CDRST2_WARN_ACD,,*min_acd,3,true,0,,,,,,,,,,,,5,CDRST_WARN_HTTP,10 vdf,minitsboy,MORE_MINUTES,STANDARD_TRIGGER,, cgrates.org,12345,TOPUP10_AT,STANDARD_TRIGGERS,, cgrates.org,123456,TOPUP10_AT,STANDARD_TRIGGERS,, +cgrates.org,dy,TOPUP10_AT,STANDARD_TRIGGERS,, cgrates.org,remo,TOPUP10_AT,,, vdf,empty0,TOPUP_SHARED0_AT,,, vdf,empty10,TOPUP_SHARED10_AT,,, @@ -1043,7 +1044,7 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 10 { + if len(csvr.accountActions) != 11 { t.Error("Failed to load account actions: ", len(csvr.accountActions)) } aa := csvr.accountActions["vdf:minitsboy"] diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 0087f9b7b..e11f731e3 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -318,7 +318,6 @@ func (i *RateInterval) GetCost(duration, startSecond time.Duration) float64 { GetRateParameters(startSecond) price /= rateUnit.Seconds() d := duration.Seconds() - return d * price } diff --git a/engine/timespans.go b/engine/timespans.go index 24df295d7..d3aba2964 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -254,21 +254,15 @@ func (ts *TimeSpan) SetRateInterval(interval *RateInterval) { // Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refund on session // manager debit loop where the cost cannot be recalculated) -func (ts *TimeSpan) getCost() float64 { +func (ts *TimeSpan) calculateCost() float64 { if ts.Increments.Length() == 0 { if ts.RateInterval == nil { return 0 } - cost := ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) - ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) - return ts.Cost + return ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) } else { cost := ts.Increments.GetTotalCost() - if ts.RateInterval != nil && ts.RateInterval.Rating != nil { - return utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) - } else { - return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) - } + return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) } } @@ -291,8 +285,8 @@ func (ts *TimeSpan) createIncrementsSlice() { // because ts cost is rounded //incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds() nbIncrements := int(ts.GetDuration() / rateIncrement) - incrementCost := ts.getCost() / float64(nbIncrements) - incrementCost = utils.Round(incrementCost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals + incrementCost := ts.calculateCost() / float64(nbIncrements) + incrementCost = utils.Round(incrementCost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) for s := 0; s < nbIncrements; s++ { inc := &Increment{ Duration: rateIncrement, diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 1a4fea010..2cc0f9deb 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -214,7 +214,7 @@ func TestTimespanGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) ts1 := TimeSpan{TimeStart: t1, TimeEnd: t2} - if ts1.getCost() != 0 { + if ts1.calculateCost() != 0 { t.Error("No interval and still kicking") } ts1.SetRateInterval( @@ -223,12 +223,12 @@ func TestTimespanGetCost(t *testing.T) { Rating: &RIRate{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}}, }, ) - if ts1.getCost() != 600 { + if ts1.calculateCost() != 600 { t.Error("Expected 10 got ", ts1.Cost) } ts1.RateInterval = nil ts1.SetRateInterval(&RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 60 * time.Second}}}}) - if ts1.getCost() != 10 { + if ts1.calculateCost() != 10 { t.Error("Expected 6000 got ", ts1.Cost) } } @@ -239,8 +239,8 @@ func TestTimespanGetCostIntervals(t *testing.T) { for i := 0; i < 11; i++ { ts.Increments[i] = &Increment{Cost: 0.02} } - if ts.getCost() != 0.22 { - t.Error("Error caclulating timespan cost: ", ts.getCost()) + if ts.calculateCost() != 0.22 { + t.Error("Error caclulating timespan cost: ", ts.calculateCost()) } } @@ -742,7 +742,7 @@ func TestTimespanCreateIncrements(t *testing.T) { if len(ts.Increments) != 3 { t.Error("Error creating increment slice: ", len(ts.Increments)) } - if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20.06667 { + if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20.07 { t.Error("Wrong second slice: ", ts.Increments[2].Cost) } } diff --git a/sessionmanager/session.go b/sessionmanager/session.go index b32485b5e..15d9532d5 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -233,8 +233,6 @@ func (s *Session) SaveOperations() { firstCC.Merge(cc) //utils.Logger.Debug(fmt.Sprintf("AFTER MERGE: %s", utils.ToJSON(firstCC))) } - // make sure we have rounded timespans final cost - firstCC.UpdateCost() var reply string err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{