mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
754 lines
29 KiB
Go
754 lines
29 KiB
Go
/*
|
|
Real-time Charging System for Telecom & ISP environments
|
|
Copyright (C) 2012-2015 ITsysCOM GmbH
|
|
|
|
This program is free software: you can Storagetribute 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, ornt
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITH*out 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 (
|
|
"encoding/json"
|
|
"math"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
func NewStoredCdrFromExternalCdr(extCdr *ExternalCdr, timezone string) (*StoredCdr, error) {
|
|
var err error
|
|
storedCdr := &StoredCdr{CgrId: extCdr.CgrId, OrderId: extCdr.OrderId, TOR: extCdr.TOR, AccId: extCdr.AccId, CdrHost: extCdr.CdrHost, CdrSource: extCdr.CdrSource,
|
|
ReqType: extCdr.ReqType, Direction: extCdr.Direction, Tenant: extCdr.Tenant, Category: extCdr.Category, Account: extCdr.Account, Subject: extCdr.Subject,
|
|
Destination: extCdr.Destination, Supplier: extCdr.Supplier, DisconnectCause: extCdr.DisconnectCause,
|
|
MediationRunId: extCdr.MediationRunId, RatedAccount: extCdr.RatedAccount, RatedSubject: extCdr.RatedSubject, Cost: extCdr.Cost, Rated: extCdr.Rated}
|
|
if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(extCdr.SetupTime, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(storedCdr.CgrId) == 0 { // Populate CgrId if not present
|
|
storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.UTC().String())
|
|
}
|
|
if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(extCdr.AnswerTime, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
if storedCdr.Usage, err = utils.ParseDurationWithSecs(extCdr.Usage); err != nil {
|
|
return nil, err
|
|
}
|
|
if storedCdr.Pdd, err = utils.ParseDurationWithSecs(extCdr.Pdd); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(extCdr.CostDetails) != 0 {
|
|
if err = json.Unmarshal([]byte(extCdr.CostDetails), storedCdr.CostDetails); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if extCdr.ExtraFields != nil {
|
|
storedCdr.ExtraFields = make(map[string]string)
|
|
}
|
|
for k, v := range extCdr.ExtraFields {
|
|
storedCdr.ExtraFields[k] = v
|
|
}
|
|
return storedCdr, nil
|
|
}
|
|
|
|
// Kinda standard of internal CDR, complies to CDR interface also
|
|
type StoredCdr struct {
|
|
CgrId string
|
|
OrderId int64 // Stor order id used as export order id
|
|
TOR string // type of record, meta-field, should map to one of the TORs hardcoded inside the server <*voice|*data|*sms|*generic>
|
|
AccId string // represents the unique accounting id given by the telecom switch generating the CDR
|
|
CdrHost string // represents the IP address of the host generating the CDR (automatically populated by the server)
|
|
CdrSource string // formally identifies the source of the CDR (free form field)
|
|
ReqType string // matching the supported request types by the **CGRateS**, accepted values are hardcoded in the server <prepaid|postpaid|pseudoprepaid|rated>.
|
|
Direction string // matching the supported direction identifiers of the CGRateS <*out>
|
|
Tenant string // tenant whom this record belongs
|
|
Category string // free-form filter for this record, matching the category defined in rating profiles.
|
|
Account string // account id (accounting subsystem) the record should be attached to
|
|
Subject string // rating subject (rating subsystem) this record should be attached to
|
|
Destination string // destination to be charged
|
|
SetupTime time.Time // set-up time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp.
|
|
Pdd time.Duration // PDD value
|
|
AnswerTime time.Time // answer time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp.
|
|
Usage time.Duration // event usage information (eg: in case of tor=*voice this will represent the total duration of a call)
|
|
Supplier string // Supplier information when available
|
|
DisconnectCause string // Disconnect cause of the event
|
|
ExtraFields map[string]string // Extra fields to be stored in CDR
|
|
MediationRunId string
|
|
RatedAccount string // Populated out of rating data
|
|
RatedSubject string
|
|
Cost float64
|
|
ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation
|
|
CostDetails *CallCost // Attach the cost details to CDR when possible
|
|
Rated bool // Mark the CDR as rated so we do not process it during mediation
|
|
}
|
|
|
|
func (storedCdr *StoredCdr) CostDetailsJson() string {
|
|
if storedCdr.CostDetails == nil {
|
|
return ""
|
|
}
|
|
mrshled, _ := json.Marshal(storedCdr.CostDetails)
|
|
return string(mrshled)
|
|
}
|
|
|
|
// Used to multiply usage on export
|
|
func (storedCdr *StoredCdr) UsageMultiply(multiplyFactor float64, roundDecimals int) {
|
|
storedCdr.Usage = time.Duration(int(utils.Round(float64(storedCdr.Usage.Nanoseconds())*multiplyFactor, roundDecimals, utils.ROUNDING_MIDDLE))) // Rounding down could introduce a slight loss here but only at nanoseconds level
|
|
}
|
|
|
|
// Used to multiply cost on export
|
|
func (storedCdr *StoredCdr) CostMultiply(multiplyFactor float64, roundDecimals int) {
|
|
storedCdr.Cost = utils.Round(storedCdr.Cost*multiplyFactor, roundDecimals, utils.ROUNDING_MIDDLE)
|
|
}
|
|
|
|
// Format cost as string on export
|
|
func (storedCdr *StoredCdr) FormatCost(shiftDecimals, roundDecimals int) string {
|
|
cost := storedCdr.Cost
|
|
if shiftDecimals != 0 {
|
|
cost = cost * math.Pow10(shiftDecimals)
|
|
}
|
|
return strconv.FormatFloat(cost, 'f', roundDecimals, 64)
|
|
}
|
|
|
|
// Formats usage on export
|
|
func (storedCdr *StoredCdr) FormatUsage(layout string) string {
|
|
if utils.IsSliceMember([]string{utils.DATA, utils.SMS, utils.GENERIC}, storedCdr.TOR) {
|
|
return strconv.FormatFloat(utils.Round(storedCdr.Usage.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64)
|
|
}
|
|
switch layout {
|
|
default:
|
|
return strconv.FormatFloat(float64(storedCdr.Usage.Nanoseconds())/1000000000, 'f', -1, 64)
|
|
}
|
|
}
|
|
|
|
// Used to retrieve fields as string, primary fields are const labeled
|
|
func (storedCdr *StoredCdr) FieldAsString(rsrFld *utils.RSRField) string {
|
|
if rsrFld.IsStatic() { // Static values do not care about headers
|
|
return rsrFld.ParseValue("")
|
|
}
|
|
switch rsrFld.Id {
|
|
case utils.CGRID:
|
|
return rsrFld.ParseValue(storedCdr.CgrId)
|
|
case utils.ORDERID:
|
|
return rsrFld.ParseValue(strconv.FormatInt(storedCdr.OrderId, 10))
|
|
case utils.TOR:
|
|
return rsrFld.ParseValue(storedCdr.TOR)
|
|
case utils.ACCID:
|
|
return rsrFld.ParseValue(storedCdr.AccId)
|
|
case utils.CDRHOST:
|
|
return rsrFld.ParseValue(storedCdr.CdrHost)
|
|
case utils.CDRSOURCE:
|
|
return rsrFld.ParseValue(storedCdr.CdrSource)
|
|
case utils.REQTYPE:
|
|
return rsrFld.ParseValue(storedCdr.ReqType)
|
|
case utils.DIRECTION:
|
|
return rsrFld.ParseValue(storedCdr.Direction)
|
|
case utils.TENANT:
|
|
return rsrFld.ParseValue(storedCdr.Tenant)
|
|
case utils.CATEGORY:
|
|
return rsrFld.ParseValue(storedCdr.Category)
|
|
case utils.ACCOUNT:
|
|
return rsrFld.ParseValue(storedCdr.Account)
|
|
case utils.SUBJECT:
|
|
return rsrFld.ParseValue(storedCdr.Subject)
|
|
case utils.DESTINATION:
|
|
return rsrFld.ParseValue(storedCdr.Destination)
|
|
case utils.SETUP_TIME:
|
|
return rsrFld.ParseValue(storedCdr.SetupTime.Format(time.RFC3339))
|
|
case utils.PDD:
|
|
return strconv.FormatFloat(storedCdr.Pdd.Seconds(), 'f', -1, 64)
|
|
case utils.ANSWER_TIME:
|
|
return rsrFld.ParseValue(storedCdr.AnswerTime.Format(time.RFC3339))
|
|
case utils.USAGE:
|
|
return strconv.FormatFloat(storedCdr.Usage.Seconds(), 'f', -1, 64)
|
|
case utils.SUPPLIER:
|
|
return rsrFld.ParseValue(storedCdr.Supplier)
|
|
case utils.DISCONNECT_CAUSE:
|
|
return rsrFld.ParseValue(storedCdr.DisconnectCause)
|
|
case utils.MEDI_RUNID:
|
|
return rsrFld.ParseValue(storedCdr.MediationRunId)
|
|
case utils.RATED_ACCOUNT:
|
|
return rsrFld.ParseValue(storedCdr.RatedAccount)
|
|
case utils.RATED_SUBJECT:
|
|
return rsrFld.ParseValue(storedCdr.RatedSubject)
|
|
case utils.RATED_FLD:
|
|
return rsrFld.ParseValue(strconv.FormatBool(storedCdr.Rated))
|
|
case utils.COST:
|
|
return rsrFld.ParseValue(strconv.FormatFloat(storedCdr.Cost, 'f', -1, 64)) // Recommended to use FormatCost
|
|
case utils.COST_DETAILS:
|
|
return rsrFld.ParseValue(storedCdr.CostDetailsJson())
|
|
default:
|
|
return rsrFld.ParseValue(storedCdr.ExtraFields[rsrFld.Id])
|
|
}
|
|
}
|
|
|
|
// concatenates values of multiple fields defined in template, used eg in CDR templates
|
|
func (storedCdr *StoredCdr) FieldsAsString(rsrFlds utils.RSRFields) string {
|
|
var fldVal string
|
|
for _, rsrFld := range rsrFlds {
|
|
fldVal += storedCdr.FieldAsString(rsrFld)
|
|
}
|
|
return fldVal
|
|
}
|
|
|
|
func (storedCdr *StoredCdr) PassesFieldFilter(fieldFilter *utils.RSRField) (bool, string) {
|
|
if fieldFilter == nil {
|
|
return true, ""
|
|
}
|
|
if fieldFilter.IsStatic() && storedCdr.FieldAsString(&utils.RSRField{Id: fieldFilter.Id}) == storedCdr.FieldAsString(fieldFilter) {
|
|
return true, storedCdr.FieldAsString(&utils.RSRField{Id: fieldFilter.Id})
|
|
}
|
|
preparedFilter := &utils.RSRField{Id: fieldFilter.Id, RSRules: make([]*utils.ReSearchReplace, len(fieldFilter.RSRules))} // Reset rules so they do not point towards same structures as original fieldFilter
|
|
for idx := range fieldFilter.RSRules {
|
|
// Hardcode the template with maximum of 5 groups ordered
|
|
preparedFilter.RSRules[idx] = &utils.ReSearchReplace{SearchRegexp: fieldFilter.RSRules[idx].SearchRegexp, ReplaceTemplate: utils.FILTER_REGEXP_TPL}
|
|
}
|
|
preparedVal := storedCdr.FieldAsString(preparedFilter)
|
|
filteredValue := storedCdr.FieldAsString(fieldFilter)
|
|
if preparedFilter.RegexpMatched() && (len(preparedVal) == 0 || preparedVal == filteredValue) {
|
|
return true, filteredValue
|
|
}
|
|
return false, ""
|
|
}
|
|
|
|
func (storedCdr *StoredCdr) AsStoredCdr(timezone string) *StoredCdr {
|
|
return storedCdr
|
|
}
|
|
|
|
func (storedCdr *StoredCdr) Clone() *StoredCdr {
|
|
clnCdr := *storedCdr
|
|
clnCdr.ExtraFields = make(map[string]string)
|
|
clnCdr.CostDetails = nil // Clean old reference
|
|
for k, v := range storedCdr.ExtraFields {
|
|
clnCdr.ExtraFields[k] = v
|
|
}
|
|
if storedCdr.CostDetails != nil {
|
|
cDetails := *storedCdr.CostDetails
|
|
clnCdr.CostDetails = &cDetails
|
|
}
|
|
return &clnCdr
|
|
}
|
|
|
|
// Ability to send the CgrCdr remotely to another CDR server, we do not include rating variables for now
|
|
func (storedCdr *StoredCdr) AsHttpForm() url.Values {
|
|
v := url.Values{}
|
|
for fld, val := range storedCdr.ExtraFields {
|
|
v.Set(fld, val)
|
|
}
|
|
v.Set(utils.TOR, storedCdr.TOR)
|
|
v.Set(utils.ACCID, storedCdr.AccId)
|
|
v.Set(utils.CDRHOST, storedCdr.CdrHost)
|
|
v.Set(utils.CDRSOURCE, storedCdr.CdrSource)
|
|
v.Set(utils.REQTYPE, storedCdr.ReqType)
|
|
v.Set(utils.DIRECTION, storedCdr.Direction)
|
|
v.Set(utils.TENANT, storedCdr.Tenant)
|
|
v.Set(utils.CATEGORY, storedCdr.Category)
|
|
v.Set(utils.ACCOUNT, storedCdr.Account)
|
|
v.Set(utils.SUBJECT, storedCdr.Subject)
|
|
v.Set(utils.DESTINATION, storedCdr.Destination)
|
|
v.Set(utils.SETUP_TIME, storedCdr.SetupTime.Format(time.RFC3339))
|
|
v.Set(utils.PDD, storedCdr.FieldAsString(&utils.RSRField{Id: utils.PDD}))
|
|
v.Set(utils.ANSWER_TIME, storedCdr.AnswerTime.Format(time.RFC3339))
|
|
v.Set(utils.USAGE, storedCdr.FormatUsage(utils.SECONDS))
|
|
v.Set(utils.SUPPLIER, storedCdr.Supplier)
|
|
v.Set(utils.DISCONNECT_CAUSE, storedCdr.DisconnectCause)
|
|
if storedCdr.CostDetails != nil {
|
|
v.Set(utils.COST_DETAILS, storedCdr.CostDetailsJson())
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Used in mediation, primaryMandatory marks whether missing field out of request represents error or can be ignored
|
|
func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tenantFld, categFld, accountFld, subjectFld, destFld, setupTimeFld, pddFld,
|
|
answerTimeFld, durationFld, supplierFld, disconnectCauseFld, ratedFld, costFld *utils.RSRField,
|
|
extraFlds []*utils.RSRField, primaryMandatory bool, timezone string) (*StoredCdr, error) {
|
|
if reqTypeFld == nil {
|
|
reqTypeFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if reqTypeFld.Id == utils.META_DEFAULT {
|
|
reqTypeFld.Id = utils.REQTYPE
|
|
}
|
|
if directionFld == nil {
|
|
directionFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if directionFld.Id == utils.META_DEFAULT {
|
|
directionFld.Id = utils.DIRECTION
|
|
}
|
|
if tenantFld == nil {
|
|
tenantFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if tenantFld.Id == utils.META_DEFAULT {
|
|
tenantFld.Id = utils.TENANT
|
|
}
|
|
if categFld == nil {
|
|
categFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if categFld.Id == utils.META_DEFAULT {
|
|
categFld.Id = utils.CATEGORY
|
|
}
|
|
if accountFld == nil {
|
|
accountFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if accountFld.Id == utils.META_DEFAULT {
|
|
accountFld.Id = utils.ACCOUNT
|
|
}
|
|
if subjectFld == nil {
|
|
subjectFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if subjectFld.Id == utils.META_DEFAULT {
|
|
subjectFld.Id = utils.SUBJECT
|
|
}
|
|
if destFld == nil {
|
|
destFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if destFld.Id == utils.META_DEFAULT {
|
|
destFld.Id = utils.DESTINATION
|
|
}
|
|
if setupTimeFld == nil {
|
|
setupTimeFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if setupTimeFld.Id == utils.META_DEFAULT {
|
|
setupTimeFld.Id = utils.SETUP_TIME
|
|
}
|
|
if answerTimeFld == nil {
|
|
answerTimeFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if answerTimeFld.Id == utils.META_DEFAULT {
|
|
answerTimeFld.Id = utils.ANSWER_TIME
|
|
}
|
|
if durationFld == nil {
|
|
durationFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if durationFld.Id == utils.META_DEFAULT {
|
|
durationFld.Id = utils.USAGE
|
|
}
|
|
if pddFld == nil {
|
|
pddFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if pddFld.Id == utils.META_DEFAULT {
|
|
pddFld.Id = utils.PDD
|
|
}
|
|
if supplierFld == nil {
|
|
supplierFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if supplierFld.Id == utils.META_DEFAULT {
|
|
supplierFld.Id = utils.SUPPLIER
|
|
}
|
|
if disconnectCauseFld == nil {
|
|
disconnectCauseFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if disconnectCauseFld.Id == utils.META_DEFAULT {
|
|
disconnectCauseFld.Id = utils.DISCONNECT_CAUSE
|
|
}
|
|
if ratedFld == nil {
|
|
ratedFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if ratedFld.Id == utils.META_DEFAULT {
|
|
ratedFld.Id = utils.RATED_FLD
|
|
}
|
|
if costFld == nil {
|
|
costFld, _ = utils.NewRSRField(utils.META_DEFAULT)
|
|
}
|
|
if costFld.Id == utils.META_DEFAULT {
|
|
costFld.Id = utils.COST
|
|
}
|
|
var err error
|
|
frkStorCdr := new(StoredCdr)
|
|
frkStorCdr.CgrId = storedCdr.CgrId
|
|
frkStorCdr.TOR = storedCdr.TOR
|
|
frkStorCdr.MediationRunId = runId
|
|
frkStorCdr.Cost = -1.0 // Default for non-rated CDR
|
|
frkStorCdr.AccId = storedCdr.AccId
|
|
frkStorCdr.CdrHost = storedCdr.CdrHost
|
|
frkStorCdr.CdrSource = storedCdr.CdrSource
|
|
frkStorCdr.ReqType = storedCdr.FieldAsString(reqTypeFld)
|
|
if primaryMandatory && len(frkStorCdr.ReqType) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.REQTYPE, reqTypeFld.Id)
|
|
}
|
|
frkStorCdr.Direction = storedCdr.FieldAsString(directionFld)
|
|
if primaryMandatory && len(frkStorCdr.Direction) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.DIRECTION, directionFld.Id)
|
|
}
|
|
frkStorCdr.Tenant = storedCdr.FieldAsString(tenantFld)
|
|
if primaryMandatory && len(frkStorCdr.Tenant) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.TENANT, tenantFld.Id)
|
|
}
|
|
frkStorCdr.Category = storedCdr.FieldAsString(categFld)
|
|
if primaryMandatory && len(frkStorCdr.Category) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.CATEGORY, categFld.Id)
|
|
}
|
|
frkStorCdr.Account = storedCdr.FieldAsString(accountFld)
|
|
if primaryMandatory && len(frkStorCdr.Account) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.ACCOUNT, accountFld.Id)
|
|
}
|
|
frkStorCdr.Subject = storedCdr.FieldAsString(subjectFld)
|
|
if primaryMandatory && len(frkStorCdr.Subject) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.SUBJECT, subjectFld.Id)
|
|
}
|
|
frkStorCdr.Destination = storedCdr.FieldAsString(destFld)
|
|
if primaryMandatory && len(frkStorCdr.Destination) == 0 && frkStorCdr.TOR == utils.VOICE {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.DESTINATION, destFld.Id)
|
|
}
|
|
sTimeStr := storedCdr.FieldAsString(setupTimeFld)
|
|
if primaryMandatory && len(sTimeStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.SETUP_TIME, setupTimeFld.Id)
|
|
} else if frkStorCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
aTimeStr := storedCdr.FieldAsString(answerTimeFld)
|
|
if primaryMandatory && len(aTimeStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.ANSWER_TIME, answerTimeFld.Id)
|
|
} else if frkStorCdr.AnswerTime, err = utils.ParseTimeDetectLayout(aTimeStr, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
durStr := storedCdr.FieldAsString(durationFld)
|
|
if primaryMandatory && len(durStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.USAGE, durationFld.Id)
|
|
} else if frkStorCdr.Usage, err = utils.ParseDurationWithSecs(durStr); err != nil {
|
|
return nil, err
|
|
}
|
|
pddStr := storedCdr.FieldAsString(pddFld)
|
|
if primaryMandatory && len(pddStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.PDD, pddFld.Id)
|
|
} else if frkStorCdr.Pdd, err = utils.ParseDurationWithSecs(pddStr); err != nil {
|
|
return nil, err
|
|
}
|
|
frkStorCdr.Supplier = storedCdr.FieldAsString(supplierFld)
|
|
frkStorCdr.DisconnectCause = storedCdr.FieldAsString(disconnectCauseFld)
|
|
ratedStr := storedCdr.FieldAsString(ratedFld)
|
|
if primaryMandatory && len(ratedStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.RATED_FLD, ratedFld.Id)
|
|
} else if frkStorCdr.Rated, err = strconv.ParseBool(ratedStr); err != nil {
|
|
return nil, err
|
|
}
|
|
costStr := storedCdr.FieldAsString(costFld)
|
|
if primaryMandatory && len(costStr) == 0 {
|
|
return nil, utils.NewErrMandatoryIeMissing(utils.COST, costFld.Id)
|
|
} else if frkStorCdr.Cost, err = strconv.ParseFloat(costStr, 64); err != nil {
|
|
return nil, err
|
|
}
|
|
frkStorCdr.ExtraFields = make(map[string]string, len(extraFlds))
|
|
for _, fld := range extraFlds {
|
|
frkStorCdr.ExtraFields[fld.Id] = storedCdr.FieldAsString(fld)
|
|
}
|
|
return frkStorCdr, nil
|
|
}
|
|
|
|
func (storedCdr *StoredCdr) AsExternalCdr() *ExternalCdr {
|
|
return &ExternalCdr{CgrId: storedCdr.CgrId,
|
|
OrderId: storedCdr.OrderId,
|
|
TOR: storedCdr.TOR,
|
|
AccId: storedCdr.AccId,
|
|
CdrHost: storedCdr.CdrHost,
|
|
CdrSource: storedCdr.CdrSource,
|
|
ReqType: storedCdr.ReqType,
|
|
Direction: storedCdr.Direction,
|
|
Tenant: storedCdr.Tenant,
|
|
Category: storedCdr.Category,
|
|
Account: storedCdr.Account,
|
|
Subject: storedCdr.Subject,
|
|
Destination: storedCdr.Destination,
|
|
SetupTime: storedCdr.SetupTime.Format(time.RFC3339),
|
|
AnswerTime: storedCdr.AnswerTime.Format(time.RFC3339),
|
|
Usage: storedCdr.FormatUsage(utils.SECONDS),
|
|
Pdd: storedCdr.FieldAsString(&utils.RSRField{Id: utils.PDD}),
|
|
Supplier: storedCdr.Supplier,
|
|
DisconnectCause: storedCdr.DisconnectCause,
|
|
ExtraFields: storedCdr.ExtraFields,
|
|
MediationRunId: storedCdr.MediationRunId,
|
|
RatedAccount: storedCdr.RatedAccount,
|
|
RatedSubject: storedCdr.RatedSubject,
|
|
Cost: storedCdr.Cost,
|
|
CostDetails: storedCdr.CostDetailsJson(),
|
|
}
|
|
}
|
|
|
|
// Implementation of Event interface, used in tests
|
|
func (storedCdr *StoredCdr) AsEvent(ignored string) Event {
|
|
return Event(storedCdr)
|
|
}
|
|
func (storedCdr *StoredCdr) ComputeLcr() bool {
|
|
return false
|
|
}
|
|
func (storedCdr *StoredCdr) GetName() string {
|
|
return storedCdr.CdrSource
|
|
}
|
|
func (storedCdr *StoredCdr) GetCgrId(timezone string) string {
|
|
return storedCdr.CgrId
|
|
}
|
|
func (storedCdr *StoredCdr) GetUUID() string {
|
|
return storedCdr.AccId
|
|
}
|
|
func (storedCdr *StoredCdr) GetSessionIds() []string {
|
|
return []string{storedCdr.GetUUID()}
|
|
}
|
|
func (storedCdr *StoredCdr) GetDirection(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.DIRECTION, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Direction
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetSubject(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.SUBJECT, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Subject
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetAccount(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.ACCOUNT, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Account
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetDestination(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Destination
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetCallDestNr(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.DESTINATION, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Destination
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetCategory(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.CATEGORY, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Category
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetTenant(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.TENANT, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Tenant
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetReqType(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.REQTYPE, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.ReqType
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetSetupTime(fieldName, timezone string) (time.Time, error) {
|
|
if utils.IsSliceMember([]string{utils.SETUP_TIME, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.SetupTime, nil
|
|
}
|
|
var sTimeVal string
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
sTimeVal = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
} else {
|
|
sTimeVal = storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
return utils.ParseTimeDetectLayout(sTimeVal, timezone)
|
|
}
|
|
func (storedCdr *StoredCdr) GetAnswerTime(fieldName, timezone string) (time.Time, error) {
|
|
if utils.IsSliceMember([]string{utils.ANSWER_TIME, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.AnswerTime, nil
|
|
}
|
|
var aTimeVal string
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
aTimeVal = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
} else {
|
|
aTimeVal = storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
return utils.ParseTimeDetectLayout(aTimeVal, timezone)
|
|
}
|
|
func (storedCdr *StoredCdr) GetEndTime() (time.Time, error) {
|
|
return storedCdr.AnswerTime.Add(storedCdr.Usage), nil
|
|
}
|
|
func (storedCdr *StoredCdr) GetDuration(fieldName string) (time.Duration, error) {
|
|
if utils.IsSliceMember([]string{utils.USAGE, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Usage, nil
|
|
}
|
|
var durVal string
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
durVal = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
} else {
|
|
durVal = storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
return utils.ParseDurationWithSecs(durVal)
|
|
}
|
|
func (storedCdr *StoredCdr) GetPdd(fieldName string) (time.Duration, error) {
|
|
if utils.IsSliceMember([]string{utils.PDD, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Pdd, nil
|
|
}
|
|
var pddVal string
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
pddVal = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
} else {
|
|
pddVal = storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
return utils.ParseDurationWithSecs(pddVal)
|
|
}
|
|
func (storedCdr *StoredCdr) GetSupplier(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.SUPPLIER, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.Supplier
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetDisconnectCause(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.DISCONNECT_CAUSE, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.DisconnectCause
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetOriginatorIP(fieldName string) string {
|
|
if utils.IsSliceMember([]string{utils.CDRHOST, utils.META_DEFAULT}, fieldName) {
|
|
return storedCdr.CdrHost
|
|
}
|
|
return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName})
|
|
}
|
|
func (storedCdr *StoredCdr) GetExtraFields() map[string]string {
|
|
return storedCdr.ExtraFields
|
|
}
|
|
func (storedCdr *StoredCdr) MissingParameter() bool {
|
|
return len(storedCdr.AccId) == 0 ||
|
|
len(storedCdr.Category) == 0 ||
|
|
len(storedCdr.Tenant) == 0 ||
|
|
len(storedCdr.Account) == 0 ||
|
|
len(storedCdr.Destination) == 0
|
|
}
|
|
func (storedCdr *StoredCdr) ParseEventValue(rsrFld *utils.RSRField, timezone string) string {
|
|
return storedCdr.FieldAsString(rsrFld)
|
|
}
|
|
func (storedCdr *StoredCdr) String() string {
|
|
mrsh, _ := json.Marshal(storedCdr)
|
|
return string(mrsh)
|
|
}
|
|
|
|
type ExternalCdr struct {
|
|
CgrId string
|
|
OrderId int64
|
|
TOR string
|
|
AccId string
|
|
CdrHost string
|
|
CdrSource string
|
|
ReqType string
|
|
Direction string
|
|
Tenant string
|
|
Category string
|
|
Account string
|
|
Subject string
|
|
Destination string
|
|
SetupTime string
|
|
AnswerTime string
|
|
Usage string
|
|
Pdd string
|
|
Supplier string
|
|
DisconnectCause string
|
|
ExtraFields map[string]string
|
|
MediationRunId string
|
|
RatedAccount string
|
|
RatedSubject string
|
|
Cost float64
|
|
CostDetails string
|
|
Rated bool // Mark the CDR as rated so we do not process it during mediation
|
|
}
|
|
|
|
// Used when authorizing requests from outside, eg ApierV1.GetMaxUsage
|
|
type UsageRecord struct {
|
|
TOR string
|
|
ReqType string
|
|
Direction string
|
|
Tenant string
|
|
Category string
|
|
Account string
|
|
Subject string
|
|
Destination string
|
|
SetupTime string
|
|
AnswerTime string
|
|
Usage string
|
|
ExtraFields map[string]string
|
|
}
|
|
|
|
func (self *UsageRecord) AsStoredCdr(timezone string) (*StoredCdr, error) {
|
|
var err error
|
|
storedCdr := &StoredCdr{TOR: self.TOR, ReqType: self.ReqType, Direction: self.Direction, Tenant: self.Tenant, Category: self.Category,
|
|
Account: self.Account, Subject: self.Subject, Destination: self.Destination}
|
|
if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(self.SetupTime, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(self.AnswerTime, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
if storedCdr.Usage, err = utils.ParseDurationWithSecs(self.Usage); err != nil {
|
|
return nil, err
|
|
}
|
|
if self.ExtraFields != nil {
|
|
storedCdr.ExtraFields = make(map[string]string)
|
|
}
|
|
for k, v := range self.ExtraFields {
|
|
storedCdr.ExtraFields[k] = v
|
|
}
|
|
return storedCdr, nil
|
|
}
|
|
|
|
func (self *UsageRecord) AsCallDescriptor(timezone string) (*CallDescriptor, error) {
|
|
var err error
|
|
cd := &CallDescriptor{
|
|
TOR: self.TOR,
|
|
Direction: self.Direction,
|
|
Tenant: self.Tenant,
|
|
Category: self.Category,
|
|
Subject: self.Subject,
|
|
Account: self.Account,
|
|
Destination: self.Destination,
|
|
}
|
|
timeStr := self.AnswerTime
|
|
if len(timeStr) == 0 { // In case of auth, answer time will not be defined, so take it out of setup one
|
|
timeStr = self.SetupTime
|
|
}
|
|
if cd.TimeStart, err = utils.ParseTimeDetectLayout(timeStr, timezone); err != nil {
|
|
return nil, err
|
|
}
|
|
if usage, err := utils.ParseDurationWithSecs(self.Usage); err != nil {
|
|
return nil, err
|
|
} else {
|
|
cd.TimeEnd = cd.TimeStart.Add(usage)
|
|
}
|
|
if self.ExtraFields != nil {
|
|
cd.ExtraFields = make(map[string]string)
|
|
}
|
|
for k, v := range self.ExtraFields {
|
|
cd.ExtraFields[k] = v
|
|
}
|
|
return cd, nil
|
|
}
|