diff --git a/engine/eventcost.go b/engine/eventcost.go index 720b37e66..aca8c0718 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -344,18 +344,46 @@ func (ec *EventCost) accountingGetIDFromEventCost(oEC *EventCost, oAccountingID return ec.Accounting.GetIDWithSet(oBC) } -// appendCIl appends a ChargingInterval to existing chargers, no compression done +// appendCIl appends a ChargingInterval to existing chargers +// no compression done at ChargingInterval level, attempted on ChargingIncrement level func (ec *EventCost) appendCIlFromEC(oEC *EventCost, cIlIdx int) { cIl := oEC.Charges[cIlIdx] cIl.RatingID = ec.ratingGetIDFomEventCost(oEC, cIl.RatingID) - for _, cIt := range cIl.Increments { - cIt.AccountingID = ec.accountingGetIDFromEventCost(oEC, cIt.AccountingID) + lastCIl := ec.Charges[len(ec.Charges)-1] + lastCIt := lastCIl.Increments[len(lastCIl.Increments)-1] + appendChargingIncrement := lastCIl.CompressFactor == 1 && + lastCIl.RatingID == cIl.RatingID // attempt compressing of the ChargingIncrements + var idxFirstCIt *int // keep here the reference towards last not appended charging increment so we can create separate ChargingInterval + var idxLastCF *int // reference towards last compress not absorbed by ec.Charges + for cF := cIl.CompressFactor; cF > 0; cF-- { + for i, cIt := range cIl.Increments { + cIt.AccountingID = ec.accountingGetIDFromEventCost(oEC, cIt.AccountingID) + if idxFirstCIt != nil { + continue + } + if !appendChargingIncrement || + !lastCIt.PartiallyEquals(cIt) { + idxFirstCIt = utils.IntPointer(i) + idxLastCF = utils.IntPointer(cF) + continue + } + lastCIt.CompressFactor += cIt.CompressFactor // compress the iterated ChargingIncrement + } + } + if idxFirstCIt != nil { // CIt was not completely absorbed + cIlCln := cIl.Clone() + cIlCln.CompressFactor = 1 + cIlCln.Increments = cIlCln.Increments[*idxFirstCIt:] + ec.Charges = append(ec.Charges, cIlCln) + if *idxLastCF > 1 { // add the remaining part out of original ChargingInterval + cIl.CompressFactor = *idxLastCF - 1 + ec.Charges = append(ec.Charges, cIl) + } } - ec.Charges = append(ec.Charges, cIl) } // AppendChargingInterval appends or compresses a &ChargingInterval to existing ec.Chargers -func (ec *EventCost) AppendChargingIntervalFromEventCost(oEC *EventCost, cIlIdx int) { +func (ec *EventCost) appendChargingIntervalFromEventCost(oEC *EventCost, cIlIdx int) { lenChargers := len(ec.Charges) if lenChargers != 0 && ec.Charges[lenChargers-1].PartiallyEquals(oEC.Charges[cIlIdx]) { ec.Charges[lenChargers-1].CompressFactor += 1 @@ -369,7 +397,7 @@ func (ec *EventCost) Merge(ecs ...*EventCost) { for _, newEC := range ecs { ec.AccountSummary = newEC.AccountSummary // updated AccountSummary information for cIlIdx := range newEC.Charges { - ec.AppendChargingIntervalFromEventCost(newEC, cIlIdx) + ec.appendChargingIntervalFromEventCost(newEC, cIlIdx) } } ec.ResetCounters() diff --git a/engine/eventcost_test.go b/engine/eventcost_test.go index 1c187694d..f3c98c660 100644 --- a/engine/eventcost_test.go +++ b/engine/eventcost_test.go @@ -1225,18 +1225,6 @@ func TestECTrimMUsage(t *testing.T) { } }) } - /* - - ec = testEC.Clone() - atUsage = time.Duration(61 * time.Second) - srplsEC, _ = ec.Trim(atUsage) - if ec.GetUsage() != atUsage { - t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec)) - } - if srplsEC.GetUsage() != time.Duration(239*time.Second) { - t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC)) - } - */ } /* @@ -1290,3 +1278,327 @@ func TestECMerge(t *testing.T) { } } */ + +func TestECMergeGT(t *testing.T) { + // InitialEventCost + ecGT := &EventCost{ + CGRID: "7636f3f1a06dffa038ba7900fb57f52d28830a24", + RunID: utils.META_DEFAULT, + StartTime: time.Date(2018, 7, 27, 0, 59, 21, 0, time.UTC), + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "cc68da4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "0d87a64", + CompressFactor: 103, + }, + }, + CompressFactor: 1, + }, + }, + AccountSummary: &AccountSummary{ + Tenant: "cgrates.org", + ID: "dan", + BalanceSummaries: []*BalanceSummary{ + &BalanceSummary{ + UUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + ID: "addon_data", + Type: utils.DATA, + Value: 10726871040}, + }, + }, + Rating: Rating{ + "cc68da4": &RatingUnit{ + RatesID: "06dee2e", + RatingFiltersID: "216b0a5", + }, + }, + Accounting: Accounting{ + "0d87a64": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "216b0a5": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "06dee2e": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + ecGTUpdt := &EventCost{ + CGRID: "7636f3f1a06dffa038ba7900fb57f52d28830a24", + RunID: utils.META_DEFAULT, + StartTime: time.Date(2018, 7, 27, 0, 59, 38, 0105472, time.UTC), + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "6a83227", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "9288f93", + CompressFactor: 84, + }, + }, + CompressFactor: 1, + }, + }, + AccountSummary: &AccountSummary{ + Tenant: "cgrates.org", + ID: "dan", + BalanceSummaries: []*BalanceSummary{ + &BalanceSummary{ + UUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + ID: "addon_data", + Type: utils.DATA, + Value: 10718269440}, + }, + }, + Rating: Rating{ + "6a83227": &RatingUnit{ + RatesID: "52f8b0f", + RatingFiltersID: "17f7216", + }, + }, + Accounting: Accounting{ + "9288f93": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "17f7216": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "52f8b0f": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + ecGT.Merge(ecGTUpdt) + ecExpct := &EventCost{ + CGRID: "7636f3f1a06dffa038ba7900fb57f52d28830a24", + RunID: utils.META_DEFAULT, + StartTime: time.Date(2018, 7, 27, 0, 59, 21, 0, time.UTC), + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "cc68da4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "0d87a64", + CompressFactor: 187, + }, + }, + CompressFactor: 1, + }, + }, + AccountSummary: &AccountSummary{ + Tenant: "cgrates.org", + ID: "dan", + BalanceSummaries: []*BalanceSummary{ + &BalanceSummary{ + UUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + ID: "addon_data", + Type: utils.DATA, + Value: 10718269440}, + }, + }, + Rating: Rating{ + "cc68da4": &RatingUnit{ + RatesID: "06dee2e", + RatingFiltersID: "216b0a5", + }, + }, + Accounting: Accounting{ + "0d87a64": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "216b0a5": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "06dee2e": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + if len(ecGT.Charges) != len(ecExpct.Charges) || + !reflect.DeepEqual(ecGT.Charges[0].TotalUsage(), ecExpct.Charges[0].TotalUsage()) || + !reflect.DeepEqual(ecGT.Charges[0].TotalCost(), ecExpct.Charges[0].TotalCost()) { + t.Errorf("expecting: %s\n\n, received: %s", + utils.ToJSON(ecExpct), utils.ToJSON(ecGT)) + } +} + +func TestECAppendCIlFromEC(t *testing.T) { + ec := &EventCost{ + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "cc68da4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "0d87a64", + CompressFactor: 103, + }, + }, + CompressFactor: 1, + }, + }, + Rating: Rating{ + "cc68da4": &RatingUnit{ + RatesID: "06dee2e", + RatingFiltersID: "216b0a5", + }, + }, + Accounting: Accounting{ + "0d87a64": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "216b0a5": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "06dee2e": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + oEC := &EventCost{ + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "6a83227", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "9288f93", + CompressFactor: 84, + }, + }, + CompressFactor: 1, + }, + }, + Rating: Rating{ + "6a83227": &RatingUnit{ + RatesID: "52f8b0f", + RatingFiltersID: "17f7216", + }, + }, + Accounting: Accounting{ + "9288f93": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "17f7216": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "52f8b0f": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + ec.appendCIlFromEC(oEC, 0) + eEC := &EventCost{ + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingID: "cc68da4", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(102400), + AccountingID: "0d87a64", + CompressFactor: 187, + }, + }, + CompressFactor: 1, + }, + }, + Rating: Rating{ + "cc68da4": &RatingUnit{ + RatesID: "06dee2e", + RatingFiltersID: "216b0a5", + }, + }, + Accounting: Accounting{ + "0d87a64": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + Units: 102400, + ExtraChargeID: utils.META_NONE, + }, + }, + RatingFilters: RatingFilters{ + "216b0a5": RatingMatchedFilters{ + "DestinationID": utils.META_ANY, + "DestinationPrefix": "42502", + "RatingPlanID": utils.META_NONE, + "Subject": "9a767726-fe69-4940-b7bd-f43de9f0f8a5", + }, + }, + Rates: ChargedRates{ + "06dee2e": RateGroups{ + &Rate{ + RateIncrement: time.Duration(102400), + RateUnit: time.Duration(102400)}, + }, + }, + } + if !reflect.DeepEqual(eEC, ec) { + t.Errorf("expecting: %s, received: %s", utils.ToJSON(eEC), utils.ToJSON(ec)) + } +} diff --git a/engine/libeventcost.go b/engine/libeventcost.go index dce1b07f7..3f2f2cfbb 100644 --- a/engine/libeventcost.go +++ b/engine/libeventcost.go @@ -36,18 +36,17 @@ type ChargingInterval struct { } // PartiallyEquals does not compare CompressFactor, usefull for Merge -func (cIl *ChargingInterval) PartiallyEquals(oCIl *ChargingInterval) (equals bool) { - if equals = cIl.RatingID == oCIl.RatingID && +func (cIl *ChargingInterval) PartiallyEquals(oCIl *ChargingInterval) bool { + if equals := cIl.RatingID == oCIl.RatingID && len(cIl.Increments) == len(oCIl.Increments); !equals { - return + return false } for i := range cIl.Increments { if !cIl.Increments[i].Equals(oCIl.Increments[i]) { - equals = false - break + return false } } - return + return true } // Usage computes the total usage of this ChargingInterval, ignoring CompressFactor @@ -136,6 +135,13 @@ func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { cIt.CompressFactor == oCIt.CompressFactor } +// PartiallyEquals ignores the CompressFactor when comparing +func (cIt *ChargingIncrement) PartiallyEquals(oCIt *ChargingIncrement) bool { + return cIt.Usage == oCIt.Usage && + cIt.Cost == oCIt.Cost && + cIt.AccountingID == oCIt.AccountingID +} + func (cIt *ChargingIncrement) Clone() (cln *ChargingIncrement) { cln = new(ChargingIncrement) *cln = *cIt @@ -161,11 +167,19 @@ type BalanceCharge struct { } func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { + bcExtraChargeID := bc.ExtraChargeID + if bcExtraChargeID == "" { + bcExtraChargeID = utils.META_NONE + } + oBCExtraChargerID := oBC.ExtraChargeID + if oBCExtraChargerID == "" { // so we can compare them properly + oBCExtraChargerID = utils.META_NONE + } return bc.AccountID == oBC.AccountID && bc.BalanceUUID == oBC.BalanceUUID && bc.RatingID == oBC.RatingID && bc.Units == oBC.Units && - bc.ExtraChargeID == oBC.ExtraChargeID + bcExtraChargeID == oBCExtraChargerID } func (bc *BalanceCharge) Clone() *BalanceCharge { @@ -176,15 +190,13 @@ func (bc *BalanceCharge) Clone() *BalanceCharge { type RatingMatchedFilters map[string]interface{} -func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) (equals bool) { - equals = true +func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) bool { for k := range rf { if rf[k] != oRF[k] { - equals = false - break + return false } } - return + return true } func (rf RatingMatchedFilters) Clone() (cln map[string]interface{}) {