diff --git a/engine/eventcost.go b/engine/eventcost.go index bf8381fe5..4bb2d6fc4 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -24,10 +24,10 @@ import ( "github.com/cgrates/cgrates/utils" ) -type RatingFilters map[string]*RatingMatchedFilters // so we can define search methods +type RatingFilters map[string]RatingMatchedFilters // so we can define search methods // GetWithSet attempts to retrieve the UUID of a matching data or create a new one -func (rfs RatingFilters) GetUUIDWithSet(rmf *RatingMatchedFilters) string { +func (rfs RatingFilters) GetUUIDWithSet(rmf RatingMatchedFilters) string { for k, v := range rfs { if v.Equals(rmf) { return k @@ -112,8 +112,8 @@ func NewEventCostFromCallCost(cc *CallCost, cgrID, runID string) (ec *EventCost) } for i, ts := range cc.Timespans { cIl := &ChargingInterval{StartTime: ts.TimeStart, CompressFactor: ts.CompressFactor} - rf := &RatingMatchedFilters{Subject: ts.MatchedSubject, DestinationPrefix: ts.MatchedPrefix, - DestinationID: ts.MatchedDestId, RatingPlanID: ts.RatingPlanId} + rf := RatingMatchedFilters{"Subject": ts.MatchedSubject, "DestinationPrefix": ts.MatchedPrefix, + "DestinationID": ts.MatchedDestId, "RatingPlanID": ts.RatingPlanId} cIl.RatingUUID = ec.ratingUUIDForRateInterval(ts.RateInterval, rf) if len(ts.Increments) != 0 { cIl.Increments = make([]*ChargingIncrement, len(ts.Increments)) @@ -177,7 +177,7 @@ type EventCost struct { Timings ChargedTimings } -func (ec *EventCost) ratingUUIDForRateInterval(ri *RateInterval, rf *RatingMatchedFilters) string { +func (ec *EventCost) ratingUUIDForRateInterval(ri *RateInterval, rf RatingMatchedFilters) string { if ri == nil || ri.Rating == nil { return "" } @@ -267,10 +267,10 @@ func (ec *EventCost) AsCallCost(ToR, Tenant, Direction, Category, Account, Subje if cIl.RatingUUID != "" { if ec.Rating[cIl.RatingUUID].RatingFiltersUUID != "" { rfs := ec.RatingFilters[ec.Rating[cIl.RatingUUID].RatingFiltersUUID] - ts.MatchedSubject = rfs.Subject - ts.MatchedPrefix = rfs.DestinationPrefix - ts.MatchedDestId = rfs.DestinationID - ts.RatingPlanId = rfs.RatingPlanID + ts.MatchedSubject = rfs["Subject"].(string) + ts.MatchedPrefix = rfs["DestinationPrefix"].(string) + ts.MatchedDestId = rfs["DestinationID"].(string) + ts.RatingPlanId = rfs["RatingPlanID"].(string) } } ts.RateInterval = ec.rateIntervalForRatingUUID(cIl.RatingUUID) @@ -302,18 +302,16 @@ func (ec *EventCost) AsCallCost(ToR, Tenant, Direction, Category, Account, Subje // ChargingInterval represents one interval out of Usage providing charging info // eg: PEAK vs OFFPEAK type ChargingInterval struct { - StartTime time.Time - IntervalDetailsUUID string // reference to CIntervDetails - RatingUUID string // reference to RatingUnit - Increments []*ChargingIncrement // specific increments applied to this interval - CompressFactor int - usage *time.Duration // cache usage computation for this interval - cost *float64 // cache cost calculation on this interval + StartTime time.Time + RatingUUID string // reference to RatingUnit + Increments []*ChargingIncrement // specific increments applied to this interval + CompressFactor int + usage *time.Duration // cache usage computation for this interval + cost *float64 // cache cost calculation on this interval } func (cIl *ChargingInterval) Equals(oCIl *ChargingInterval) (equals bool) { if equals = cIl.StartTime.Equal(oCIl.StartTime) && - cIl.IntervalDetailsUUID == oCIl.IntervalDetailsUUID && cIl.RatingUUID == oCIl.RatingUUID && len(cIl.Increments) == len(oCIl.Increments); !equals { return @@ -383,18 +381,17 @@ func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { bc.ExtraChargeUUID == oBC.ExtraChargeUUID } -type RatingMatchedFilters struct { - Subject string // matched subject - DestinationPrefix string // matched destination prefix - DestinationID string // matched destinationID - RatingPlanID string // matched ratingPlanID -} +type RatingMatchedFilters map[string]interface{} -func (rf *RatingMatchedFilters) Equals(oRF *RatingMatchedFilters) bool { - return rf.Subject == oRF.Subject && - rf.DestinationPrefix == oRF.DestinationPrefix && - rf.DestinationID == oRF.DestinationID && - rf.RatingPlanID == oRF.RatingPlanID +func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) (equals bool) { + equals = true + for k := range rf { + if rf[k] != oRF[k] { + equals = false + break + } + } + return } // ChargedTiming represents one timing attached to a charge @@ -433,5 +430,6 @@ func (ru *RatingUnit) Equals(oRU *RatingUnit) bool { ru.MaxCost == oRU.MaxCost && ru.MaxCostStrategy == oRU.MaxCostStrategy && ru.TimingUUID == oRU.TimingUUID && - ru.RatesUUID == oRU.RatesUUID + ru.RatesUUID == oRU.RatesUUID && + ru.RatingFiltersUUID == oRU.RatingFiltersUUID } diff --git a/engine/eventcost_test.go b/engine/eventcost_test.go new file mode 100644 index 000000000..21837a135 --- /dev/null +++ b/engine/eventcost_test.go @@ -0,0 +1,340 @@ +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ +package engine + +import ( + "testing" + "time" + + "github.com/cgrates/cgrates/utils" +) + +func TestNewEventCostFromCallCost(t *testing.T) { + cc := &CallCost{ + Direction: utils.META_OUT, + Category: "call", + Tenant: "cgrates.org", + Subject: "dan", + Account: "dan", + Destination: "+4986517174963", + TOR: utils.VOICE, + Cost: 0.85, + RatedUsage: 120.0, + Timespans: TimeSpans{ + &TimeSpan{ + TimeStart: time.Date(2017, 1, 9, 16, 18, 21, 0, time.UTC), + TimeEnd: time.Date(2017, 1, 9, 16, 19, 21, 0, time.UTC), + Cost: 0.25, + RateInterval: &RateInterval{ // standard rating + Timing: &RITiming{ + StartTime: "00:00:00", + }, + Rating: &RIRate{ + ConnectFee: 0.1, + RoundingMethod: "*up", + RoundingDecimals: 5, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.01, + RateUnit: time.Duration(1 * time.Second), + RateIncrement: time.Duration(1 * time.Minute), + }, + }, + }, + }, + DurationIndex: time.Duration(1 * time.Minute), + MatchedSubject: "*out:cgrates.org:call:*any", + MatchedPrefix: "+49", + MatchedDestId: "GERMANY", + RatingPlanId: "RPL_RETAIL1", + CompressFactor: 1, + Increments: Increments{ + &Increment{ // ConnectFee + Cost: 0.1, + BalanceInfo: &DebitInfo{ + Monetary: &MonetaryInfo{UUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + ID: utils.META_DEFAULT, + Value: 9.9}, + AccountID: "cgrates.org:dan", + }, + CompressFactor: 1, + }, + &Increment{ // First 30 seconds free + Duration: time.Duration(1 * time.Second), + Cost: 0, + BalanceInfo: &DebitInfo{ + Unit: &UnitInfo{ + UUID: "9d54a9e9-d610-4c82-bcb5-a315b9a65089", + ID: "free_mins", + Value: 0, + Consumed: 1.0, + TOR: utils.VOICE, + }, + AccountID: "cgrates.org:dan", + }, + CompressFactor: 30, + }, + &Increment{ // Minutes with special price + Duration: time.Duration(1 * time.Second), + Cost: 0.005, + BalanceInfo: &DebitInfo{ + Unit: &UnitInfo{ // Minutes with special price + UUID: "7a54a9e9-d610-4c82-bcb5-a315b9a65010", + ID: "discounted_mins", + Value: 0, + Consumed: 1.0, + TOR: utils.VOICE, + RateInterval: &RateInterval{ + Timing: &RITiming{ + StartTime: "00:00:00", + }, + Rating: &RIRate{ + ConnectFee: 0, + RoundingMethod: "*up", + RoundingDecimals: 5, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.005, + RateUnit: time.Duration(1 * time.Second), + RateIncrement: time.Duration(1 * time.Second), + }, + &Rate{ + GroupIntervalStart: time.Duration(60 * time.Second), + Value: 0.005, + RateUnit: time.Duration(1 * time.Second), + RateIncrement: time.Duration(1 * time.Second), + }, + }, + }, + }, + }, + Monetary: &MonetaryInfo{ + UUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + ID: utils.META_DEFAULT, + Value: 9.75}, + AccountID: "cgrates.org:dan", + }, + CompressFactor: 30, + }, + }, + }, + + &TimeSpan{ + TimeStart: time.Date(2017, 1, 9, 16, 19, 21, 0, time.UTC), + TimeEnd: time.Date(2017, 1, 9, 16, 20, 21, 0, time.UTC), + Cost: 0.01, + RateInterval: &RateInterval{ // standard rating + Timing: &RITiming{ + StartTime: "00:00:00", + }, + Rating: &RIRate{ + ConnectFee: 0.1, + RoundingMethod: "*up", + RoundingDecimals: 5, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.01, + RateUnit: time.Duration(1 * time.Second), + RateIncrement: time.Duration(1 * time.Minute), + }, + }, + }, + }, + DurationIndex: time.Duration(1 * time.Minute), + MatchedSubject: "*out:cgrates.org:call:*any", + MatchedPrefix: "+49", + MatchedDestId: "GERMANY", + RatingPlanId: "RPL_RETAIL1", + CompressFactor: 1, + Increments: Increments{ + &Increment{ + Cost: 0.01, + Duration: time.Duration(1 * time.Second), + BalanceInfo: &DebitInfo{ + Monetary: &MonetaryInfo{UUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + ID: utils.META_DEFAULT, + Value: 9.15}, + AccountID: "cgrates.org:dan", + }, + CompressFactor: 60, + }, + }, + }, + }, + } + + eEC := &EventCost{ + CGRID: "164b0422fdc6a5117031b427439482c6a4f90e41", + RunID: utils.META_DEFAULT, + Cost: utils.Float64Pointer(0.85), + Usage: utils.DurationPointer(time.Duration(2 * time.Minute)), + Charges: []*ChargingInterval{ + &ChargingInterval{ + StartTime: time.Date(2017, 1, 9, 16, 18, 21, 0, time.UTC), + RatingUUID: "bebf80cf-cba5-4e36-89dc-86673cff8cc4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(0), + Cost: 0.1, + BalanceChargeUUID: "716a278d-9ca5-451a-aa59-b6a43f4fb4ef", + CompressFactor: 1, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0, + BalanceChargeUUID: "8ee1f8ee-5783-487b-87e3-cb1bb6fd8f9f", + CompressFactor: 30, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.005, + BalanceChargeUUID: "77c904d4-c579-4687-8c28-a1561e39dae2", + CompressFactor: 30, + }, + }, + CompressFactor: 1, + }, + &ChargingInterval{ + StartTime: time.Date(2017, 1, 9, 16, 19, 21, 0, time.UTC), + RatingUUID: "bebf80cf-cba5-4e36-89dc-86673cff8cc4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(0), + Cost: 0.01, + BalanceChargeUUID: "79463f6e-d70f-41ac-9345-76bd21714759", + CompressFactor: 60, + }, + }, + CompressFactor: 1, + }, + }, + Rating: Rating{ + "bebf80cf-cba5-4e36-89dc-86673cff8cc4": &RatingUnit{ + ConnectFee: 0.1, + RoundingMethod: "*up", + RoundingDecimals: 5, + TimingUUID: "3e4c7dd1-10f9-4fdc-b7df-8833724933dd", + RatesUUID: "5f04c792-5c79-4873-ba39-413342671595", + RatingFiltersUUID: "8fa45f23-5bb1-44ee-867c-ad09b2bae981", + }, + "2b7333c0-479c-4e5d-8d72-d089e93b2b6a": &RatingUnit{ + RoundingMethod: "*up", + RoundingDecimals: 5, + TimingUUID: "3e4c7dd1-10f9-4fdc-b7df-8833724933dd", + RatesUUID: "3246cb23-ef2e-4080-ba5b-45300cbede3f", + RatingFiltersUUID: "8fa45f23-5bb1-44ee-867c-ad09b2bae981", + }, + }, + Accounting: Accounting{ + "2afef931-eb94-46df-8fb4-3509954e771c": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.005, + }, + "716a278d-9ca5-451a-aa59-b6a43f4fb4ef": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.1, + }, + "77c904d4-c579-4687-8c28-a1561e39dae2": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "7a54a9e9-d610-4c82-bcb5-a315b9a65010", + RatingUUID: "2b7333c0-479c-4e5d-8d72-d089e93b2b6a", + Units: 1, + ExtraChargeUUID: "2afef931-eb94-46df-8fb4-3509954e771c", + }, + "79463f6e-d70f-41ac-9345-76bd21714759": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.01, + }, + "8ee1f8ee-5783-487b-87e3-cb1bb6fd8f9f": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9d54a9e9-d610-4c82-bcb5-a315b9a65089", + Units: 1, + ExtraChargeUUID: "*none", + }, + }, + RatingFilters: RatingFilters{ + "8fa45f23-5bb1-44ee-867c-ad09b2bae981": RatingMatchedFilters{ + "DestinationID": "GERMANY", + "DestinationPrefix": "+49", + "RatingPlanID": "RPL_RETAIL1", + "Subject": "*out:cgrates.org:call:*any", + }, + }, + Rates: ChargedRates{ + "3246cb23-ef2e-4080-ba5b-45300cbede3f": RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.01, + RateIncrement: time.Duration(1 * time.Minute), + RateUnit: time.Duration(1 * time.Second)}, + }, + "5f04c792-5c79-4873-ba39-413342671595": RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.005, + RateIncrement: time.Duration(1 * time.Second), + RateUnit: time.Duration(1 * time.Second)}, + &Rate{ + GroupIntervalStart: time.Duration(60 * time.Second), + Value: 0.005, + RateIncrement: time.Duration(1 * time.Second), + RateUnit: time.Duration(1 * time.Second)}, + }, + }, + Timings: ChargedTimings{ + "3e4c7dd1-10f9-4fdc-b7df-8833724933dd": &ChargedTiming{ + StartTime: "00:00:00", + }, + }, + } + ec := NewEventCostFromCallCost(cc, "164b0422fdc6a5117031b427439482c6a4f90e41", utils.META_DEFAULT) + if cost := ec.ComputeCost(); cost != cc.Cost { + t.Errorf("Expecting: %f, received: %f", cc.Cost, cost) + } + eUsage := time.Duration(int64(cc.RatedUsage * 1000000000)) + if usage := ec.ComputeUsage(); usage != eUsage { + t.Errorf("Expecting: %v, received: %v", eUsage, usage) + } + if len(ec.Charges) != len(eEC.Charges) { + t.Errorf("Expecting: %+v, received: %+v", eEC, ec) + } + for i := range eEC.Charges { + if len(eEC.Charges[i].Increments) != len(ec.Charges[i].Increments) { + t.Errorf("At index %d, expecting: %+v, received: %+v", eEC.Charges[i].Increments, ec.Charges[i].Increments) + } + } + if len(ec.Rating) != len(eEC.Rating) { + t.Errorf("Expecting: %+v, received: %+v", eEC, ec) + } + if len(ec.Accounting) != len(eEC.Accounting) { + t.Errorf("Expecting: %+v, received: %+v", eEC, ec) + } + if len(ec.Rates) != len(eEC.Rates) { + t.Errorf("Expecting: %+v, received: %+v", eEC, ec) + } + if len(ec.Timings) != len(eEC.Timings) { + t.Errorf("Expecting: %+v, received: %+v", eEC, ec) + } + +}