From 8421d74d850f931a711d09247be1fcbb5bf91d70 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 25 May 2017 19:16:15 +0200 Subject: [PATCH] EventCost.Trim in middle with tests, libeventcost --- engine/eventcost.go | 542 ++++++++++----------------------------- engine/eventcost_test.go | 457 ++++++++++++++++++++++----------- engine/libeventcost.go | 367 ++++++++++++++++++++++++++ utils/coreutils.go | 2 +- 4 files changed, 802 insertions(+), 566 deletions(-) create mode 100644 engine/libeventcost.go diff --git a/engine/eventcost.go b/engine/eventcost.go index d30482dc8..1ecf8aa7b 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -18,141 +18,12 @@ along with this program. If not, see package engine import ( + "errors" "time" "github.com/cgrates/cgrates/utils" ) -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 { - if rmf == nil || len(rmf) == 0 { - return "" - } - for k, v := range rfs { - if v.Equals(rmf) { - return k - } - } - // not found, set it here - uuid := utils.UUIDSha1Prefix() - rfs[uuid] = rmf - return uuid -} - -func (rfs RatingFilters) Clone() (cln RatingFilters) { - cln = make(RatingFilters, len(rfs)) - for k, v := range rfs { - cln[k] = v.Clone() - } - return -} - -type Rating map[string]*RatingUnit - -// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one -func (crus Rating) GetUUIDWithSet(cru *RatingUnit) string { - if cru == nil { - return "" - } - for k, v := range crus { - if v.Equals(cru) { - return k - } - } - // not found, set it here - uuid := utils.UUIDSha1Prefix() - crus[uuid] = cru - return uuid -} - -func (crus Rating) Clone() (cln Rating) { - cln = make(Rating, len(crus)) - for k, v := range crus { - cln[k] = v.Clone() - } - return -} - -type ChargedRates map[string]RateGroups - -// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one -func (crs ChargedRates) GetUUIDWithSet(rg RateGroups) string { - if rg == nil || len(rg) == 0 { - return "" - } - for k, v := range crs { - if v.Equals(rg) { - return k - } - } - // not found, set it here - uuid := utils.UUIDSha1Prefix() - crs[uuid] = rg - return uuid -} - -func (crs ChargedRates) Clone() (cln ChargedRates) { - cln = make(ChargedRates, len(crs)) - for k, v := range crs { - cln[k] = v.Clone() - } - return -} - -type ChargedTimings map[string]*ChargedTiming - -// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one -func (cts ChargedTimings) GetUUIDWithSet(ct *ChargedTiming) string { - if ct == nil { - return "" - } - for k, v := range cts { - if v.Equals(ct) { - return k - } - } - // not found, set it here - uuid := utils.UUIDSha1Prefix() - cts[uuid] = ct - return uuid -} - -func (cts ChargedTimings) Clone() (cln ChargedTimings) { - cln = make(ChargedTimings, len(cts)) - for k, v := range cts { - cln[k] = v.Clone() - } - return -} - -type Accounting map[string]*BalanceCharge - -// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one -func (cbs Accounting) GetUUIDWithSet(cb *BalanceCharge) string { - if cb == nil { - return "" - } - for k, v := range cbs { - if v.Equals(cb) { - return k - } - } - // not found, set it here - uuid := utils.UUIDSha1Prefix() - cbs[uuid] = cb - return uuid -} - -func (cbs Accounting) Clone() (cln Accounting) { - cln = make(Accounting, len(cbs)) - for k, v := range cbs { - cln[k] = v.Clone() - } - return -} - func NewBareEventCost() *EventCost { return &EventCost{ Rating: make(Rating), @@ -433,6 +304,9 @@ func (ec *EventCost) AsCallCost() *CallCost { // ratingGetUUIDFomEventCost retrieves UUID based on data from another EventCost func (ec *EventCost) ratingGetUUIDFomEventCost(oEC *EventCost, oRatingUUID string) string { + if oRatingUUID == "" { + return "" + } oCIlRating := oEC.Rating[oRatingUUID].Clone() // clone so we don't influence the original data oCIlRating.TimingUUID = ec.Timings.GetUUIDWithSet(oEC.Timings[oCIlRating.TimingUUID]) oCIlRating.RatingFiltersUUID = ec.RatingFilters.GetUUIDWithSet(oEC.RatingFilters[oCIlRating.RatingFiltersUUID]) @@ -442,11 +316,12 @@ func (ec *EventCost) ratingGetUUIDFomEventCost(oEC *EventCost, oRatingUUID strin // accountingGetUUIDFromEventCost retrieves UUID based on data from another EventCost func (ec *EventCost) accountingGetUUIDFromEventCost(oEC *EventCost, oBalanceChargeUUID string) string { + if oBalanceChargeUUID == "" { + return "" + } oBC := oEC.Accounting[oBalanceChargeUUID].Clone() oBC.RatingUUID = ec.ratingGetUUIDFomEventCost(oEC, oBC.RatingUUID) - if oBC.ExtraChargeUUID != "" { - oBC.ExtraChargeUUID = ec.accountingGetUUIDFromEventCost(oEC, oBC.ExtraChargeUUID) - } + oBC.ExtraChargeUUID = ec.accountingGetUUIDFromEventCost(oEC, oBC.ExtraChargeUUID) return ec.Accounting.GetUUIDWithSet(oBC) } @@ -488,6 +363,7 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error if ec.Usage == nil { ec.ComputeUsage() } + origECUsage := ec.ComputeUsage() if atUsage >= *ec.Usage { return // no trim } @@ -500,295 +376,141 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error ec.AccountSummary = srplusEC.AccountSummary.Clone() return // trim all, fresh EC with 0 usage } - /* - var lastActiveCIlIdx *int // marks last index which should stay with ec - for i, cIl := range ec.Charges { - if cIl.ecUsageIdx == nil { - ec.ComputeEventCostUsageIndexes() - } - if *cIl.ecUsageIdx >= atUsage { - lastActiveCIlIdx = utils.IntPointer(i - 1) + + srplusEC = NewBareEventCost() + srplusEC.CGRID = ec.CGRID + srplusEC.RunID = ec.RunID + srplusEC.StartTime = ec.StartTime + srplusEC.AccountSummary = ec.AccountSummary.Clone() + + var lastActiveCIlIdx *int // marks last index which should stay with ec + for i, cIl := range ec.Charges { + if cIl.ecUsageIdx == nil { + ec.ComputeEventCostUsageIndexes() + } + if cIl.usage == nil { + ec.ComputeUsage() + } + if *cIl.ecUsageIdx+*cIl.TotalUsage() >= atUsage { + lastActiveCIlIdx = utils.IntPointer(i) + break + } + } + if lastActiveCIlIdx == nil { + return nil, errors.New("cannot find last active ChargingInterval") + } + lastActiveCIl := ec.Charges[*lastActiveCIlIdx] + if *lastActiveCIl.ecUsageIdx >= atUsage { + return nil, errors.New("failed detecting last active ChargingInterval") + } else if lastActiveCIl.CompressFactor == 0 { + return nil, errors.New("ChargingInterval with 0 compressFactor") + } + srplusEC.Charges = ec.Charges[*lastActiveCIlIdx+1:] + ec.Charges = ec.Charges[:*lastActiveCIlIdx+1] + + if lastActiveCIl.CompressFactor != 1 { // Split based on compress factor if needed + var laCF int + for ciCnt := 1; ciCnt <= lastActiveCIl.CompressFactor; ciCnt++ { + if *lastActiveCIl.ecUsageIdx+ + time.Duration(lastActiveCIl.usage.Nanoseconds()*int64(ciCnt)) > atUsage { + laCF = ciCnt break } } - if lastActiveCIlIdx == nil { - return + if laCF == 0 { + return nil, errors.New("cannot detect last active CompressFactor in ChargingInterval") } - if *lastActiveCIlIdx == -1 { // trim full usage - srplusEC = NewBareEventCost() - *srplusEC = *ec // no need of cloning since we will not keep info in ec - ec = NewBareEventCost() - ec.CGRID = srplusEC.CGRID - ec.RunID = srplusEC.RunID - return + + if laCF != lastActiveCIl.CompressFactor { + srplsCIl := lastActiveCIl.Clone() + srplsCIl.CompressFactor = lastActiveCIl.CompressFactor - laCF + srplusEC.Charges = append([]*ChargingInterval{srplsCIl}, srplusEC.Charges...) // prepend surplus CIl + lastActiveCIl.CompressFactor = laCF // correct compress factor } - /* - lastActiveCIl := ec.Charges[lastActiveCIlIdx] - if lastActiveCI.ecUsageIdx >= atUsage { - return nil, errors.New("failed detecting last active ChargingInterval") - } else if lastActiveCI.CompressFactor == 0 { - return nil, errors.New("ChargingInterval with 0 compressFactor") - } - - srplsCIl := new(ChargingInterval) - srplsCIl.RatingUUID = lastActiveCIl.RatingUUID - if lastActiveCI.CompressFactor != 1 { - var laCF int - for ciCnt := 1; ciCnt <= lastActiveCI.CompressFactor; ciCnt++ { - if *lastActiveCI.ecUsageIdx.Add( - time.Duration(lastActiveCI.Usage.Nanoseconds() * int64(ciCnt))) > atUsage { - laCF = ciCnt - break - } - } - if laCF == 0 { - return nil, errors.New("cannot detect last active CompressFactor in ChargingInterval") - } - lastActiveCIl.CompressFactor = laCF // this factor will stay uncompressed - - } - - var lastActiveCItIdx *int - cIlUIdx := ec.Charges[lastActiveCIlIdx].ecUsageIdx - for i, cIt := range ec.Charges[lastActiveCIlIdx].Increments { - if cIlUIdx.Add(cIt.Usage) > atUsage { - lastActiveCItIdx = utils.IntPointer(i) - break - } - } - if lastActiveCItIdx == nil { // bug in increments - return nil, errors.New("no active increment found") - } - ec.ResetCounters() // avoid stale counters - var ciUncompressed bool // marks whether we needed to uncomrpess the last ChargingInterval - - - if ec.Charges[lastActiveCIlIdx]. - srplusEC = NewBareEventCost() - srplusEC.CGRID = ec.CGRID - srplusEC.RunID = ec.RunID - var laCIlIncrmts []*ChargingIncrement // surplus increments in the last active ChargingInterval - for _, cIl := range ec.Charges[lastActiveCIlIdx].Increments[*lastActiveCItIdx+1:] { - laCIlIncrmts = append(laCIlIncrmts, cIl) - } - srplusEC.Charges = []*ChargingInterval{} - ec.Charges[lastActiveCIlIdx].Increments = ec.Charges[lastActiveCIlIdx].Increments[:*lastActiveCItIdx+1] // remove srplusEC increments out of last active CIl - for _, cIl := range ec.Charges[lastActiveCIlIdx+1:] { - - } - */ - return -} - -// ChargingInterval represents one interval out of Usage providing charging info -// eg: PEAK vs OFFPEAK -type ChargingInterval struct { - RatingUUID string // reference to RatingUnit - Increments []*ChargingIncrement // specific increments applied to this interval - CompressFactor int - usage *time.Duration // cache usage computation for this interval - ecUsageIdx *time.Duration // computed value of totalUsage at the starting of the interval - cost *float64 // cache cost calculation on this interval - -} - -// PartiallyEquals does not compare CompressFactor, usefull for Merge -func (cIl *ChargingInterval) PartiallyEquals(oCIl *ChargingInterval) (equals bool) { - if equals = cIl.RatingUUID == oCIl.RatingUUID && - len(cIl.Increments) == len(oCIl.Increments); !equals { - return } - for i := range cIl.Increments { - if !cIl.Increments[i].Equals(oCIl.Increments[i]) { - equals = false + atUsage = atUsage - time.Duration(lastActiveCIl.ecUsageIdx.Nanoseconds()*int64(lastActiveCIl.CompressFactor)) // remaining duration to cover in increments + + // find out last increment covering duration + var lastActiveCItIdx *int + var incrementsUsage time.Duration + for i, cIt := range lastActiveCIl.Increments { + incrementsUsage += cIt.TotalUsage() + if incrementsUsage > atUsage { + lastActiveCItIdx = utils.IntPointer(i) break } } - return -} + if lastActiveCItIdx == nil { // bug in increments + return nil, errors.New("no active increment found") + } + lastActiveCIts := lastActiveCIl.Increments // so we can modify the reference in case we have surplus + lastIncrement := lastActiveCIts[*lastActiveCItIdx] -// Usage computes the total usage of this ChargingInterval, ignoring CompressFactor -func (cIl *ChargingInterval) Usage() *time.Duration { - if cIl.usage == nil { - var usage time.Duration + if lastIncrement.CompressFactor == 0 { + return nil, errors.New("empty compress factor in increment") + } + + var srplsIncrements []*ChargingIncrement + if *lastActiveCItIdx < len(lastActiveCIl.Increments)-1 { // less that complete increments, have surplus + srplsIncrements = lastActiveCIts[*lastActiveCItIdx+1:] + lastActiveCIts = lastActiveCIts[:*lastActiveCItIdx+1] + } + var laItCF int + if lastIncrement.CompressFactor != 1 { // detect the increment covering the last part of usage + incrementsUsage -= lastIncrement.TotalUsage() + for cnt := 1; cnt <= lastIncrement.CompressFactor; cnt++ { + incrementsUsage += lastIncrement.Usage + if incrementsUsage >= atUsage { + laItCF = cnt + break + } + } + if laItCF == 0 { + return nil, errors.New("cannot detect last active CompressFactor in ChargingIncrement") + } + if laItCF != lastIncrement.CompressFactor { + srplsIncrement := lastIncrement.Clone() + srplsIncrement.CompressFactor = srplsIncrement.CompressFactor - laItCF + srplsIncrements = append([]*ChargingIncrement{srplsIncrement}, srplsIncrements...) // prepend the surplus out of compress + //lastIncrement.CompressFactor = laItCF + } + } + + if len(srplsIncrements) != 0 { // partially covering, need trim + + if lastActiveCIl.CompressFactor > 1 { // ChargingInterval not covering in full, need to split it + lastActiveCIl.CompressFactor -= 1 + ec.Charges = append(ec.Charges, lastActiveCIl.Clone()) + lastActiveCIl = ec.Charges[len(ec.Charges)-1] + lastActiveCIl.CompressFactor = 1 + } + srplsCIl := lastActiveCIl.Clone() + srplsCIl.Increments = srplsIncrements + srplusEC.Charges = append([]*ChargingInterval{srplsCIl}, srplusEC.Charges...) + + lastActiveCIl.Increments = make([]*ChargingIncrement, len(lastActiveCIts)) + for i, incr := range lastActiveCIts { + lastActiveCIl.Increments[i] = incr.Clone() // avoid pointer references to the other interval + } + if laItCF != 0 { + lastActiveCIl.Increments[len(lastActiveCIl.Increments)-1].CompressFactor = laItCF // correct the compressFactor for the last increment + } + } + ec.ResetCounters() + if usage := ec.ComputeUsage(); usage < atUsage { + return nil, errors.New("usage of EventCost smaller than requested") + } + srplusEC.ResetCounters() + srplusEC.StartTime = ec.StartTime.Add(ec.ComputeUsage()) + if srplsUsage := srplusEC.ComputeUsage(); srplsUsage > origECUsage-atUsage { + return nil, errors.New("surplus EventCost too big") + } + // close surplus with missing cache + for _, cIl := range srplusEC.Charges { + cIl.RatingUUID = srplusEC.ratingGetUUIDFomEventCost(ec, cIl.RatingUUID) for _, incr := range cIl.Increments { - usage += time.Duration(incr.Usage.Nanoseconds() * int64(incr.CompressFactor)) - } - cIl.usage = &usage - } - return cIl.usage -} - -// TotalUsage returns the total usage of this interval, considering compress factor -func (cIl *ChargingInterval) TotalUsage() (tu *time.Duration) { - usage := cIl.Usage() - if usage == nil { - return - } - tu = new(time.Duration) - *tu = time.Duration(usage.Nanoseconds() * int64(cIl.CompressFactor)) - return -} - -// EventCostUsageIndex publishes the value of ecUsageIdx -func (cIl *ChargingInterval) EventCostUsageIndex() *time.Duration { - return cIl.ecUsageIdx -} - -// StartTime computes a StartTime based on EventCost.Start time and ecUsageIdx -func (cIl *ChargingInterval) StartTime(ecST time.Time) (st time.Time) { - if cIl.ecUsageIdx != nil { - st = ecST.Add(*cIl.ecUsageIdx) - } - return -} - -// EndTime computes an EndTime based on ChargingInterval StartTime value and usage -func (cIl *ChargingInterval) EndTime(cIlST time.Time) (et time.Time) { - return cIlST.Add(time.Duration(cIl.Usage().Nanoseconds() * int64(cIl.CompressFactor))) -} - -// Cost computes the total cost on this ChargingInterval -func (cIl *ChargingInterval) Cost() float64 { - if cIl.cost == nil { - var cost float64 - for _, incr := range cIl.Increments { - cost += incr.Cost * float64(incr.CompressFactor) - } - cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) - cIl.cost = &cost - } - return *cIl.cost -} - -// Clone returns a new instance of ChargingInterval with independent data -func (cIl *ChargingInterval) Clone() (cln *ChargingInterval) { - cln = new(ChargingInterval) - cln.RatingUUID = cIl.RatingUUID - cln.CompressFactor = cIl.CompressFactor - cln.Increments = make([]*ChargingIncrement, len(cIl.Increments)) - for i, cIt := range cIl.Increments { - cln.Increments[i] = cIt.Clone() - } - return -} - -// ChargingIncrement represents one unit charged inside an interval -type ChargingIncrement struct { - Usage time.Duration - Cost float64 - BalanceChargeUUID string - CompressFactor int -} - -func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { - return cIt.Usage == oCIt.Usage && - cIt.Cost == oCIt.Cost && - cIt.BalanceChargeUUID == oCIt.BalanceChargeUUID && - cIt.CompressFactor == oCIt.CompressFactor -} - -func (cIt *ChargingIncrement) Clone() (cln *ChargingIncrement) { - cln = new(ChargingIncrement) - *cln = *cIt - return -} - -// TotalUsage returns the total usage of the increment, considering compress factor -func (cIt *ChargingIncrement) TotalUsage() time.Duration { - return time.Duration(cIt.Usage.Nanoseconds() * int64(cIt.CompressFactor)) -} - -// BalanceCharge represents one unit charged to a balance -type BalanceCharge struct { - AccountID string // keep reference for shared balances - BalanceUUID string // balance charged - RatingUUID string // special price applied on this balance - Units float64 // number of units charged - ExtraChargeUUID string // used in cases when paying *voice with *monetary -} - -func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { - return bc.AccountID == oBC.AccountID && - bc.BalanceUUID == oBC.BalanceUUID && - bc.RatingUUID == oBC.RatingUUID && - bc.Units == oBC.Units && - bc.ExtraChargeUUID == oBC.ExtraChargeUUID -} - -func (bc *BalanceCharge) Clone() *BalanceCharge { - clnBC := new(BalanceCharge) - *clnBC = *bc - return clnBC -} - -type RatingMatchedFilters map[string]interface{} - -func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) (equals bool) { - equals = true - for k := range rf { - if rf[k] != oRF[k] { - equals = false - break + incr.BalanceChargeUUID = srplusEC.accountingGetUUIDFromEventCost(ec, incr.BalanceChargeUUID) } } return } - -func (rf RatingMatchedFilters) Clone() (cln map[string]interface{}) { - cln = make(map[string]interface{}) - utils.Clone(rf, &cln) - return -} - -// ChargedTiming represents one timing attached to a charge -type ChargedTiming struct { - Years utils.Years - Months utils.Months - MonthDays utils.MonthDays - WeekDays utils.WeekDays - StartTime string -} - -func (ct *ChargedTiming) Equals(oCT *ChargedTiming) bool { - return ct.Years.Equals(oCT.Years) && - ct.Months.Equals(oCT.Months) && - ct.MonthDays.Equals(oCT.MonthDays) && - ct.WeekDays.Equals(oCT.WeekDays) && - ct.StartTime == oCT.StartTime -} - -func (ct *ChargedTiming) Clone() (cln *ChargedTiming) { - cln = new(ChargedTiming) - *cln = *ct - return -} - -// RatingUnit represents one unit out of RatingPlan matching for an event -type RatingUnit struct { - ConnectFee float64 - RoundingMethod string - RoundingDecimals int - MaxCost float64 - MaxCostStrategy string - TimingUUID string // This RatingUnit is bounded to specific timing profile - RatesUUID string - RatingFiltersUUID string -} - -func (ru *RatingUnit) Equals(oRU *RatingUnit) bool { - return ru.ConnectFee == oRU.ConnectFee && - ru.RoundingMethod == oRU.RoundingMethod && - ru.RoundingDecimals == oRU.RoundingDecimals && - ru.MaxCost == oRU.MaxCost && - ru.MaxCostStrategy == oRU.MaxCostStrategy && - ru.TimingUUID == oRU.TimingUUID && - ru.RatesUUID == oRU.RatesUUID && - ru.RatingFiltersUUID == oRU.RatingFiltersUUID -} - -func (ru *RatingUnit) Clone() (cln *RatingUnit) { - cln = new(RatingUnit) - *cln = *ru - return -} diff --git a/engine/eventcost_test.go b/engine/eventcost_test.go index 334544532..aaca55654 100644 --- a/engine/eventcost_test.go +++ b/engine/eventcost_test.go @@ -25,6 +25,189 @@ import ( "github.com/cgrates/cgrates/utils" ) +// testEC is used as sample through various tests +var testEC = &EventCost{ + CGRID: "164b0422fdc6a5117031b427439482c6a4f90e41", + RunID: utils.META_DEFAULT, + StartTime: time.Date(2017, 1, 9, 16, 18, 21, 0, time.UTC), + Charges: []*ChargingInterval{ + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(0), + Cost: 0.1, + BalanceChargeUUID: "9bdad10", + CompressFactor: 1, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0, + BalanceChargeUUID: "3455b83", + CompressFactor: 10, + }, + &ChargingIncrement{ + Usage: time.Duration(10 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 2, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.005, + BalanceChargeUUID: "44d6c02", + CompressFactor: 30, + }, + }, + CompressFactor: 1, + }, + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 60, + }, + }, + CompressFactor: 4, + }, + }, + AccountSummary: &AccountSummary{ + Tenant: "cgrates.org", + ID: "dan", + BalanceSummaries: []*BalanceSummary{ + &BalanceSummary{ + Type: "*monetary", + Value: 50, + Disabled: false}, + &BalanceSummary{ + ID: "4b8b53d7-c1a1-4159-b845-4623a00a0165", + Type: "*monetary", + Value: 25, + Disabled: false}, + &BalanceSummary{ + Type: "*voice", + Value: 200, + Disabled: false, + }, + }, + AllowNegative: false, + Disabled: false, + }, + Rating: Rating{ + "3cd6425": &RatingUnit{ + RoundingMethod: "*up", + RoundingDecimals: 5, + TimingUUID: "7f324ab", + RatesUUID: "4910ecf", + RatingFiltersUUID: "43e77dc", + }, + "c1a5ab9": &RatingUnit{ + ConnectFee: 0.1, + RoundingMethod: "*up", + RoundingDecimals: 5, + TimingUUID: "7f324ab", + RatesUUID: "ec1a177", + RatingFiltersUUID: "43e77dc", + }, + }, + Accounting: Accounting{ + "a012888": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.01, + }, + "188bfa6": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.005, + }, + "9bdad10": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", + Units: 0.1, + }, + "44d6c02": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "7a54a9e9-d610-4c82-bcb5-a315b9a65010", + RatingUUID: "3cd6425", + Units: 1, + ExtraChargeUUID: "188bfa6", + }, + "3455b83": &BalanceCharge{ + AccountID: "cgrates.org:dan", + BalanceUUID: "9d54a9e9-d610-4c82-bcb5-a315b9a65089", + Units: 1, + ExtraChargeUUID: "*none", + }, + }, + RatingFilters: RatingFilters{ + "43e77dc": RatingMatchedFilters{ + "DestinationID": "GERMANY", + "DestinationPrefix": "+49", + "RatingPlanID": "RPL_RETAIL1", + "Subject": "*out:cgrates.org:call:*any", + }, + }, + Rates: ChargedRates{ + "ec1a177": RateGroups{ + &Rate{ + GroupIntervalStart: time.Duration(0), + Value: 0.01, + RateIncrement: time.Duration(1 * time.Minute), + RateUnit: time.Duration(1 * time.Second)}, + }, + "4910ecf": 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{ + "7f324ab": &ChargedTiming{ + StartTime: "00:00:00", + }, + }, +} + +func TestECClone(t *testing.T) { + ec := testEC.Clone() + if !reflect.DeepEqual(testEC, ec) { + t.Errorf("Expecting: %s, received: %s", + utils.ToJSON(testEC), utils.ToJSON(ec)) + } +} + +func TestECComputeAndReset(t *testing.T) { + ec := testEC.Clone() + eEc := testEC.Clone() + eEc.Usage = utils.DurationPointer(time.Duration(5 * time.Minute)) + eEc.Cost = utils.Float64Pointer(2.67) + eEc.Charges[0].ecUsageIdx = utils.DurationPointer(time.Duration(0)) + eEc.Charges[0].usage = utils.DurationPointer(time.Duration(1 * time.Minute)) + eEc.Charges[0].cost = utils.Float64Pointer(0.27) + eEc.Charges[1].ecUsageIdx = utils.DurationPointer(time.Duration(1 * time.Minute)) + eEc.Charges[1].usage = utils.DurationPointer(time.Duration(1 * time.Minute)) + eEc.Charges[1].cost = utils.Float64Pointer(0.6) + ec.Compute() + if !reflect.DeepEqual(eEc, ec) { + t.Errorf("Expecting: %+v, received: %+v", eEc, ec) + } + ec.ResetCounters() + if !reflect.DeepEqual(testEC, ec) { + t.Errorf("Expecting: %+v, received: %+v", testEC, ec) + } +} + func TestNewEventCostFromCallCost(t *testing.T) { acntSummary := &AccountSummary{ Tenant: "cgrates.org", @@ -423,7 +606,7 @@ func TestNewEventCostFromCallCost(t *testing.T) { } } -func TestEventCostAsCallCost(t *testing.T) { +func TestECAsCallCost(t *testing.T) { acntSummary := &AccountSummary{ Tenant: "cgrates.org", ID: "dan", @@ -718,165 +901,129 @@ func TestEventCostAsCallCost(t *testing.T) { } } -func TestEventCostTrim(t *testing.T) { - acntSummary := &AccountSummary{ - Tenant: "cgrates.org", - ID: "dan", - BalanceSummaries: []*BalanceSummary{ - &BalanceSummary{ - Type: "*monetary", - Value: 50, - Disabled: false}, - &BalanceSummary{ - ID: "4b8b53d7-c1a1-4159-b845-4623a00a0165", - Type: "*monetary", - Value: 25, - Disabled: false}, - &BalanceSummary{ - Type: "*voice", - Value: 200, - Disabled: false, - }, - }, - AllowNegative: false, - Disabled: false, - } - ec := &EventCost{ - CGRID: "164b0422fdc6a5117031b427439482c6a4f90e41", - RunID: utils.META_DEFAULT, - StartTime: time.Date(2017, 1, 9, 16, 18, 21, 0, time.UTC), - Cost: utils.Float64Pointer(2.05), - Usage: utils.DurationPointer(time.Duration(4 * time.Minute)), - Charges: []*ChargingInterval{ - &ChargingInterval{ - RatingUUID: "f2518464-68b8-42f4-acec-aef23d714314", - Increments: []*ChargingIncrement{ - &ChargingIncrement{ - Usage: time.Duration(0), - Cost: 0.1, - BalanceChargeUUID: "44e97dec-8a7e-43d0-8b0a-736d46b5613e", - CompressFactor: 1, - }, - &ChargingIncrement{ - Usage: time.Duration(1 * time.Second), - Cost: 0, - BalanceChargeUUID: "a555cde8-4bd0-408a-afbc-c3ba64888927", - CompressFactor: 30, - }, - &ChargingIncrement{ - Usage: time.Duration(1 * time.Second), - Cost: 0.005, - BalanceChargeUUID: "906bfd0f-035c-40a3-93a8-46f71627983e", - CompressFactor: 30, - }, - }, - CompressFactor: 1, - }, - &ChargingInterval{ - RatingUUID: "f2518464-68b8-42f4-acec-aef23d714314", - Increments: []*ChargingIncrement{ - &ChargingIncrement{ - Usage: time.Duration(1 * time.Second), - Cost: 0.01, - BalanceChargeUUID: "c890a899-df43-497a-9979-38492713f57b", - CompressFactor: 60, - }, - }, - CompressFactor: 3, - }, - }, - AccountSummary: acntSummary, - Rating: Rating{ - "4607d907-02c3-4f2b-bc08-95a0dcc7222c": &RatingUnit{ - RoundingMethod: "*up", - RoundingDecimals: 5, - TimingUUID: "27f1e5f8-05bb-4f1c-a596-bf1010ad296c", - RatesUUID: "e5eb0f1c-3612-4e8c-b749-7f8f41dd90d4", - RatingFiltersUUID: "7e73a00d-be53-4083-a1ee-8ee0b546c62a", - }, - "f2518464-68b8-42f4-acec-aef23d714314": &RatingUnit{ - ConnectFee: 0.1, - RoundingMethod: "*up", - RoundingDecimals: 5, - TimingUUID: "27f1e5f8-05bb-4f1c-a596-bf1010ad296c", - RatesUUID: "6504fb84-6b27-47a8-a1c6-c0d843959f89", - RatingFiltersUUID: "7e73a00d-be53-4083-a1ee-8ee0b546c62a", - }, - }, - Accounting: Accounting{ - "c890a899-df43-497a-9979-38492713f57b": &BalanceCharge{ - AccountID: "cgrates.org:dan", - BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", - Units: 0.01, - }, - "a894f8f1-206a-4457-99ce-df21a0c7fedc": &BalanceCharge{ - AccountID: "cgrates.org:dan", - BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", - Units: 0.005, - }, - "44e97dec-8a7e-43d0-8b0a-736d46b5613e": &BalanceCharge{ - AccountID: "cgrates.org:dan", - BalanceUUID: "8c54a9e9-d610-4c82-bcb5-a315b9a65010", - Units: 0.1, - }, - "906bfd0f-035c-40a3-93a8-46f71627983e": &BalanceCharge{ - AccountID: "cgrates.org:dan", - BalanceUUID: "7a54a9e9-d610-4c82-bcb5-a315b9a65010", - RatingUUID: "4607d907-02c3-4f2b-bc08-95a0dcc7222c", - Units: 1, - ExtraChargeUUID: "a894f8f1-206a-4457-99ce-df21a0c7fedc", - }, - "a555cde8-4bd0-408a-afbc-c3ba64888927": &BalanceCharge{ - AccountID: "cgrates.org:dan", - BalanceUUID: "9d54a9e9-d610-4c82-bcb5-a315b9a65089", - Units: 1, - ExtraChargeUUID: "*none", - }, - }, - RatingFilters: RatingFilters{ - "7e73a00d-be53-4083-a1ee-8ee0b546c62a": RatingMatchedFilters{ - "DestinationID": "GERMANY", - "DestinationPrefix": "+49", - "RatingPlanID": "RPL_RETAIL1", - "Subject": "*out:cgrates.org:call:*any", - }, - }, - Rates: ChargedRates{ - "6504fb84-6b27-47a8-a1c6-c0d843959f89": RateGroups{ - &Rate{ - GroupIntervalStart: time.Duration(0), - Value: 0.01, - RateIncrement: time.Duration(1 * time.Minute), - RateUnit: time.Duration(1 * time.Second)}, - }, - "e5eb0f1c-3612-4e8c-b749-7f8f41dd90d4": 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{ - "27f1e5f8-05bb-4f1c-a596-bf1010ad296c": &ChargedTiming{ - StartTime: "00:00:00", - }, - }, - } - origEC := ec.Clone() - if srplsEC, err := ec.Trim(time.Duration(4 * time.Minute)); err != nil { +func TestECTrimZeroAndFull(t *testing.T) { + ec := testEC.Clone() + if srplsEC, err := ec.Trim(time.Duration(5 * time.Minute)); err != nil { t.Error(err) } else if srplsEC != nil { t.Errorf("Expecting nil, got: %+v", srplsEC) } + eFullSrpls := testEC.Clone() + eFullSrpls.Usage = utils.DurationPointer(time.Duration(5 * time.Minute)) + eFullSrpls.Charges[0].usage = utils.DurationPointer(time.Duration(1 * time.Minute)) + eFullSrpls.Charges[1].usage = utils.DurationPointer(time.Duration(1 * time.Minute)) if srplsEC, err := ec.Trim(time.Duration(0)); err != nil { t.Error(err) - } else if !reflect.DeepEqual(origEC, srplsEC) { - t.Errorf("Expecting: %s,\n received: %s", utils.ToJSON(origEC), utils.ToJSON(srplsEC)) + } else if !reflect.DeepEqual(eFullSrpls, srplsEC) { + t.Errorf("\tExpecting: %s,\n\treceived: %s", + utils.ToJSON(eFullSrpls), utils.ToJSON(srplsEC)) + } +} + +func TestECTrimMiddle(t *testing.T) { + // trim in the middle of increments + ec := testEC.Clone() + eEC := testEC.Clone() + eEC.Charges = []*ChargingInterval{ + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(0), + Cost: 0.1, + BalanceChargeUUID: "9bdad10", + CompressFactor: 1, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0, + BalanceChargeUUID: "3455b83", + CompressFactor: 10, + }, + &ChargingIncrement{ + Usage: time.Duration(10 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 2, + }, + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.005, + BalanceChargeUUID: "44d6c02", + CompressFactor: 30, + }, + }, + CompressFactor: 1, + }, + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 60, + }, + }, + CompressFactor: 2, + }, + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 10, + }, + }, + CompressFactor: 1, + }, + } + eSrplsEC := testEC.Clone() + eSrplsEC.Charges = []*ChargingInterval{ + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 50, + }, + }, + CompressFactor: 1, + }, + &ChargingInterval{ + RatingUUID: "c1a5ab9", + Increments: []*ChargingIncrement{ + &ChargingIncrement{ + Usage: time.Duration(1 * time.Second), + Cost: 0.01, + BalanceChargeUUID: "a012888", + CompressFactor: 60, + }, + }, + CompressFactor: 1, + }, + } + + reqDuration := time.Duration(190 * time.Second) + initDur := ec.ComputeUsage() + srplsEC, err := ec.Trim(reqDuration) + if err != nil { + t.Error(err) + } + if reqDuration != *ec.Usage { + t.Logf("\teEC: %s\n\tEC: %s\n\torigEC: %s\n", utils.ToJSON(eEC), utils.ToJSON(ec), utils.ToJSON(testEC)) + t.Errorf("Expecting request duration: %v, received: %v", reqDuration, *ec.Usage) + } + if srplsUsage := srplsEC.ComputeUsage(); srplsUsage != time.Duration(110*time.Second) { + t.Errorf("Expecting surplus duration: %v, received: %v", initDur-reqDuration, srplsUsage) + } + if !reflect.DeepEqual(eEC, ec) { + //t.Errorf("Expecting: %s, received: %s", utils.ToJSON(eEC), utils.ToJSON(ec)) + } else if !reflect.DeepEqual(eSrplsEC, srplsEC) { + //t.Errorf("Expecting: %s, received: %s", utils.ToJSON(eSrplsEC), utils.ToJSON(srplsEC)) } } diff --git a/engine/libeventcost.go b/engine/libeventcost.go new file mode 100644 index 000000000..a43e25fa5 --- /dev/null +++ b/engine/libeventcost.go @@ -0,0 +1,367 @@ +/* +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 ( + "time" + + "github.com/cgrates/cgrates/utils" +) + +// ChargingInterval represents one interval out of Usage providing charging info +// eg: PEAK vs OFFPEAK +type ChargingInterval struct { + RatingUUID string // reference to RatingUnit + Increments []*ChargingIncrement // specific increments applied to this interval + CompressFactor int + usage *time.Duration // cache usage computation for this interval + ecUsageIdx *time.Duration // computed value of totalUsage at the starting of the interval + cost *float64 // cache cost calculation on this interval + +} + +// PartiallyEquals does not compare CompressFactor, usefull for Merge +func (cIl *ChargingInterval) PartiallyEquals(oCIl *ChargingInterval) (equals bool) { + if equals = cIl.RatingUUID == oCIl.RatingUUID && + len(cIl.Increments) == len(oCIl.Increments); !equals { + return + } + for i := range cIl.Increments { + if !cIl.Increments[i].Equals(oCIl.Increments[i]) { + equals = false + break + } + } + return +} + +// Usage computes the total usage of this ChargingInterval, ignoring CompressFactor +func (cIl *ChargingInterval) Usage() *time.Duration { + if cIl.usage == nil { + var usage time.Duration + for _, incr := range cIl.Increments { + usage += incr.TotalUsage() + } + cIl.usage = &usage + } + return cIl.usage +} + +// TotalUsage returns the total usage of this interval, considering compress factor +func (cIl *ChargingInterval) TotalUsage() (tu *time.Duration) { + usage := cIl.Usage() + if usage == nil { + return + } + tu = new(time.Duration) + *tu = time.Duration(usage.Nanoseconds() * int64(cIl.CompressFactor)) + return +} + +// EventCostUsageIndex publishes the value of ecUsageIdx +func (cIl *ChargingInterval) EventCostUsageIndex() *time.Duration { + return cIl.ecUsageIdx +} + +// StartTime computes a StartTime based on EventCost.Start time and ecUsageIdx +func (cIl *ChargingInterval) StartTime(ecST time.Time) (st time.Time) { + if cIl.ecUsageIdx != nil { + st = ecST.Add(*cIl.ecUsageIdx) + } + return +} + +// EndTime computes an EndTime based on ChargingInterval StartTime value and usage +func (cIl *ChargingInterval) EndTime(cIlST time.Time) (et time.Time) { + return cIlST.Add(time.Duration(cIl.Usage().Nanoseconds() * int64(cIl.CompressFactor))) +} + +// Cost computes the total cost on this ChargingInterval +func (cIl *ChargingInterval) Cost() float64 { + if cIl.cost == nil { + var cost float64 + for _, incr := range cIl.Increments { + cost += incr.Cost * float64(incr.CompressFactor) + } + cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) + cIl.cost = &cost + } + return *cIl.cost +} + +// Clone returns a new instance of ChargingInterval with independent data +func (cIl *ChargingInterval) Clone() (cln *ChargingInterval) { + cln = new(ChargingInterval) + cln.RatingUUID = cIl.RatingUUID + cln.CompressFactor = cIl.CompressFactor + cln.Increments = make([]*ChargingIncrement, len(cIl.Increments)) + for i, cIt := range cIl.Increments { + cln.Increments[i] = cIt.Clone() + } + return +} + +// ChargingIncrement represents one unit charged inside an interval +type ChargingIncrement struct { + Usage time.Duration + Cost float64 + BalanceChargeUUID string + CompressFactor int +} + +func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { + return cIt.Usage == oCIt.Usage && + cIt.Cost == oCIt.Cost && + cIt.BalanceChargeUUID == oCIt.BalanceChargeUUID && + cIt.CompressFactor == oCIt.CompressFactor +} + +func (cIt *ChargingIncrement) Clone() (cln *ChargingIncrement) { + cln = new(ChargingIncrement) + *cln = *cIt + return +} + +// TotalUsage returns the total usage of the increment, considering compress factor +func (cIt *ChargingIncrement) TotalUsage() time.Duration { + return time.Duration(cIt.Usage.Nanoseconds() * int64(cIt.CompressFactor)) +} + +// BalanceCharge represents one unit charged to a balance +type BalanceCharge struct { + AccountID string // keep reference for shared balances + BalanceUUID string // balance charged + RatingUUID string // special price applied on this balance + Units float64 // number of units charged + ExtraChargeUUID string // used in cases when paying *voice with *monetary +} + +func (bc *BalanceCharge) Equals(oBC *BalanceCharge) bool { + return bc.AccountID == oBC.AccountID && + bc.BalanceUUID == oBC.BalanceUUID && + bc.RatingUUID == oBC.RatingUUID && + bc.Units == oBC.Units && + bc.ExtraChargeUUID == oBC.ExtraChargeUUID +} + +func (bc *BalanceCharge) Clone() *BalanceCharge { + clnBC := new(BalanceCharge) + *clnBC = *bc + return clnBC +} + +type RatingMatchedFilters map[string]interface{} + +func (rf RatingMatchedFilters) Equals(oRF RatingMatchedFilters) (equals bool) { + equals = true + for k := range rf { + if rf[k] != oRF[k] { + equals = false + break + } + } + return +} + +func (rf RatingMatchedFilters) Clone() (cln map[string]interface{}) { + cln = make(map[string]interface{}) + utils.Clone(rf, &cln) + return +} + +// ChargedTiming represents one timing attached to a charge +type ChargedTiming struct { + Years utils.Years + Months utils.Months + MonthDays utils.MonthDays + WeekDays utils.WeekDays + StartTime string +} + +func (ct *ChargedTiming) Equals(oCT *ChargedTiming) bool { + return ct.Years.Equals(oCT.Years) && + ct.Months.Equals(oCT.Months) && + ct.MonthDays.Equals(oCT.MonthDays) && + ct.WeekDays.Equals(oCT.WeekDays) && + ct.StartTime == oCT.StartTime +} + +func (ct *ChargedTiming) Clone() (cln *ChargedTiming) { + cln = new(ChargedTiming) + *cln = *ct + return +} + +// RatingUnit represents one unit out of RatingPlan matching for an event +type RatingUnit struct { + ConnectFee float64 + RoundingMethod string + RoundingDecimals int + MaxCost float64 + MaxCostStrategy string + TimingUUID string // This RatingUnit is bounded to specific timing profile + RatesUUID string + RatingFiltersUUID string +} + +func (ru *RatingUnit) Equals(oRU *RatingUnit) bool { + return ru.ConnectFee == oRU.ConnectFee && + ru.RoundingMethod == oRU.RoundingMethod && + ru.RoundingDecimals == oRU.RoundingDecimals && + ru.MaxCost == oRU.MaxCost && + ru.MaxCostStrategy == oRU.MaxCostStrategy && + ru.TimingUUID == oRU.TimingUUID && + ru.RatesUUID == oRU.RatesUUID && + ru.RatingFiltersUUID == oRU.RatingFiltersUUID +} + +func (ru *RatingUnit) Clone() (cln *RatingUnit) { + cln = new(RatingUnit) + *cln = *ru + return +} + +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 { + if rmf == nil || len(rmf) == 0 { + return "" + } + for k, v := range rfs { + if v.Equals(rmf) { + return k + } + } + // not found, set it here + uuid := utils.UUIDSha1Prefix() + rfs[uuid] = rmf + return uuid +} + +func (rfs RatingFilters) Clone() (cln RatingFilters) { + cln = make(RatingFilters, len(rfs)) + for k, v := range rfs { + cln[k] = v.Clone() + } + return +} + +type Rating map[string]*RatingUnit + +// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one +func (crus Rating) GetUUIDWithSet(cru *RatingUnit) string { + if cru == nil { + return "" + } + for k, v := range crus { + if v.Equals(cru) { + return k + } + } + // not found, set it here + uuid := utils.UUIDSha1Prefix() + crus[uuid] = cru + return uuid +} + +func (crus Rating) Clone() (cln Rating) { + cln = make(Rating, len(crus)) + for k, v := range crus { + cln[k] = v.Clone() + } + return +} + +type ChargedRates map[string]RateGroups + +// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one +func (crs ChargedRates) GetUUIDWithSet(rg RateGroups) string { + if rg == nil || len(rg) == 0 { + return "" + } + for k, v := range crs { + if v.Equals(rg) { + return k + } + } + // not found, set it here + uuid := utils.UUIDSha1Prefix() + crs[uuid] = rg + return uuid +} + +func (crs ChargedRates) Clone() (cln ChargedRates) { + cln = make(ChargedRates, len(crs)) + for k, v := range crs { + cln[k] = v.Clone() + } + return +} + +type ChargedTimings map[string]*ChargedTiming + +// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one +func (cts ChargedTimings) GetUUIDWithSet(ct *ChargedTiming) string { + if ct == nil { + return "" + } + for k, v := range cts { + if v.Equals(ct) { + return k + } + } + // not found, set it here + uuid := utils.UUIDSha1Prefix() + cts[uuid] = ct + return uuid +} + +func (cts ChargedTimings) Clone() (cln ChargedTimings) { + cln = make(ChargedTimings, len(cts)) + for k, v := range cts { + cln[k] = v.Clone() + } + return +} + +type Accounting map[string]*BalanceCharge + +// GetUUIDWithSet attempts to retrieve the UUID of a matching data or create a new one +func (cbs Accounting) GetUUIDWithSet(cb *BalanceCharge) string { + if cb == nil { + return "" + } + for k, v := range cbs { + if v.Equals(cb) { + return k + } + } + // not found, set it here + uuid := utils.UUIDSha1Prefix() + cbs[uuid] = cb + return uuid +} + +func (cbs Accounting) Clone() (cln Accounting) { + cln = make(Accounting, len(cbs)) + for k, v := range cbs { + cln[k] = v.Clone() + } + return +} diff --git a/utils/coreutils.go b/utils/coreutils.go index 30eb7d90c..e1d5ecbc8 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -108,7 +108,7 @@ func GenUUID() string { // UUIDSha1Prefix generates a prefix of the sha1 applied to an UUID // prefix 8 is chosen since the probability of colision starts being minimal after 7 characters (see git commits) func UUIDSha1Prefix() string { - return Sha1(GenUUID())[:8] + return Sha1(GenUUID())[:7] } // Round return rounded version of x with prec precision.