diff --git a/engine/balances.go b/engine/balances.go index 214c3cfa8..d51f4e299 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -642,6 +642,7 @@ func (bc BalanceChain) SaveDirtyBalances(acc *Account) { allowNegative := "" disabled := "" if b.account != nil { // only publish modifications for balances with account set + //utils.LogStack() accountId = b.account.Id allowNegative = strconv.FormatBool(b.account.AllowNegative) disabled = strconv.FormatBool(b.account.Disabled) diff --git a/engine/callcost.go b/engine/callcost.go index 238619491..c692fe963 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -152,3 +152,15 @@ func (cc *CallCost) AsJSON() string { ccJson, _ := json.Marshal(cc) return string(ccJson) } + +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() + 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 f6af50f7e..4b93208cc 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -83,7 +83,7 @@ var ( // Exported method to set the storage getter. func SetRatingStorage(sg RatingStorage) { - ratingStorage = sg + ratingStorage = sg } func SetAccountingStorage(ag AccountingStorage) { @@ -636,16 +636,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 } - cost := 0.0 - // calculate call cost after balances - if cc.deductConnectFee { // add back the connectFee - cost += cc.GetConnectFee() - } - for _, ts := range cc.Timespans { - cost += ts.getCost() - cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals - } - cc.Cost = cost + cc.UpdateCost() cc.Timespans.Compress() //log.Printf("OUT CC: ", cc) return diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 21827f091..6e31a701e 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -557,6 +557,28 @@ func TestGetCostWithMaxCost(t *testing.T) { t.Errorf("Expected %v was %v", expected, cc.Cost) } } +func TestGetCostRoundingIssue(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, + } + cc, err := cd.GetCost() + expected := 0.17 + if cc.Cost != expected || err != nil { + t.Errorf("Expected %v was %+v", expected, cc) + } +} func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 9d4fe2b1c..8ff6e154d 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -70,6 +70,7 @@ RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s R_URG,0,0,1,1,0 MX,0,1,1s,1s,0 +DY,0.15,0.05,60s,1s,0s ` destinationRates = ` RT_STANDARD,GERMANY,R1,*middle,4,0, @@ -91,6 +92,7 @@ DATA_RATE,*any,LANDLINE_OFFPEAK,*middle,4,0, RT_URG,URG,R_URG,*middle,4,0, MX_FREE,RET,MX,*middle,4,10,*free MX_DISC,RET,MX,*middle,4,10,*disconnect +RT_DY,RET,DY,*up,2,0, ` ratingPlans = ` STANDARD,RT_STANDARD,WORKDAYS_00,10 @@ -115,6 +117,7 @@ RP_MX,MX_DISC,WORKDAYS_00,10 RP_MX,MX_FREE,WORKDAYS_18,10 GER_ONLY,GER,*any,10 ANY_PLAN,DATA_RATE,*any,10 +DY_PLAN,RT_DY,*any,10 ` ratingProfiles = ` *out,CUSTOMER_1,0,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb, @@ -141,6 +144,7 @@ ANY_PLAN,DATA_RATE,*any,10 *out,cgrates.org,call,nt,2012-02-28T00:00:00Z,GER_ONLY,, *in,cgrates.org,LCR_STANDARD,max,2013-03-23T00:00:00Z,RP_MX,, *out,cgrates.org,call,money,2015-02-28T00:00:00Z,EVENING,, +*out,cgrates.org,call,dy,2015-02-28T00:00:00Z,DY_PLAN,, ` sharedGroups = ` SG1,*any,*lowest, @@ -391,7 +395,7 @@ func TestLoadTimimgs(t *testing.T) { } func TestLoadRates(t *testing.T) { - if len(csvr.rates) != 13 { + if len(csvr.rates) != 14 { t.Error("Failed to load rates: ", len(csvr.rates)) } rate := csvr.rates["R1"].RateSlots[0] @@ -461,7 +465,7 @@ func TestLoadRates(t *testing.T) { } func TestLoadDestinationRates(t *testing.T) { - if len(csvr.destinationRates) != 14 { + if len(csvr.destinationRates) != 15 { t.Error("Failed to load destinationrates: ", len(csvr.destinationRates)) } drs := csvr.destinationRates["RT_STANDARD"] @@ -609,7 +613,7 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadRatingPlans(t *testing.T) { - if len(csvr.ratingPlans) != 13 { + if len(csvr.ratingPlans) != 14 { t.Error("Failed to load rating plans: ", len(csvr.ratingPlans)) } rplan := csvr.ratingPlans["STANDARD"] @@ -781,7 +785,7 @@ func TestLoadRatingPlans(t *testing.T) { } func TestLoadRatingProfiles(t *testing.T) { - if len(csvr.ratingProfiles) != 21 { + if len(csvr.ratingProfiles) != 22 { t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles) } rp := csvr.ratingProfiles["*out:test:0:trp"] diff --git a/engine/timespans.go b/engine/timespans.go index a497ab4ef..24df295d7 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -263,11 +263,7 @@ func (ts *TimeSpan) getCost() float64 { ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) return ts.Cost } else { - cost := 0.0 - // some increments may have 0 cost because of the max cost strategy - for _, inc := range ts.Increments { - cost += inc.Cost - } + 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 { diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 813058f65..faa5c9b7d 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -233,6 +233,8 @@ 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{ diff --git a/utils/logger.go b/utils/logger.go index d5300b93b..cabbe068b 100644 --- a/utils/logger.go +++ b/utils/logger.go @@ -22,6 +22,7 @@ import ( "fmt" "log" "log/syslog" + "runtime" ) var Logger LoggerInterface @@ -85,3 +86,9 @@ func (sl *StdLogger) Warning(m string) (err error) { log.Print("[WARNING]" + m) return } + +func LogStack() { + buf := make([]byte, 300) + runtime.Stack(buf, false) + Logger.Debug(string(buf)) +}