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.