mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
EventCost.Trim in middle with tests, libeventcost
This commit is contained in:
@@ -18,141 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
367
engine/libeventcost.go
Normal file
367
engine/libeventcost.go
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user