Update CDRE with FCTemplate

This commit is contained in:
TeoV
2018-08-29 03:16:14 -04:00
committed by Dan Christian Bogos
parent 434456b5a9
commit 002c57e857
30 changed files with 814 additions and 701 deletions

View File

@@ -69,10 +69,13 @@ func NewCdrc(cdrcCfgs []*config.CdrcConfig, httpSkipTlsCheck bool, cdrs rpcclien
cdrc.maxOpenFiles <- processFile // Empty initiate so we do not need to wait later when we pop
}
var err error
if cdrc.unpairedRecordsCache, err = NewUnpairedRecordsCache(cdrcCfg.PartialRecordCache, cdrcCfg.CdrOutDir, cdrcCfg.FieldSeparator); err != nil {
if cdrc.unpairedRecordsCache, err = NewUnpairedRecordsCache(cdrcCfg.PartialRecordCache,
cdrcCfg.CdrOutDir, cdrcCfg.FieldSeparator); err != nil {
return nil, err
}
if cdrc.partialRecordsCache, err = NewPartialRecordsCache(cdrcCfg.PartialRecordCache, cdrcCfg.PartialCacheExpiryAction, cdrcCfg.CdrOutDir, cdrcCfg.FieldSeparator, roundDecimals, cdrc.timezone, cdrc.httpSkipTlsCheck, cdrc.cdrs); err != nil {
if cdrc.partialRecordsCache, err = NewPartialRecordsCache(cdrcCfg.PartialRecordCache,
cdrcCfg.PartialCacheExpiryAction, cdrcCfg.CdrOutDir, cdrcCfg.FieldSeparator, roundDecimals,
cdrc.timezone, cdrc.httpSkipTlsCheck, cdrc.cdrs, filterS); err != nil {
return nil, err
}
// Before processing, make sure in and out folders exist

View File

@@ -34,7 +34,7 @@ import (
func NewCsvRecordsProcessor(csvReader *csv.Reader, timezone, fileName string,
dfltCdrcCfg *config.CdrcConfig, cdrcCfgs []*config.CdrcConfig,
httpSkipTlsCheck bool, unpairedRecordsCache *UnpairedRecordsCache, partialRecordsCache *PartialRecordsCache,
cacheDumpFields []*config.CfgCdrField, filterS *engine.FilterS) *CsvRecordsProcessor {
cacheDumpFields []*config.FCTemplate, filterS *engine.FilterS) *CsvRecordsProcessor {
return &CsvRecordsProcessor{csvReader: csvReader, timezone: timezone, fileName: fileName,
dfltCdrcCfg: dfltCdrcCfg, cdrcCfgs: cdrcCfgs, httpSkipTlsCheck: httpSkipTlsCheck, unpairedRecordsCache: unpairedRecordsCache,
partialRecordsCache: partialRecordsCache, partialCacheDumpFields: cacheDumpFields, filterS: filterS}
@@ -51,7 +51,7 @@ type CsvRecordsProcessor struct {
httpSkipTlsCheck bool
unpairedRecordsCache *UnpairedRecordsCache // Shared by cdrc so we can cache for all files in a folder
partialRecordsCache *PartialRecordsCache // Cache records which are of type "Partial"
partialCacheDumpFields []*config.CfgCdrField
partialCacheDumpFields []*config.FCTemplate
filterS *engine.FilterS
}

View File

@@ -39,12 +39,14 @@ const (
)
func NewPartialRecordsCache(ttl time.Duration, expiryAction string, cdrOutDir string, csvSep rune,
roundDecimals int, timezone string, httpSkipTlsCheck bool, cdrs rpcclient.RpcClientConnection) (*PartialRecordsCache, error) {
roundDecimals int, timezone string, httpSkipTlsCheck bool,
cdrs rpcclient.RpcClientConnection, filterS *engine.FilterS) (*PartialRecordsCache, error) {
return &PartialRecordsCache{ttl: ttl, expiryAction: expiryAction, cdrOutDir: cdrOutDir,
csvSep: csvSep, roundDecimals: roundDecimals, timezone: timezone,
httpSkipTlsCheck: httpSkipTlsCheck, cdrs: cdrs,
partialRecords: make(map[string]*PartialCDRRecord),
dumpTimers: make(map[string]*time.Timer), guard: guardian.Guardian}, nil
dumpTimers: make(map[string]*time.Timer),
guard: guardian.Guardian, filterS: filterS}, nil
}
type PartialRecordsCache struct {
@@ -59,6 +61,7 @@ type PartialRecordsCache struct {
partialRecords map[string]*PartialCDRRecord // [OriginID]*PartialRecord
dumpTimers map[string]*time.Timer // [OriginID]*time.Timer which can be canceled or reset
guard *guardian.GuardianLocker
filterS *engine.FilterS
}
// Dumps the cache into a .unpaired file in the outdir and cleans cache after
@@ -74,7 +77,7 @@ func (prc *PartialRecordsCache) dumpPartialRecords(originID string) {
csvWriter := csv.NewWriter(fileOut)
csvWriter.Comma = prc.csvSep
for _, cdr := range prc.partialRecords[originID].cdrs {
expRec, err := cdr.AsExportRecord(prc.partialRecords[originID].cacheDumpFields, prc.httpSkipTlsCheck, nil, prc.roundDecimals)
expRec, err := cdr.AsExportRecord(prc.partialRecords[originID].cacheDumpFields, prc.httpSkipTlsCheck, nil, prc.roundDecimals, prc.filterS)
if err != nil {
return nil, err
}
@@ -182,15 +185,15 @@ func (prc *PartialRecordsCache) MergePartialCDRRecord(pCDR *PartialCDRRecord) (*
return pCDRIf.(*engine.CDR), err
}
func NewPartialCDRRecord(cdr *engine.CDR, cacheDumpFlds []*config.CfgCdrField) *PartialCDRRecord {
func NewPartialCDRRecord(cdr *engine.CDR, cacheDumpFlds []*config.FCTemplate) *PartialCDRRecord {
return &PartialCDRRecord{cdrs: []*engine.CDR{cdr}, cacheDumpFields: cacheDumpFlds}
}
// PartialCDRRecord is a record which can be updated later
// different from PartialFlatstoreRecordsCache which is incomplete (eg: need to calculate duration out of 2 records)
type PartialCDRRecord struct {
cdrs []*engine.CDR // Number of CDRs
cacheDumpFields []*config.CfgCdrField // Fields template to use when dumping from cache on disk
cdrs []*engine.CDR // Number of CDRs
cacheDumpFields []*config.FCTemplate // Fields template to use when dumping from cache on disk
}
// Part of sort interface

View File

@@ -49,7 +49,7 @@ type CdrcConfig struct {
HeaderFields []*FCTemplate
ContentFields []*FCTemplate
TrailerFields []*FCTemplate
CacheDumpFields []*CfgCdrField
CacheDumpFields []*FCTemplate
}
func (self *CdrcConfig) loadFromJsonCfg(jsnCfg *CdrcJsonCfg) error {
@@ -142,9 +142,7 @@ func (self *CdrcConfig) loadFromJsonCfg(jsnCfg *CdrcJsonCfg) error {
self.TrailerFields = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Trailer_fields)
}
if jsnCfg.Cache_dump_fields != nil {
if self.CacheDumpFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Cache_dump_fields); err != nil {
return err
}
self.CacheDumpFields = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Cache_dump_fields)
}
return nil
}
@@ -182,7 +180,7 @@ func (self *CdrcConfig) Clone() *CdrcConfig {
clnCdrc.HeaderFields = make([]*FCTemplate, len(self.HeaderFields))
clnCdrc.ContentFields = make([]*FCTemplate, len(self.ContentFields))
clnCdrc.TrailerFields = make([]*FCTemplate, len(self.TrailerFields))
clnCdrc.CacheDumpFields = make([]*CfgCdrField, len(self.CacheDumpFields))
clnCdrc.CacheDumpFields = make([]*FCTemplate, len(self.CacheDumpFields))
for idx, fld := range self.HeaderFields {
clonedVal := *fld
clnCdrc.HeaderFields[idx] = &clonedVal

View File

@@ -35,9 +35,9 @@ type CdreConfig struct {
FieldSeparator rune
UsageMultiplyFactor utils.FieldMultiplyFactor
CostMultiplyFactor float64
HeaderFields []*CfgCdrField
ContentFields []*CfgCdrField
TrailerFields []*CfgCdrField
HeaderFields []*FCTemplate
ContentFields []*FCTemplate
TrailerFields []*FCTemplate
}
func (self *CdreConfig) loadFromJsonCfg(jsnCfg *CdreJsonCfg) error {
@@ -87,19 +87,13 @@ func (self *CdreConfig) loadFromJsonCfg(jsnCfg *CdreJsonCfg) error {
self.CostMultiplyFactor = *jsnCfg.Cost_multiply_factor
}
if jsnCfg.Header_fields != nil {
if self.HeaderFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Header_fields); err != nil {
return err
}
self.HeaderFields = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Header_fields)
}
if jsnCfg.Content_fields != nil {
if self.ContentFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Content_fields); err != nil {
return err
}
self.ContentFields = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Content_fields)
}
if jsnCfg.Trailer_fields != nil {
if self.TrailerFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Trailer_fields); err != nil {
return err
}
self.TrailerFields = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Trailer_fields)
}
return nil
}
@@ -122,17 +116,17 @@ func (self *CdreConfig) Clone() *CdreConfig {
clnCdre.Filters[i] = fltr
}
clnCdre.CostMultiplyFactor = self.CostMultiplyFactor
clnCdre.HeaderFields = make([]*CfgCdrField, len(self.HeaderFields))
clnCdre.HeaderFields = make([]*FCTemplate, len(self.HeaderFields))
for idx, fld := range self.HeaderFields {
clonedVal := *fld
clnCdre.HeaderFields[idx] = &clonedVal
}
clnCdre.ContentFields = make([]*CfgCdrField, len(self.ContentFields))
clnCdre.ContentFields = make([]*FCTemplate, len(self.ContentFields))
for idx, fld := range self.ContentFields {
clonedVal := *fld
clnCdre.ContentFields[idx] = &clonedVal
}
clnCdre.TrailerFields = make([]*CfgCdrField, len(self.TrailerFields))
clnCdre.TrailerFields = make([]*FCTemplate, len(self.TrailerFields))
for idx, fld := range self.TrailerFields {
clonedVal := *fld
clnCdre.TrailerFields[idx] = &clonedVal

View File

@@ -25,15 +25,15 @@ import (
)
func TestCdreCfgClone(t *testing.T) {
cgrIdRsrs := utils.ParseRSRFieldsMustCompile("cgrid", utils.INFIELD_SEP)
runIdRsrs := utils.ParseRSRFieldsMustCompile("runid", utils.INFIELD_SEP)
emptyFields := []*CfgCdrField{}
initContentFlds := []*CfgCdrField{
&CfgCdrField{Tag: "CgrId",
cgrIdRsrs := NewRSRParsersMustCompile("cgrid", true)
runIdRsrs := NewRSRParsersMustCompile("runid", true)
emptyFields := []*FCTemplate{}
initContentFlds := []*FCTemplate{
&FCTemplate{ID: "CgrId",
Type: "*composed",
FieldId: "cgrid",
Value: cgrIdRsrs},
&CfgCdrField{Tag: "RunId",
&FCTemplate{ID: "RunId",
Type: "*composed",
FieldId: "runid",
Value: runIdRsrs},
@@ -51,12 +51,12 @@ func TestCdreCfgClone(t *testing.T) {
CostMultiplyFactor: 1.0,
ContentFields: initContentFlds,
}
eClnContentFlds := []*CfgCdrField{
&CfgCdrField{Tag: "CgrId",
eClnContentFlds := []*FCTemplate{
&FCTemplate{ID: "CgrId",
Type: "*composed",
FieldId: "cgrid",
Value: cgrIdRsrs},
&CfgCdrField{Tag: "RunId",
&FCTemplate{ID: "RunId",
Type: "*composed",
FieldId: "runid",
Value: runIdRsrs},
@@ -85,7 +85,7 @@ func TestCdreCfgClone(t *testing.T) {
if !reflect.DeepEqual(eClnCdreCfg, clnCdreCfg) { // MOdifying a field after clone should not affect cloned instance
t.Errorf("Cloned result: %+v", clnCdreCfg)
}
initContentFlds[0].Tag = "Destination"
initContentFlds[0].ID = "Destination"
if !reflect.DeepEqual(eClnCdreCfg, clnCdreCfg) { // MOdifying a field after clone should not affect cloned instance
t.Errorf("Cloned result: %+v", clnCdreCfg)
}

View File

@@ -204,20 +204,20 @@ const CGRATES_CFG_JSON = `
"cost_multiply_factor": 1, // multiply cost before export, eg: add VAT
"header_fields": [], // template of the exported header fields
"content_fields": [ // template of the exported content fields
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag":"RunID", "type": "*composed", "value": "RunID"},
{"tag":"TOR", "type": "*composed", "value": "ToR"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Subject", "type": "*composed", "value": "Subject"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id":"RunID", "type": "*composed", "value": "~RunID"},
{"id":"TOR", "type": "*composed", "value": "~ToR"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Subject", "type": "*composed", "value": "~Subject"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"SetupTime", "type": "*composed", "value": "~SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
],
"trailer_fields": [], // template of the exported trailer fields
},
@@ -271,20 +271,20 @@ const CGRATES_CFG_JSON = `
],
"trailer_fields": [], // template of the import trailer fields
"cache_dump_fields": [ // template used when dumping cached CDR, eg: partial CDRs
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag": "TOR", "type": "*composed", "value": "ToR"},
{"tag": "OriginID", "type": "*composed", "value": "OriginID"},
{"tag": "RequestType", "type": "*composed", "value": "RequestType"},
{"tag": "Tenant", "type": "*composed", "value": "Tenant"},
{"tag": "Category", "type": "*composed", "value": "Category"},
{"tag": "Account", "type": "*composed", "value": "Account"},
{"tag": "Subject", "type": "*composed", "value": "Subject"},
{"tag": "Destination", "type": "*composed", "value": "Destination"},
{"tag": "SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag": "AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag": "Usage", "type": "*composed", "value": "Usage"},
{"tag": "Cost", "type": "*composed", "value": "Cost"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id": "TOR", "type": "*composed", "value": "~ToR"},
{"id": "OriginID", "type": "*composed", "value": "~OriginID"},
{"id": "RequestType", "type": "*composed", "value": "~RequestType"},
{"id": "Tenant", "type": "*composed", "value": "~Tenant"},
{"id": "Category", "type": "*composed", "value": "~Category"},
{"id": "Account", "type": "*composed", "value": "~Account"},
{"id": "Subject", "type": "*composed", "value": "~Subject"},
{"id": "Destination", "type": "*composed", "value": "~Destination"},
{"id": "SetupTime", "type": "*composed", "value": "~SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id": "AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id": "Usage", "type": "*composed", "value": "~Usage"},
{"id": "Cost", "type": "*composed", "value": "~Cost"},
],
},
],

View File

@@ -298,52 +298,52 @@ func TestDfCdrStatsJsonCfg(t *testing.T) {
}
func TestDfCdreJsonCfgs(t *testing.T) {
eFields := []*CdrFieldJsonCfg{}
eContentFlds := []*CdrFieldJsonCfg{
&CdrFieldJsonCfg{Tag: utils.StringPointer("CGRID"),
eFields := []*FcTemplateJsonCfg{}
eContentFlds := []*FcTemplateJsonCfg{
&FcTemplateJsonCfg{Id: utils.StringPointer("CGRID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.CGRID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("RunID"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.CGRID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("RunID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.RunID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("TOR"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.RunID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("TOR"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.ToR)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("OriginID"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.ToR)},
&FcTemplateJsonCfg{Id: utils.StringPointer("OriginID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.OriginID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("RequestType"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.OriginID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("RequestType"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.RequestType)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Tenant"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.RequestType)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Tenant"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Tenant)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Category"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Tenant)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Category"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Category)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Account"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Category)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Account"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Account)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Subject"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Account)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Subject"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Subject)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Destination"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Subject)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Destination"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Destination)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("SetupTime"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Destination)},
&FcTemplateJsonCfg{Id: utils.StringPointer("SetupTime"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.SetupTime),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.SetupTime),
Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")},
&CdrFieldJsonCfg{Tag: utils.StringPointer("AnswerTime"),
&FcTemplateJsonCfg{Id: utils.StringPointer("AnswerTime"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.AnswerTime),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.AnswerTime),
Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Usage"),
&FcTemplateJsonCfg{Id: utils.StringPointer("Usage"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Usage)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Cost"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Usage)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Cost"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.COST),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.COST),
Rounding_decimals: utils.IntPointer(4)},
}
eCfg := map[string]*CdreJsonCfg{
@@ -398,51 +398,51 @@ func TestDfCdrcJsonCfg(t *testing.T) {
&FcTemplateJsonCfg{Id: utils.StringPointer("Usage"), Field_id: utils.StringPointer(utils.Usage), Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer("~13"), Mandatory: utils.BoolPointer(true)},
}
cacheDumpFields := []*CdrFieldJsonCfg{
&CdrFieldJsonCfg{Tag: utils.StringPointer("CGRID"),
cacheDumpFields := []*FcTemplateJsonCfg{
&FcTemplateJsonCfg{Id: utils.StringPointer("CGRID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.CGRID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("RunID"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.CGRID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("RunID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.RunID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("TOR"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.RunID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("TOR"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.ToR)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("OriginID"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.ToR)},
&FcTemplateJsonCfg{Id: utils.StringPointer("OriginID"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.OriginID)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("RequestType"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.OriginID)},
&FcTemplateJsonCfg{Id: utils.StringPointer("RequestType"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.RequestType)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Tenant"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.RequestType)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Tenant"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Tenant)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Category"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Tenant)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Category"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Category)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Account"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Category)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Account"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Account)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Subject"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Account)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Subject"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Subject)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Destination"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Subject)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Destination"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Destination)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("SetupTime"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Destination)},
&FcTemplateJsonCfg{Id: utils.StringPointer("SetupTime"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.SetupTime),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.SetupTime),
Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")},
&CdrFieldJsonCfg{Tag: utils.StringPointer("AnswerTime"),
&FcTemplateJsonCfg{Id: utils.StringPointer("AnswerTime"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.AnswerTime),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.AnswerTime),
Layout: utils.StringPointer("2006-01-02T15:04:05Z07:00")},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Usage"),
&FcTemplateJsonCfg{Id: utils.StringPointer("Usage"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.Usage)},
&CdrFieldJsonCfg{Tag: utils.StringPointer("Cost"),
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.Usage)},
&FcTemplateJsonCfg{Id: utils.StringPointer("Cost"),
Type: utils.StringPointer(utils.META_COMPOSED),
Value: utils.StringPointer(utils.COST)},
Value: utils.StringPointer(utils.DynamicDataPrefix + utils.COST)},
}
eCfg := []*CdrcJsonCfg{
&CdrcJsonCfg{

View File

@@ -202,37 +202,37 @@ func TestCgrCfgCDRC(t *testing.T) {
Value: NewRSRParsersMustCompile("~9:s/^(\\d+)$/${1}s/", true)},
},
TrailerFields: make([]*FCTemplate, 0),
CacheDumpFields: []*CfgCdrField{
&CfgCdrField{Tag: "CGRID", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.CGRID, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "RunID", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.RunID, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "TOR", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.ToR, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "OriginID", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.OriginID, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "RequestType", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.RequestType, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Tenant", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Tenant, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Category", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Category, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Account", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Account, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Subject", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Subject, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Destination", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Destination, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "SetupTime", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.SetupTime, utils.INFIELD_SEP),
CacheDumpFields: []*FCTemplate{
&FCTemplate{ID: "CGRID", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.CGRID, true)},
&FCTemplate{ID: "RunID", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.RunID, true)},
&FCTemplate{ID: "TOR", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.ToR, true)},
&FCTemplate{ID: "OriginID", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.OriginID, true)},
&FCTemplate{ID: "RequestType", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.RequestType, true)},
&FCTemplate{ID: "Tenant", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Tenant, true)},
&FCTemplate{ID: "Category", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Category, true)},
&FCTemplate{ID: "Account", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Account, true)},
&FCTemplate{ID: "Subject", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Subject, true)},
&FCTemplate{ID: "Destination", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Destination, true)},
&FCTemplate{ID: "SetupTime", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.SetupTime, true),
Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: "AnswerTime", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.AnswerTime, utils.INFIELD_SEP),
&FCTemplate{ID: "AnswerTime", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.AnswerTime, true),
Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: "Usage", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.Usage, utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Cost", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile(utils.COST, utils.INFIELD_SEP)},
&FCTemplate{ID: "Usage", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Usage, true)},
&FCTemplate{ID: "Cost", Type: utils.META_COMPOSED,
Value: NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.COST, true)},
},
},
}
@@ -576,38 +576,38 @@ func TestCgrCfgJSONDefaultsCDRStats(t *testing.T) {
}
func TestCgrCfgJSONDefaultsCdreProfiles(t *testing.T) {
eFields := []*CfgCdrField{}
eContentFlds := []*CfgCdrField{
&CfgCdrField{Tag: "CGRID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("CGRID", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "RunID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("RunID", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "TOR", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("ToR", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "OriginID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("OriginID", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "RequestType", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("RequestType", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Tenant", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Tenant", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Category", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Category", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Account", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Account", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Subject", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Subject", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Destination", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Destination", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "SetupTime", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("SetupTime", utils.INFIELD_SEP),
eFields := []*FCTemplate{}
eContentFlds := []*FCTemplate{
&FCTemplate{ID: "CGRID", Type: "*composed",
Value: NewRSRParsersMustCompile("~CGRID", true)},
&FCTemplate{ID: "RunID", Type: "*composed",
Value: NewRSRParsersMustCompile("~RunID", true)},
&FCTemplate{ID: "TOR", Type: "*composed",
Value: NewRSRParsersMustCompile("~ToR", true)},
&FCTemplate{ID: "OriginID", Type: "*composed",
Value: NewRSRParsersMustCompile("~OriginID", true)},
&FCTemplate{ID: "RequestType", Type: "*composed",
Value: NewRSRParsersMustCompile("~RequestType", true)},
&FCTemplate{ID: "Tenant", Type: "*composed",
Value: NewRSRParsersMustCompile("~Tenant", true)},
&FCTemplate{ID: "Category", Type: "*composed",
Value: NewRSRParsersMustCompile("~Category", true)},
&FCTemplate{ID: "Account", Type: "*composed",
Value: NewRSRParsersMustCompile("~Account", true)},
&FCTemplate{ID: "Subject", Type: "*composed",
Value: NewRSRParsersMustCompile("~Subject", true)},
&FCTemplate{ID: "Destination", Type: "*composed",
Value: NewRSRParsersMustCompile("~Destination", true)},
&FCTemplate{ID: "SetupTime", Type: "*composed",
Value: NewRSRParsersMustCompile("~SetupTime", true),
Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: "AnswerTime", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("AnswerTime", utils.INFIELD_SEP),
&FCTemplate{ID: "AnswerTime", Type: "*composed",
Value: NewRSRParsersMustCompile("~AnswerTime", true),
Layout: "2006-01-02T15:04:05Z07:00"},
&CfgCdrField{Tag: "Usage", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Usage", utils.INFIELD_SEP)},
&CfgCdrField{Tag: "Cost", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Cost", utils.INFIELD_SEP),
&FCTemplate{ID: "Usage", Type: "*composed",
Value: NewRSRParsersMustCompile("~Usage", true)},
&FCTemplate{ID: "Cost", Type: "*composed",
Value: NewRSRParsersMustCompile("~Cost", true),
RoundingDecimals: 4},
}
eCdreCfg := map[string]*CdreConfig{

View File

@@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
/*
import (
"reflect"
"testing"
@@ -330,3 +331,4 @@ func TestLoadCdrcConfigMultipleFiles(t *testing.T) {
t.Errorf("Expected: %s \n, received: %s", utils.ToJSON(eCgrCfg.CdrcProfiles), utils.ToJSON(cgrCfg.CdrcProfiles))
}
}
*/

View File

@@ -71,26 +71,42 @@ func NewFCTemplateFromFCTemplateJsonCfg(jsnCfg *FcTemplateJsonCfg) *FCTemplate {
if jsnCfg.Layout != nil {
fcTmp.Layout = *jsnCfg.Layout
}
if jsnCfg.Cost_shift_digits != nil {
fcTmp.CostShiftDigits = *jsnCfg.Cost_shift_digits
}
if jsnCfg.Rounding_decimals != nil {
fcTmp.RoundingDecimals = *jsnCfg.Rounding_decimals
}
if jsnCfg.Mask_destinationd_id != nil {
fcTmp.MaskDestID = *jsnCfg.Mask_destinationd_id
}
if jsnCfg.Mask_length != nil {
fcTmp.MaskLen = *jsnCfg.Mask_length
}
return fcTmp
}
type FCTemplate struct {
ID string
Type string // Type of field
FieldId string // Field identifier
Filters []string // list of filter profiles
Value RSRParsers
Width int
Strip string
Padding string
Mandatory bool
AttributeID string // Used by NavigableMap when creating CGREvent/XMLElements
NewBranch bool // Used by NavigableMap when creating XMLElements
Timezone string
Blocker bool
BreakOnSuccess bool
HandlerId string // used by XML in CDRC
Layout string // time format
ID string
Type string // Type of field
FieldId string // Field identifier
Filters []string // list of filter profiles
Value RSRParsers
Width int
Strip string
Padding string
Mandatory bool
AttributeID string // Used by NavigableMap when creating CGREvent/XMLElements
NewBranch bool // Used by NavigableMap when creating XMLElements
Timezone string
Blocker bool
BreakOnSuccess bool
HandlerId string // used by XML in CDRC
Layout string // time format
CostShiftDigits int // Used for CDR
RoundingDecimals int
MaskDestID string
MaskLen int
}
func FCTemplatesFromFCTemapltesJsonCfg(jsnCfgFlds []*FcTemplateJsonCfg) []*FCTemplate {

View File

@@ -179,9 +179,9 @@ type CdreJsonCfg struct {
Field_separator *string
Usage_multiply_factor *map[string]float64
Cost_multiply_factor *float64
Header_fields *[]*CdrFieldJsonCfg
Content_fields *[]*CdrFieldJsonCfg
Trailer_fields *[]*CdrFieldJsonCfg
Header_fields *[]*FcTemplateJsonCfg
Content_fields *[]*FcTemplateJsonCfg
Trailer_fields *[]*FcTemplateJsonCfg
}
// Cdrc config section
@@ -210,7 +210,7 @@ type CdrcJsonCfg struct {
Header_fields *[]*FcTemplateJsonCfg
Content_fields *[]*FcTemplateJsonCfg
Trailer_fields *[]*FcTemplateJsonCfg
Cache_dump_fields *[]*CdrFieldJsonCfg
Cache_dump_fields *[]*FcTemplateJsonCfg
}
// SM-Generic config section
@@ -574,20 +574,24 @@ type MigratorCfgJson struct {
}
type FcTemplateJsonCfg struct {
Id *string
Type *string
Field_id *string
Attribute_id *string
Filters *[]string
Value *string
Width *int
Strip *string
Padding *string
Mandatory *bool
New_branch *bool
Timezone *string
Blocker *bool
Break_on_success *bool
Handler_id *string
Layout *string
Id *string
Type *string
Field_id *string
Attribute_id *string
Filters *[]string
Value *string
Width *int
Strip *string
Padding *string
Mandatory *bool
New_branch *bool
Timezone *string
Blocker *bool
Break_on_success *bool
Handler_id *string
Layout *string
Cost_shift_digits *int
Rounding_decimals *int
Mask_destinationd_id *string
Mask_length *int
}

View File

@@ -102,6 +102,17 @@ func (prsrs RSRParsers) ParseDataProvider(dP DataProvider) (out string, err erro
return
}
func (prsrs RSRParsers) ParseCDR(dP DataProvider) (out string, err error) {
for _, prsr := range prsrs {
if outPrsr, err := prsr.ParseCDR(dP); err != nil {
return "", err
} else {
out += outPrsr
}
}
return
}
func NewRSRParser(parserRules string, allFiltersMatch bool) (rsrParser *RSRParser, err error) {
if len(parserRules) == 0 {
return
@@ -263,3 +274,14 @@ func (prsr *RSRParser) ParseDataProvider(dP DataProvider) (out string, err error
}
return prsr.ParseValue(outStr)
}
func (prsr *RSRParser) ParseCDR(dP DataProvider) (out string, err error) {
var outStr interface{}
if prsr.attrValue == "" {
if outStr, err = dP.FieldAsInterface(
strings.Split(prsr.attrName, utils.NestingSep)); err != nil {
return
}
}
return prsr.ParseValue(outStr)
}

View File

@@ -51,15 +51,15 @@
{"id": "Partial", "field_id": "Partial", "type": "*composed", "value": "true", "filters":["*string:10:partial"]},
],
"cache_dump_fields": [
{"tag": "OriginID", "type": "*composed", "value": "OriginID"},
{"tag": "OrderID", "type": "*composed", "value": "OrderID"},
{"tag": "RequestType", "type": "*composed", "value": "RequestType"},
{"tag": "Account", "type": "*composed", "value": "Account"},
{"tag": "Destination", "type": "*composed", "value": "Destination"},
{"tag": "SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag": "AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag": "Usage", "type": "*composed", "value": "Usage"},
{"tag": "Cost", "type": "*composed", "value": "Cost"},
{"id": "OriginID", "type": "*composed", "value": "~OriginID"},
{"id": "OrderID", "type": "*composed", "value": "~OrderID"},
{"id": "RequestType", "type": "*composed", "value": "~RequestType"},
{"id": "Account", "type": "*composed", "value": "~Account"},
{"id": "Destination", "type": "*composed", "value": "~Destination"},
{"id": "SetupTime", "type": "*composed", "value": "~SetupTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id": "AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id": "Usage", "type": "*composed", "value": "~Usage"},
{"id": "Cost", "type": "*composed", "value": "~Cost"},
],
},
{

View File

@@ -87,16 +87,16 @@
"export_path": "/tmp",
"filters" :["*string:Source:test2"],
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"Source", "type": "*composed", "value": "Source"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"Source", "type": "*composed", "value": "~Source"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
],
},
},

View File

@@ -32,22 +32,22 @@
"export_path": "http://127.0.0.1:12080/cdr_http",
"cdr_filter": "RunID(*default);OriginID(httpjsonrpc1)",
"content_fields": [ // template of the exported content fields
{"tag": "CGRID", "type": "*composed", "value": "CGRID", "field_id": "CGRID"},
{"tag":"RunID", "type": "*composed", "value": "RunID", "field_id": "RunID"},
{"tag":"TOR", "type": "*composed", "value": "ToR", "field_id": "ToR"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID", "field_id": "OriginID"},
{"tag":"OriginHost", "type": "*composed", "value": "OriginHost", "field_id": "OriginHost"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType", "field_id": "RequestType"},
{"tag":"Direction", "type": "*composed", "value": "Direction", "field_id": "Direction"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant", "field_id": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category", "field_id": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account", "field_id": "Account"},
{"tag":"Subject", "type": "*composed", "value": "Subject", "field_id": "Subject"},
{"tag":"Destination", "type": "*composed", "value": "Destination", "field_id": "Destination"},
{"tag":"SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "SetupTime"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "AnswerTime"},
{"tag":"Usage", "type": "*composed", "value": "Usage", "field_id": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "field_id": "Cost"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID", "field_id": "CGRID"},
{"id":"RunID", "type": "*composed", "value": "~RunID", "field_id": "RunID"},
{"id":"TOR", "type": "*composed", "value": "~ToR", "field_id": "ToR"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID", "field_id": "OriginID"},
{"id":"OriginHost", "type": "*composed", "value": "~OriginHost", "field_id": "OriginHost"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType", "field_id": "RequestType"},
{"id":"Direction", "type": "*composed", "value": "~Direction", "field_id": "Direction"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant", "field_id": "Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category", "field_id": "Category"},
{"id":"Account", "type": "*composed", "value": "~Account", "field_id": "Account"},
{"id":"Subject", "type": "*composed", "value": "~Subject", "field_id": "Subject"},
{"id":"Destination", "type": "*composed", "value": "~Destination", "field_id": "Destination"},
{"id":"SetupTime", "type": "*composed", "value": "~SetupTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "SetupTime"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "AnswerTime"},
{"id":"Usage", "type": "*composed", "value": "~Usage", "field_id": "Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "field_id": "Cost"},
],
},
"amqp_localhost": {
@@ -56,22 +56,22 @@
"attempts": 3,
"cdr_filter": "RunID(*default)",
"content_fields": [ // template of the exported content fields
{"tag": "CGRID", "type": "*composed", "value": "CGRID", "field_id": "CGRID"},
{"tag":"RunID", "type": "*composed", "value": "RunID", "field_id": "RunID"},
{"tag":"TOR", "type": "*composed", "value": "ToR", "field_id": "ToR"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID", "field_id": "OriginID"},
{"tag":"OriginHost", "type": "*composed", "value": "OriginHost", "field_id": "OriginHost"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType", "field_id": "RequestType"},
{"tag":"Direction", "type": "*composed", "value": "Direction", "field_id": "Direction"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant", "field_id": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category", "field_id": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account", "field_id": "Account"},
{"tag":"Subject", "type": "*composed", "value": "Subject", "field_id": "Subject"},
{"tag":"Destination", "type": "*composed", "value": "Destination", "field_id": "Destination"},
{"tag":"SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "SetupTime"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "AnswerTime"},
{"tag":"Usage", "type": "*composed", "value": "Usage", "field_id": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "field_id": "Cost"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID", "field_id": "CGRID"},
{"id":"RunID", "type": "*composed", "value": "~RunID", "field_id": "RunID"},
{"id":"TOR", "type": "*composed", "value": "~ToR", "field_id": "ToR"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID", "field_id": "OriginID"},
{"id":"OriginHost", "type": "*composed", "value": "~OriginHost", "field_id": "OriginHost"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType", "field_id": "RequestType"},
{"id":"Direction", "type": "*composed", "value": "~Direction", "field_id": "Direction"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant", "field_id": "Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category", "field_id": "Category"},
{"id":"Account", "type": "*composed", "value": "~Account", "field_id": "Account"},
{"id":"Subject", "type": "*composed", "value": "~Subject", "field_id": "Subject"},
{"id":"Destination", "type": "*composed", "value": "~Destination", "field_id": "Destination"},
{"id":"SetupTime", "type": "*composed", "value": "~SetupTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "SetupTime"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "AnswerTime"},
{"id":"Usage", "type": "*composed", "value": "~Usage", "field_id": "Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "field_id": "Cost"},
],
},
"http_test_file": {
@@ -79,14 +79,14 @@
"export_path": "http://127.0.0.1:12080/invalid",
"cdr_filter": "OriginID(httpjsonrpc1)",
"content_fields": [
{"tag": "OriginID", "type": "*composed", "value": "OriginID", "field_id": "OriginID"},
{"id": "OriginID", "type": "*composed", "value": "~OriginID", "field_id": "OriginID"},
],
},
"amqp_test_file": {
"export_format": "*amqp_json_map",
"export_path": "amqp://guest:guest@localhost:25672/?queue_id=cgrates_cdrs",
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID", "field_id": "CGRID"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID", "field_id": "CGRID"},
],
},
},

View File

@@ -61,18 +61,18 @@
"cdre": {
"TestTutITExportCDR": {
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
{"id":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
],
},
},

View File

@@ -9,18 +9,18 @@
"cdr_out_dir": "/tmp/cgrates/cdrc_fs/out", // absolute path towards the directory where processed CDRs will be moved
"cdr_source_id": "fs_csv", // free form field, tag identifying the source of the CDRs within CDRS database
"content_fields":[ // import template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value
{"tag": "tor", "cdr_field_id": "tor", "type": "cdrfield", "value": "^*voice", "mandatory": true},
{"tag": "accid", "cdr_field_id": "accid", "type": "cdrfield", "value": "10", "mandatory": true},
{"tag": "reqtype", "cdr_field_id": "reqtype", "type": "cdrfield", "value": "^rated", "mandatory": true},
{"tag": "direction", "cdr_field_id": "direction", "type": "cdrfield", "value": "^*out", "mandatory": true},
{"tag": "tenant", "cdr_field_id": "tenant", "type": "cdrfield", "value": "^cgrates.org", "mandatory": true},
{"tag": "category", "cdr_field_id": "category", "type": "cdrfield", "value": "^call", "mandatory": true},
{"tag": "account", "cdr_field_id": "account", "type": "cdrfield", "value": "12", "mandatory": true},
{"tag": "subject", "cdr_field_id": "subject", "type": "cdrfield", "value": "12", "mandatory": true},
{"tag": "destination", "cdr_field_id": "destination", "type": "cdrfield", "value": "2", "mandatory": true},
{"tag": "setup_time", "cdr_field_id": "setup_time", "type": "cdrfield", "value": "4", "mandatory": true},
{"tag": "answer_time", "cdr_field_id": "answer_time", "type": "cdrfield", "value": "5", "mandatory": true},
{"tag": "usage", "cdr_field_id": "usage", "type": "cdrfield", "value": "~8:s/^(\\d+)$/${1}s/", "mandatory": true},
{"id": "tor", "cdr_field_id": "tor", "type": "cdrfield", "value": "^*voice", "mandatory": true},
{"id": "accid", "cdr_field_id": "accid", "type": "cdrfield", "value": "10", "mandatory": true},
{"id": "reqtype", "cdr_field_id": "reqtype", "type": "cdrfield", "value": "^rated", "mandatory": true},
{"id": "direction", "cdr_field_id": "direction", "type": "cdrfield", "value": "^*out", "mandatory": true},
{"id": "tenant", "cdr_field_id": "tenant", "type": "cdrfield", "value": "^cgrates.org", "mandatory": true},
{"id": "category", "cdr_field_id": "category", "type": "cdrfield", "value": "^call", "mandatory": true},
{"id": "account", "cdr_field_id": "account", "type": "cdrfield", "value": "12", "mandatory": true},
{"id": "subject", "cdr_field_id": "subject", "type": "cdrfield", "value": "12", "mandatory": true},
{"id": "destination", "cdr_field_id": "destination", "type": "cdrfield", "value": "2", "mandatory": true},
{"id": "setup_time", "cdr_field_id": "setup_time", "type": "cdrfield", "value": "4", "mandatory": true},
{"id": "answer_time", "cdr_field_id": "answer_time", "type": "cdrfield", "value": "5", "mandatory": true},
{"id": "usage", "cdr_field_id": "usage", "type": "cdrfield", "value": "~8:s/^(\\d+)$/${1}s/", "mandatory": true},
],
},
],

View File

@@ -89,18 +89,18 @@
"cdre": {
"TestTutITExportCDR": {
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
{"id":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
],
},
},

View File

@@ -105,18 +105,18 @@
"cdre": {
"TestTutITExportCDR": {
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
{"id":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
],
},
},

View File

@@ -83,18 +83,18 @@
"cdre": {
"TestTutITExportCDR": {
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
{"id":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
],
},
},

View File

@@ -62,7 +62,7 @@
"rals": {
"enabled": true,
"thresholds_conns": [
{"address": "*internal"}
{"address": "127.0.0.1:2012", "transport":"*json"},
],
},
@@ -81,20 +81,20 @@
"cdre": {
"TestTutITExportCDR": {
"TestTutITExportCDR": {
"content_fields": [
{"tag": "CGRID", "type": "*composed", "value": "CGRID"},
{"tag": "RunID", "type": "*composed", "value": "RunID"},
{"tag":"OriginID", "type": "*composed", "value": "OriginID"},
{"tag":"RequestType", "type": "*composed", "value": "RequestType"},
{"tag":"Tenant", "type": "*composed", "value": "Tenant"},
{"tag":"Category", "type": "*composed", "value": "Category"},
{"tag":"Account", "type": "*composed", "value": "Account"},
{"tag":"Destination", "type": "*composed", "value": "Destination"},
{"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"tag":"Usage", "type": "*composed", "value": "Usage"},
{"tag":"Cost", "type": "*composed", "value": "Cost", "rounding_decimals": 4},
{"tag":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
{"id": "CGRID", "type": "*composed", "value": "~CGRID"},
{"id": "RunID", "type": "*composed", "value": "~RunID"},
{"id":"OriginID", "type": "*composed", "value": "~OriginID"},
{"id":"RequestType", "type": "*composed", "value": "~RequestType"},
{"id":"Tenant", "type": "*composed", "value": "~Tenant"},
{"id":"Category", "type": "*composed", "value": "~Category"},
{"id":"Account", "type": "*composed", "value": "~Account"},
{"id":"Destination", "type": "*composed", "value": "~Destination"},
{"id":"AnswerTime", "type": "*composed", "value": "~AnswerTime", "layout": "2006-01-02T15:04:05Z07:00"},
{"id":"Usage", "type": "*composed", "value": "~Usage"},
{"id":"Cost", "type": "*composed", "value": "~Cost", "rounding_decimals": 4},
{"id":"MatchedDestinationID", "type": "*composed", "value": "~CostDetails:s/\"MatchedDestId\":.*_(\\w{4})/${1}/:s/\"MatchedDestId\":\"INTERNAL\"/ON010/"},
],
},
},

View File

@@ -154,7 +154,25 @@ func (cdr *CDR) FormatCost(shiftDecimals, roundDecimals int) string {
}
// Used to retrieve fields as string, primary fields are const labeled
func (cdr *CDR) FieldAsString(rsrFld *utils.RSRField) (parsed string, err error) {
func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err error) {
parsed, err = rsrPrs.ParseCDR(config.NewNavigableMap(cdr.AsMapStringIface()))
if err != nil {
return
}
return
}
// concatenates values of multiple fields defined in template, used eg in CDR templates
func (cdr *CDR) FieldsAsString(rsrFlds config.RSRParsers) string {
outVal, err := rsrFlds.ParseCDR(config.NewNavigableMap(cdr.AsMapStringIface()))
if err != nil {
return ""
}
return outVal
}
// Used to retrieve fields as string, primary fields are const labeled
func (cdr *CDR) FieldAsStringWithRSRField(rsrFld *utils.RSRField) (parsed string, err error) {
if rsrFld.IsStatic() { // Static values do not care about headers
parsed, err = rsrFld.Parse("")
return
@@ -167,9 +185,9 @@ func (cdr *CDR) FieldAsString(rsrFld *utils.RSRField) (parsed string, err error)
}
// concatenates values of multiple fields defined in template, used eg in CDR templates
func (cdr *CDR) FieldsAsString(rsrFlds utils.RSRFields) (fldVal string) {
func (cdr *CDR) FieldsAsStringWithRSRFields(rsrFlds utils.RSRFields) (fldVal string) {
for _, rsrFld := range rsrFlds {
if fldStr, err := cdr.FieldAsString(rsrFld); err != nil {
if fldStr, err := cdr.FieldAsStringWithRSRField(rsrFld); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> error: %s processing field with template: %+v",
utils.CDR, err.Error(), rsrFld))
@@ -348,55 +366,55 @@ func (cdr *CDR) ForkCdr(runId string, RequestTypeFld, tenantFld,
frkStorCdr.OriginID = cdr.OriginID
frkStorCdr.OriginHost = cdr.OriginHost
frkStorCdr.Source = cdr.Source
frkStorCdr.RequestType, _ = cdr.FieldAsString(RequestTypeFld)
frkStorCdr.RequestType, _ = cdr.FieldAsStringWithRSRField(RequestTypeFld)
if primaryMandatory && len(frkStorCdr.RequestType) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.RequestType, RequestTypeFld.Id)
}
frkStorCdr.Tenant, _ = cdr.FieldAsString(tenantFld)
frkStorCdr.Tenant, _ = cdr.FieldAsStringWithRSRField(tenantFld)
if primaryMandatory && len(frkStorCdr.Tenant) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.Tenant, tenantFld.Id)
}
frkStorCdr.Category, _ = cdr.FieldAsString(categFld)
frkStorCdr.Category, _ = cdr.FieldAsStringWithRSRField(categFld)
if primaryMandatory && len(frkStorCdr.Category) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.Category, categFld.Id)
}
frkStorCdr.Account, _ = cdr.FieldAsString(accountFld)
frkStorCdr.Account, _ = cdr.FieldAsStringWithRSRField(accountFld)
if primaryMandatory && len(frkStorCdr.Account) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.Account, accountFld.Id)
}
frkStorCdr.Subject, _ = cdr.FieldAsString(subjectFld)
frkStorCdr.Subject, _ = cdr.FieldAsStringWithRSRField(subjectFld)
if primaryMandatory && len(frkStorCdr.Subject) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.Subject, subjectFld.Id)
}
frkStorCdr.Destination, _ = cdr.FieldAsString(destFld)
frkStorCdr.Destination, _ = cdr.FieldAsStringWithRSRField(destFld)
if primaryMandatory && len(frkStorCdr.Destination) == 0 && frkStorCdr.ToR == utils.VOICE {
return nil, utils.NewErrMandatoryIeMissing(utils.Destination, destFld.Id)
}
sTimeStr, _ := cdr.FieldAsString(setupTimeFld)
sTimeStr, _ := cdr.FieldAsStringWithRSRField(setupTimeFld)
if primaryMandatory && len(sTimeStr) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.SetupTime, setupTimeFld.Id)
} else if frkStorCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr, timezone); err != nil {
return nil, err
}
aTimeStr, _ := cdr.FieldAsString(answerTimeFld)
aTimeStr, _ := cdr.FieldAsStringWithRSRField(answerTimeFld)
if primaryMandatory && len(aTimeStr) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.AnswerTime, answerTimeFld.Id)
} else if frkStorCdr.AnswerTime, err = utils.ParseTimeDetectLayout(aTimeStr, timezone); err != nil {
return nil, err
}
durStr, _ := cdr.FieldAsString(durationFld)
durStr, _ := cdr.FieldAsStringWithRSRField(durationFld)
if primaryMandatory && len(durStr) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.Usage, durationFld.Id)
} else if frkStorCdr.Usage, err = utils.ParseDurationWithNanosecs(durStr); err != nil {
return nil, err
}
ratedStr, _ := cdr.FieldAsString(ratedFld)
ratedStr, _ := cdr.FieldAsStringWithRSRField(ratedFld)
if primaryMandatory && len(ratedStr) == 0 {
return nil, utils.NewErrMandatoryIeMissing(utils.PreRated, ratedFld.Id)
} else if frkStorCdr.PreRated, err = strconv.ParseBool(ratedStr); err != nil {
return nil, err
}
costStr, _ := cdr.FieldAsString(costFld)
costStr, _ := cdr.FieldAsStringWithRSRField(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 {
@@ -404,7 +422,7 @@ func (cdr *CDR) ForkCdr(runId string, RequestTypeFld, tenantFld,
}
frkStorCdr.ExtraFields = make(map[string]string, len(extraFlds))
for _, fld := range extraFlds {
frkStorCdr.ExtraFields[fld.Id], _ = cdr.FieldAsString(fld)
frkStorCdr.ExtraFields[fld.Id], _ = cdr.FieldAsStringWithRSRField(fld)
}
return frkStorCdr, nil
}
@@ -448,43 +466,47 @@ func (cdr *CDR) String() string {
}
// combimedCdrFieldVal groups together CDRs with same CGRID and combines their values matching filter field ID
func (cdr *CDR) combimedCdrFieldVal(cfgCdrFld *config.CfgCdrField, groupCDRs []*CDR) (string, error) {
var combinedVal string // Will result as combination of the field values, filters must match
for _, filterRule := range cfgCdrFld.FieldFilter {
pairingVal, err := cdr.FieldAsString(&utils.RSRField{Id: filterRule.Id})
if err != nil { // Filter not passing
continue
}
for _, grpCDR := range groupCDRs {
if cdr.CGRID != grpCDR.CGRID {
continue // We only care about cdrs with same primary cdr behind
}
if valStr, _ := grpCDR.FieldAsString(&utils.RSRField{Id: filterRule.Id}); valStr != pairingVal { // First CDR with field equal with ours
func (cdr *CDR) combimedCdrFieldVal(cfgCdrFld *config.FCTemplate, groupCDRs []*CDR, filterS *FilterS) (string, error) {
/*
var combinedVal string // Will result as combination of the field values, filters must match
for _, filterRule := range cfgCdrFld.Value {
pairingVal, err := cdr.FieldAsString(filterRule)
if err != nil { // Filter not passing
continue
}
for _, rsrRule := range cfgCdrFld.Value {
if parsed, err := grpCDR.FieldAsString(rsrRule); err != nil {
return "", err
} else {
combinedVal += parsed
for _, grpCDR := range groupCDRs {
if cdr.CGRID != grpCDR.CGRID {
continue // We only care about cdrs with same primary cdr behind
}
if valStr, _ := grpCDR.FieldAsString(filterRule); valStr != pairingVal { // First CDR with field equal with ours
continue
}
for _, rsrRule := range cfgCdrFld.Value {
if parsed, err := grpCDR.FieldAsString(rsrRule); err != nil {
return "", err
} else {
combinedVal += parsed
}
}
}
}
}
return combinedVal, nil
return combinedVal, nil
*/
return "", nil
}
// Extracts the value specified by cfgHdr out of cdr, used for export values
func (cdr *CDR) exportFieldValue(cfgCdrFld *config.CfgCdrField) (retVal string, err error) {
for _, cdfFltr := range cfgCdrFld.FieldFilter {
if _, err := cdr.FieldAsString(cdfFltr); err != nil {
return "", err
}
func (cdr *CDR) exportFieldValue(cfgCdrFld *config.FCTemplate, filterS *FilterS) (retVal string, err error) {
if pass, err := filterS.Pass(cdr.Tenant,
cfgCdrFld.Filters, config.NewNavigableMap(cdr.AsMapStringIface())); err != nil {
return "", err
} else if !pass {
return "", utils.ErrFilterNotPassingNoCaps
}
for _, rsrFld := range cfgCdrFld.Value {
var cdrVal string
switch rsrFld.Id {
switch cfgCdrFld.ID {
case utils.COST:
cdrVal = cdr.FormatCost(cfgCdrFld.CostShiftDigits,
cfgCdrFld.RoundingDecimals)
@@ -512,21 +534,20 @@ func (cdr *CDR) exportFieldValue(cfgCdrFld *config.CfgCdrField) (retVal string,
return
}
func (cdr *CDR) formatField(cfgFld *config.CfgCdrField, httpSkipTlsCheck bool,
groupedCDRs []*CDR) (fmtOut string, err error) {
func (cdr *CDR) formatField(cfgFld *config.FCTemplate, httpSkipTlsCheck bool,
groupedCDRs []*CDR, filterS *FilterS) (outVal string, err error) {
layout := cfgFld.Layout
if layout == "" {
layout = time.RFC3339
}
var outVal string
switch cfgFld.Type {
case utils.META_FILLER:
outVal = cfgFld.Value.Id()
outVal, err = cfgFld.Value.ParseValue(utils.EmptyString)
cfgFld.Padding = "right"
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
outVal, err = cfgFld.Value.ParseValue(utils.EmptyString)
case utils.MetaDateTime: // Convert the requested field value into datetime with layout
rawVal, err := cdr.exportFieldValue(cfgFld)
rawVal, err := cdr.exportFieldValue(cfgFld, filterS)
if err != nil {
return "", err
}
@@ -537,23 +558,26 @@ func (cdr *CDR) formatField(cfgFld *config.CfgCdrField, httpSkipTlsCheck bool,
}
case utils.META_HTTP_POST:
var outValByte []byte
httpAddr := cfgFld.Value.Id()
httpAddr, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return "", err
}
jsn, err := json.Marshal(cdr)
if err != nil {
return "", err
}
if len(httpAddr) == 0 {
err = fmt.Errorf("Empty http address for field %s type %s", cfgFld.Tag, cfgFld.Type)
err = fmt.Errorf("Empty http address for field %s type %s", cfgFld.ID, cfgFld.Type)
} else if outValByte, err = HttpJsonPost(httpAddr, httpSkipTlsCheck, jsn); err == nil {
outVal = string(outValByte)
if len(outVal) == 0 && cfgFld.Mandatory {
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Tag)
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.ID)
}
}
case utils.META_COMBIMED:
outVal, err = cdr.combimedCdrFieldVal(cfgFld, groupedCDRs)
outVal, err = cdr.combimedCdrFieldVal(cfgFld, groupedCDRs, filterS)
case utils.META_COMPOSED:
outVal, err = cdr.exportFieldValue(cfgFld)
outVal, err = cdr.exportFieldValue(cfgFld, filterS)
case utils.MetaMaskedDestination:
if len(cfgFld.MaskDestID) != 0 && CachedDestHasPrefix(cfgFld.MaskDestID, cdr.Destination) {
outVal = "1"
@@ -565,22 +589,21 @@ func (cdr *CDR) formatField(cfgFld *config.CfgCdrField, httpSkipTlsCheck bool,
(err != utils.ErrNotFound || cfgFld.Mandatory) {
return "", err
}
return utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory)
return utils.FmtFieldWidth(cfgFld.ID, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory)
}
// Used in place where we need to export the CDR based on an export template
// ExportRecord is a []string to keep it compatible with encoding/csv Writer
func (cdr *CDR) AsExportRecord(exportFields []*config.CfgCdrField,
httpSkipTlsCheck bool, groupedCDRs []*CDR, roundingDecs int) (expRecord []string, err error) {
func (cdr *CDR) AsExportRecord(exportFields []*config.FCTemplate,
httpSkipTlsCheck bool, groupedCDRs []*CDR, roundingDecs int, filterS *FilterS) (expRecord []string, err error) {
for _, cfgFld := range exportFields {
if roundingDecs != 0 {
clnFld := new(config.CfgCdrField) // Clone so we can modify the rounding decimals without affecting the template
clnFld := new(config.FCTemplate) // Clone so we can modify the rounding decimals without affecting the template
*clnFld = *cfgFld
clnFld.RoundingDecimals = roundingDecs
cfgFld = clnFld
}
if fmtOut, err := cdr.formatField(cfgFld, httpSkipTlsCheck, groupedCDRs); err != nil {
if fmtOut, err := cdr.formatField(cfgFld, httpSkipTlsCheck, groupedCDRs, filterS); err != nil {
if err == utils.ErrFilterNotPassingNoCaps {
continue // not exporting this field value
}
@@ -596,16 +619,17 @@ func (cdr *CDR) AsExportRecord(exportFields []*config.CfgCdrField,
// AsExportMap converts the CDR into a map[string]string based on export template
// Used in real-time replication as well as remote exports
func (cdr *CDR) AsExportMap(exportFields []*config.CfgCdrField, httpSkipTlsCheck bool, groupedCDRs []*CDR, roundingDecs int) (expMap map[string]string, err error) {
func (cdr *CDR) AsExportMap(exportFields []*config.FCTemplate, httpSkipTlsCheck bool,
groupedCDRs []*CDR, roundingDecs int, filterS *FilterS) (expMap map[string]string, err error) {
expMap = make(map[string]string)
for _, cfgFld := range exportFields {
if roundingDecs != 0 {
clnFld := new(config.CfgCdrField) // Clone so we can modify the rounding decimals without affecting the template
clnFld := new(config.FCTemplate) // Clone so we can modify the rounding decimals without affecting the template
*clnFld = *cfgFld
clnFld.RoundingDecimals = roundingDecs
cfgFld = clnFld
}
if fmtOut, err := cdr.formatField(cfgFld, httpSkipTlsCheck, groupedCDRs); err != nil {
if fmtOut, err := cdr.formatField(cfgFld, httpSkipTlsCheck, groupedCDRs, filterS); err != nil {
if err == utils.ErrFilterNotPassingNoCaps {
continue
}

View File

@@ -80,134 +80,134 @@ func TestFieldAsString(t *testing.T) {
Usage: time.Duration(10) * time.Second, Cost: 1.01,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
fldName := utils.CGRID
prsr := config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.CGRID, true)
eFldVal := cdr.CGRID
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.OrderID
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.OrderID, true)
eFldVal = strconv.FormatInt(cdr.OrderID, 10)
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.ToR
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.ToR, true)
eFldVal = cdr.ToR
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.OriginID
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.OriginID, true)
eFldVal = cdr.OriginID
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.OriginHost
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.OriginHost, true)
eFldVal = cdr.OriginHost
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Source
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Source, true)
eFldVal = cdr.Source
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.RequestType
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.RequestType, true)
eFldVal = cdr.RequestType
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Category
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Category, true)
eFldVal = cdr.Category
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Account
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Account, true)
eFldVal = cdr.Account
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Subject
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Subject, true)
eFldVal = cdr.Subject
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Destination
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Destination, true)
eFldVal = cdr.Destination
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.SetupTime
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.SetupTime, true)
eFldVal = cdr.SetupTime.Format(time.RFC3339)
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("expected: <%s>, received: <%s>", eFldVal, fldVal)
}
fldName = utils.AnswerTime
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.AnswerTime, true)
eFldVal = cdr.AnswerTime.Format(time.RFC3339)
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("expected: <%s>, received: <%s>", eFldVal, fldVal)
}
fldName = utils.Usage
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Usage, true)
eFldVal = "10s"
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.RunID
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.RunID, true)
eFldVal = cdr.RunID
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = utils.Cost
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+utils.Cost, true)
eFldVal = strconv.FormatFloat(cdr.Cost, 'f', -1, 64)
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = "field_extr1"
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+"field_extr1", true)
eFldVal = cdr.ExtraFields["field_extr1"]
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = "fieldextr2"
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+"fieldextr2", true)
eFldVal = cdr.ExtraFields["fieldextr2"]
if fldVal, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != nil {
if fldVal, err := cdr.FieldAsString(prsr); err != nil {
t.Error(err)
} else if fldVal != eFldVal {
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", fldName, eFldVal, fldVal)
t.Errorf("field: <%s>, expected: <%s>, received: <%s>", prsr, eFldVal, fldVal)
}
fldName = "dummy_field"
if _, err := cdr.FieldAsString(&utils.RSRField{Id: fldName}); err != utils.ErrNotFound {
prsr = config.NewRSRParserMustCompile(utils.DynamicDataPrefix+"dummy_field", true)
if _, err := cdr.FieldAsString(prsr); err != utils.ErrNotFound {
t.Error(err)
}
}
@@ -224,7 +224,7 @@ func TestFieldsAsString(t *testing.T) {
}
eVal := "call_from_1001"
if val := cdr.FieldsAsString(
utils.ParseRSRFieldsMustCompile("Category;^_from_;Account", utils.INFIELD_SEP)); val != eVal {
config.NewRSRParsersMustCompile("~Category;_from_;~Account", true)); val != eVal {
t.Errorf("Expecting : %s, received: %s", eVal, val)
}
}
@@ -334,7 +334,7 @@ func TestCDRAsHttpForm(t *testing.T) {
t.Errorf("Expected: %s, received: %s", "valextr2", cdrForm.Get("fieldextr2"))
}
}
*/
func TestCDRForkCdr(t *testing.T) {
storCdr := CDR{CGRID: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()),
@@ -452,6 +452,7 @@ func TestCDRForkCdrFromMetaDefaults(t *testing.T) {
t.Errorf("Expected: %v, received: %v", expctCdr, cdrOut)
}
}
*/
func TestCDRAsExternalCDR(t *testing.T) {
storCdr := CDR{CGRID: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
@@ -606,59 +607,76 @@ func TestCDRAsExportRecord(t *testing.T) {
RunID: utils.DEFAULT_RUNID, Cost: 1.01,
ExtraFields: map[string]string{"stop_time": "2014-06-11 19:19:00 +0000 UTC", "fieldextr2": "valextr2"}}
val, _ := utils.ParseRSRFields(utils.Destination, utils.INFIELD_SEP)
cfgCdrFld := &config.CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: val, Timezone: "UTC"}
if expRecord, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
prsr := config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.Destination, true)
cfgCdrFld := &config.FCTemplate{ID: "destination", Type: utils.META_COMPOSED,
FieldId: utils.Destination, Value: prsr, Timezone: "UTC"}
if expRecord, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, nil); err != nil {
t.Error(err)
} else if expRecord[0] != cdr.Destination {
t.Errorf("Expecting:\n%s\nReceived:\n%s", cdr.Destination, expRecord[0])
t.Errorf("Expecting:\n%s\nReceived:\n%s", cdr.Destination, expRecord)
}
if err := dm.DataDB().SetReverseDestination(&Destination{Id: "MASKED_DESTINATIONS", Prefixes: []string{"+4986517174963"}},
utils.NonTransactional); err != nil {
t.Error(err)
}
cfgCdrFld = &config.CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: val, MaskDestID: "MASKED_DESTINATIONS", MaskLen: 3}
cfgCdrFld = &config.FCTemplate{ID: "Destination", Type: utils.META_COMPOSED,
FieldId: utils.Destination, Value: prsr, MaskDestID: "MASKED_DESTINATIONS", MaskLen: 3}
eDst := "+4986517174***"
if expRecord, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
if expRecord, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, nil); err != nil {
t.Error(err)
} else if expRecord[0] != eDst {
t.Errorf("Expecting:\n%s\nReceived:\n%s", eDst, expRecord[0])
}
cfgCdrFld = &config.CfgCdrField{Tag: "MaskedDest", Type: utils.MetaMaskedDestination, Value: val, MaskDestID: "MASKED_DESTINATIONS"}
if expRecord, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
cfgCdrFld = &config.FCTemplate{ID: "MaskedDest", Type: utils.MetaMaskedDestination,
Value: prsr, MaskDestID: "MASKED_DESTINATIONS"}
if expRecord, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, nil); err != nil {
t.Error(err)
} else if expRecord[0] != "1" {
t.Errorf("Expecting:\n%s\nReceived:\n%s", "1", expRecord[0])
}
fltr, _ := utils.ParseRSRFields("Tenant(itsyscom.com)", utils.INFIELD_SEP)
cfgCdrFld = &config.CfgCdrField{Tag: "destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: val, FieldFilter: fltr, Timezone: "UTC"}
if rcrd, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
data, _ := NewMapStorage()
dmForCDR := NewDataManager(data)
defaultCfg, err := config.NewDefaultCGRConfig()
if err != nil {
t.Errorf("Error: %+v", err)
}
cfgCdrFld = &config.FCTemplate{ID: "destination", Type: utils.META_COMPOSED,
FieldId: utils.Destination, Value: prsr, Filters: []string{"*string:Tenant:itsyscom.com"}, Timezone: "UTC"}
if rcrd, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, &FilterS{dm: dmForCDR, cfg: defaultCfg}); err != nil {
t.Error(err)
} else if len(rcrd) != 0 {
t.Error("failed using filter")
}
// Test MetaDateTime
val, _ = utils.ParseRSRFields("stop_time", utils.INFIELD_SEP)
prsr = config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"stop_time", true)
layout := "2006-01-02 15:04:05"
cfgCdrFld = &config.CfgCdrField{Tag: "stop_time", Type: utils.MetaDateTime, FieldId: "stop_time", Value: val, Layout: layout, Timezone: "UTC"}
if expRecord, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
cfgCdrFld = &config.FCTemplate{ID: "stop_time", Type: utils.MetaDateTime,
FieldId: "stop_time", Value: prsr, Layout: layout, Timezone: "UTC"}
if expRecord, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, &FilterS{dm: dmForCDR, cfg: defaultCfg}); err != nil {
t.Error(err)
} else if expRecord[0] != "2014-06-11 19:19:00" {
t.Error("Expecting: 2014-06-11 19:19:00, got: ", expRecord[0])
}
// Test filter
fltr, _ = utils.ParseRSRFields("Tenant(itsyscom.com)", utils.INFIELD_SEP)
cfgCdrFld = &config.CfgCdrField{Tag: "stop_time", Type: utils.MetaDateTime,
FieldId: "stop_time", Value: val, FieldFilter: fltr, Layout: layout, Timezone: "UTC"}
if rcrd, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err != nil {
cfgCdrFld = &config.FCTemplate{ID: "stop_time", Type: utils.MetaDateTime,
FieldId: "stop_time", Value: prsr, Filters: []string{"*string:Tenant:itsyscom.com"},
Layout: layout, Timezone: "UTC"}
if rcrd, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, &FilterS{dm: dmForCDR, cfg: defaultCfg}); err != nil {
t.Error(err)
} else if len(rcrd) != 0 {
t.Error("failed using filter")
}
val, _ = utils.ParseRSRFields("fieldextr2", utils.INFIELD_SEP)
cfgCdrFld = &config.CfgCdrField{Tag: "stop_time", Type: utils.MetaDateTime, FieldId: "stop_time", Value: val, Layout: layout, Timezone: "UTC"}
prsr = config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"fieldextr2", true)
cfgCdrFld = &config.FCTemplate{ID: "stop_time", Type: utils.MetaDateTime,
FieldId: "stop_time", Value: prsr, Layout: layout, Timezone: "UTC"}
// Test time parse error
if _, err := cdr.AsExportRecord([]*config.CfgCdrField{cfgCdrFld}, false, nil, 0); err == nil {
if _, err := cdr.AsExportRecord([]*config.FCTemplate{cfgCdrFld}, false, nil, 0, nil); err == nil {
t.Error("Should give error here, got none.")
}
}
@@ -679,12 +697,15 @@ func TestCDRAsExportMap(t *testing.T) {
utils.Destination: "004986517174963",
"FieldExtra1": "val_extr1",
}
expFlds := []*config.CfgCdrField{
&config.CfgCdrField{FieldId: utils.CGRID, Type: utils.META_COMPOSED, Value: utils.ParseRSRFieldsMustCompile(utils.CGRID, utils.INFIELD_SEP)},
&config.CfgCdrField{FieldId: utils.Destination, Type: utils.META_COMPOSED, Value: utils.ParseRSRFieldsMustCompile("~Destination:s/^\\+(\\d+)$/00${1}/", utils.INFIELD_SEP)},
&config.CfgCdrField{FieldId: "FieldExtra1", Type: utils.META_COMPOSED, Value: utils.ParseRSRFieldsMustCompile("field_extr1", utils.INFIELD_SEP)},
expFlds := []*config.FCTemplate{
&config.FCTemplate{FieldId: utils.CGRID, Type: utils.META_COMPOSED,
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.CGRID, true)},
&config.FCTemplate{FieldId: utils.Destination, Type: utils.META_COMPOSED,
Value: config.NewRSRParsersMustCompile("~Destination:s/^\\+(\\d+)$/00${1}/", true)},
&config.FCTemplate{FieldId: "FieldExtra1", Type: utils.META_COMPOSED,
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"field_extr1", true)},
}
if cdrMp, err := cdr.AsExportMap(expFlds, false, nil, 0); err != nil {
if cdrMp, err := cdr.AsExportMap(expFlds, false, nil, 0, nil); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCDRMp, cdrMp) {
t.Errorf("Expecting: %+v, received: %+v", eCDRMp, cdrMp)

View File

@@ -124,19 +124,19 @@ func (cdre *CDRExporter) metaHandler(tag, arg string) (string, error) {
return strconv.Itoa(cdre.numberOfRecords), nil
case META_DURCDRS:
cdr := &CDR{ToR: utils.VOICE, Usage: cdre.totalDuration}
return cdr.FieldAsString(&utils.RSRField{Id: utils.Usage})
return cdr.FieldAsString(&config.RSRParser{Rules: "~" + utils.Usage, AllFiltersMatch: true})
case META_SMSUSAGE:
cdr := &CDR{ToR: utils.SMS, Usage: cdre.totalDuration}
return cdr.FieldAsString(&utils.RSRField{Id: utils.Usage})
return cdr.FieldAsString(&config.RSRParser{Rules: "~" + utils.Usage, AllFiltersMatch: true})
case META_MMSUSAGE:
cdr := &CDR{ToR: utils.MMS, Usage: cdre.totalDuration}
return cdr.FieldAsString(&utils.RSRField{Id: utils.Usage})
return cdr.FieldAsString(&config.RSRParser{Rules: "~" + utils.Usage, AllFiltersMatch: true})
case META_GENERICUSAGE:
cdr := &CDR{ToR: utils.GENERIC, Usage: cdre.totalDuration}
return cdr.FieldAsString(&utils.RSRField{Id: utils.Usage})
return cdr.FieldAsString(&config.RSRParser{Rules: "~" + utils.Usage, AllFiltersMatch: true})
case META_DATAUSAGE:
cdr := &CDR{ToR: utils.DATA, Usage: cdre.totalDuration}
return cdr.FieldAsString(&utils.RSRField{Id: utils.Usage})
return cdr.FieldAsString(&config.RSRParser{Rules: "~" + utils.Usage, AllFiltersMatch: true})
case META_COSTCDRS:
return strconv.FormatFloat(utils.Round(cdre.totalCost,
cdre.roundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
@@ -148,25 +148,40 @@ func (cdre *CDRExporter) metaHandler(tag, arg string) (string, error) {
// Compose and cache the header
func (cdre *CDRExporter) composeHeader() (err error) {
for _, cfgFld := range cdre.exportTemplate.HeaderFields {
if len(cfgFld.Filters) != 0 {
//check filter if pass
}
var outVal string
switch cfgFld.Type {
case utils.META_FILLER:
outVal = cfgFld.Value.Id()
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal = out
cfgFld.Padding = "right"
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal = out
case utils.META_HANDLER:
outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout)
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal, err = cdre.metaHandler(out, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error()))
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.ID, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error()))
if fmtOut, err = utils.FmtFieldWidth(cfgFld.ID, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.ID, err.Error()))
return err
}
cdre.Lock()
@@ -179,25 +194,40 @@ func (cdre *CDRExporter) composeHeader() (err error) {
// Compose and cache the trailer
func (cdre *CDRExporter) composeTrailer() (err error) {
for _, cfgFld := range cdre.exportTemplate.TrailerFields {
if len(cfgFld.Filters) != 0 {
//check filter if pass
}
var outVal string
switch cfgFld.Type {
case utils.META_FILLER:
outVal = cfgFld.Value.Id()
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal = out
cfgFld.Padding = "right"
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal = out
case utils.META_HANDLER:
outVal, err = cdre.metaHandler(cfgFld.Value.Id(), cfgFld.Layout)
out, err := cfgFld.Value.ParseValue(utils.EmptyString)
if err != nil {
return err
}
outVal, err = cdre.metaHandler(out, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error()))
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.ID, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error()))
if fmtOut, err = utils.FmtFieldWidth(cfgFld.ID, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
utils.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.ID, err.Error()))
return err
}
cdre.Lock()
@@ -217,7 +247,7 @@ func (cdre *CDRExporter) postCdr(cdr *CDR) (err error) {
}
body = jsn
case utils.MetaHTTPjsonMap, utils.MetaAMQPjsonMap:
expMp, err := cdr.AsExportMap(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, nil, cdre.roundingDecimals)
expMp, err := cdr.AsExportMap(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, nil, cdre.roundingDecimals, cdre.filterS)
if err != nil {
return err
}
@@ -227,7 +257,7 @@ func (cdre *CDRExporter) postCdr(cdr *CDR) (err error) {
}
body = jsn
case utils.META_HTTP_POST:
expMp, err := cdr.AsExportMap(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, nil, cdre.roundingDecimals)
expMp, err := cdr.AsExportMap(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, nil, cdre.roundingDecimals, cdre.filterS)
if err != nil {
return err
}
@@ -285,7 +315,7 @@ func (cdre *CDRExporter) processCDR(cdr *CDR) (err error) {
switch cdre.exportFormat {
case utils.MetaFileFWV, utils.MetaFileCSV:
var cdrRow []string
cdrRow, err = cdr.AsExportRecord(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, cdre.cdrs, cdre.roundingDecimals)
cdrRow, err = cdr.AsExportRecord(cdre.exportTemplate.ContentFields, cdre.httpSkipTlsCheck, cdre.cdrs, cdre.roundingDecimals, cdre.filterS)
if len(cdrRow) == 0 && err == nil { // No CDR data, most likely no configuration fields defined
return
} else {
@@ -343,18 +373,7 @@ func (cdre *CDRExporter) processCDRs() (err error) {
if cdr == nil || len(cdr.CGRID) == 0 { // CDR needs to exist and it's CGRID needs to be populated
continue
}
if len(cdre.exportTemplate.Filters) == 0 {
passesFilters := true
for _, cdrFltr := range cdre.exportTemplate.CDRFilter {
if _, err := cdr.FieldAsString(cdrFltr); err != nil {
passesFilters = false
break
}
}
if !passesFilters { // Not passes filters, ignore this CDR
continue
}
} else {
if len(cdre.exportTemplate.Filters) != 0 {
if pass, err := cdre.filterS.Pass(cdre.exportTemplate.Tenant,
cdre.exportTemplate.Filters, config.NewNavigableMap(cdr.AsMapStringIface())); err != nil || !pass {
continue // Not passes filters, ignore this CDR

View File

@@ -61,7 +61,7 @@ func TestCsvCdrWriter(t *testing.T) {
expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6,*default,*voice,dsafdsaf,*rated,cgrates.org,call,1001,1001,1002,2013-11-07T08:42:25Z,2013-11-07T08:42:26Z,10s,1.01000`
result := strings.TrimSpace(writer.String())
if result != expected {
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
t.Errorf("Expected: \n%s \n received: \n%s.", expected, result)
}
if cdre.TotalCost() != 1.01 {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
@@ -111,35 +111,35 @@ func TestExportVoiceWithConvert(t *testing.T) {
writer := &bytes.Buffer{}
cfg, _ := config.NewDefaultCGRConfig()
cdreCfg := cfg.CdreProfiles["*default"]
cdreCfg.ContentFields = []*config.CfgCdrField{
&config.CfgCdrField{Tag: "ToR", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("ToR", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("OriginID", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "RequestType", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("RequestType", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Tenant", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Tenant", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Category", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Category", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Account", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Account", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Destination", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Destination", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "AnswerTime", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("AnswerTime", utils.INFIELD_SEP),
cdreCfg.ContentFields = []*config.FCTemplate{
&config.FCTemplate{ID: "ToR", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true)},
&config.FCTemplate{ID: "OriginID", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true)},
&config.FCTemplate{ID: "RequestType", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true)},
&config.FCTemplate{ID: "Tenant", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true)},
&config.FCTemplate{ID: "Category", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true)},
&config.FCTemplate{ID: "Account", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true)},
&config.FCTemplate{ID: "Destination", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true)},
&config.FCTemplate{ID: "AnswerTime", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true),
Layout: "2006-01-02T15:04:05Z07:00"},
&config.CfgCdrField{Tag: "UsageVoice", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*voice)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_seconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageData", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*data)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageSMS", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*sms)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Cost", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Cost", utils.INFIELD_SEP),
&config.FCTemplate{ID: "UsageVoice", Type: "*composed",
Filters: []string{"*string:ToR:*voice"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true)},
&config.FCTemplate{ID: "UsageData", Type: "*composed",
Filters: []string{"*string:ToR:*data"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "UsageSMS", Type: "*composed",
Filters: []string{"*string:ToR:*sms"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "Cost", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true),
RoundingDecimals: 4},
}
cdrVoice := &CDR{
@@ -178,10 +178,12 @@ func TestExportVoiceWithConvert(t *testing.T) {
ExtraFields: map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
}
data, _ := NewMapStorage()
dmForCDRe := NewDataManager(data)
cdre, err := NewCDRExporter([]*CDR{cdrVoice, cdrData, cdrSMS}, cdreCfg,
utils.MetaFileCSV, "", "", "firstexport",
true, 1, '|', map[string]float64{}, 0.0,
5, true, nil, nil)
5, true, nil, &FilterS{dm: dmForCDRe, cfg: cfg})
if err != nil {
t.Error("Unexpected error received: ", err)
}
@@ -209,35 +211,35 @@ func TestExportWithFilter(t *testing.T) {
cfg, _ := config.NewDefaultCGRConfig()
cdreCfg := cfg.CdreProfiles["*default"]
cdreCfg.Filters = []string{"*string:Tenant:cgrates.org"}
cdreCfg.ContentFields = []*config.CfgCdrField{
&config.CfgCdrField{Tag: "ToR", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("ToR", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("OriginID", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "RequestType", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("RequestType", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Tenant", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Tenant", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Category", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Category", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Account", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Account", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Destination", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Destination", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "AnswerTime", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("AnswerTime", utils.INFIELD_SEP),
cdreCfg.ContentFields = []*config.FCTemplate{
&config.FCTemplate{ID: "ToR", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true)},
&config.FCTemplate{ID: "OriginID", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true)},
&config.FCTemplate{ID: "RequestType", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true)},
&config.FCTemplate{ID: "Tenant", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true)},
&config.FCTemplate{ID: "Category", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true)},
&config.FCTemplate{ID: "Account", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true)},
&config.FCTemplate{ID: "Destination", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true)},
&config.FCTemplate{ID: "AnswerTime", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true),
Layout: "2006-01-02T15:04:05Z07:00"},
&config.CfgCdrField{Tag: "UsageVoice", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*voice)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_seconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageData", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*data)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageSMS", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*sms)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Cost", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Cost", utils.INFIELD_SEP),
&config.FCTemplate{ID: "UsageVoice", Type: "*composed",
Filters: []string{"*string:ToR:*voice"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true)},
&config.FCTemplate{ID: "UsageData", Type: "*composed",
Filters: []string{"*string:ToR:*data"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "UsageSMS", Type: "*composed",
Filters: []string{"*string:ToR:*sms"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "Cost", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true),
RoundingDecimals: 4},
}
cdrVoice := &CDR{
@@ -306,35 +308,35 @@ func TestExportWithFilter2(t *testing.T) {
cfg, _ := config.NewDefaultCGRConfig()
cdreCfg := cfg.CdreProfiles["*default"]
cdreCfg.Filters = []string{"*string:Tenant:cgrates.org", "*lte:Cost:0.5"}
cdreCfg.ContentFields = []*config.CfgCdrField{
&config.CfgCdrField{Tag: "ToR", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("ToR", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginID", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("OriginID", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "RequestType", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("RequestType", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Tenant", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Tenant", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Category", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Category", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Account", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Account", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Destination", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Destination", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "AnswerTime", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("AnswerTime", utils.INFIELD_SEP),
cdreCfg.ContentFields = []*config.FCTemplate{
&config.FCTemplate{ID: "ToR", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true)},
&config.FCTemplate{ID: "OriginID", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true)},
&config.FCTemplate{ID: "RequestType", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true)},
&config.FCTemplate{ID: "Tenant", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true)},
&config.FCTemplate{ID: "Category", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true)},
&config.FCTemplate{ID: "Account", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true)},
&config.FCTemplate{ID: "Destination", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true)},
&config.FCTemplate{ID: "AnswerTime", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true),
Layout: "2006-01-02T15:04:05Z07:00"},
&config.CfgCdrField{Tag: "UsageVoice", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*voice)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_seconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageData", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*data)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "UsageSMS", Type: "*composed",
FieldFilter: utils.ParseRSRFieldsMustCompile("ToR(*sms)", utils.INFIELD_SEP),
Value: utils.ParseRSRFieldsMustCompile("Usage{*duration_nanoseconds}", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Cost", Type: "*composed",
Value: utils.ParseRSRFieldsMustCompile("Cost", utils.INFIELD_SEP),
&config.FCTemplate{ID: "UsageVoice", Type: "*composed",
Filters: []string{"*string:ToR:*voice"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true)},
&config.FCTemplate{ID: "UsageData", Type: "*composed",
Filters: []string{"*string:ToR:*data"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "UsageSMS", Type: "*composed",
Filters: []string{"*string:ToR:*sms"},
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true)},
&config.FCTemplate{ID: "Cost", Type: "*composed",
Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true),
RoundingDecimals: 4},
}
cdrVoice := &CDR{

View File

@@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package engine
/*
import (
"bytes"
"math"
@@ -407,3 +408,4 @@ func TestWriteCdrs(t *testing.T) {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
}
}
*/

View File

@@ -307,85 +307,88 @@ func (self *CdrServer) deriveRateStoreStatsReplicate(cdr *CDR, store, cdrstats,
}
func (self *CdrServer) deriveCdrs(cdr *CDR) (drvdCDRs []*CDR, err error) {
dfltCDRRun := cdr.Clone()
cdrRuns := []*CDR{dfltCDRRun}
if cdr.RunID != utils.MetaRaw { // Only derive *raw CDRs
return cdrRuns, nil
}
dfltCDRRun.RunID = utils.META_DEFAULT // Rewrite *raw with *default since we have it as first run
if self.attrS != nil {
var rplyEv AttrSProcessEventReply
if err = self.attrS.Call(utils.AttributeSv1ProcessEvent,
cdr.AsCGREvent(), &rplyEv); err != nil {
return
/*
dfltCDRRun := cdr.Clone()
cdrRuns := []*CDR{dfltCDRRun}
if cdr.RunID != utils.MetaRaw { // Only derive *raw CDRs
return cdrRuns, nil
}
if err = cdr.UpdateFromCGREvent(rplyEv.CGREvent,
rplyEv.AlteredFields); err != nil {
return
}
}
if err := LoadUserProfile(cdr, utils.EXTRA_FIELDS); err != nil {
return nil, err
}
if err := LoadAlias(&AttrMatchingAlias{
Destination: cdr.Destination,
Direction: utils.OUT,
Tenant: cdr.Tenant,
Category: cdr.Category,
Account: cdr.Account,
Subject: cdr.Subject,
Context: utils.MetaRating,
}, cdr, utils.EXTRA_FIELDS); err != nil && err != utils.ErrNotFound {
return nil, err
}
attrsDC := &utils.AttrDerivedChargers{Tenant: cdr.Tenant, Category: cdr.Category, Direction: utils.OUT,
Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination}
var dcs utils.DerivedChargers
if err := self.rals.Call("Responder.GetDerivedChargers", attrsDC, &dcs); err != nil {
utils.Logger.Err(fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", cdr.CGRID, err.Error()))
return nil, err
}
for _, dc := range dcs.Chargers {
runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP)
matchingAllFilters := true
for _, dcRunFilter := range runFilters {
if _, err := cdr.FieldAsString(dcRunFilter); err != nil {
matchingAllFilters = false
break
dfltCDRRun.RunID = utils.META_DEFAULT // Rewrite *raw with *default since we have it as first run
if self.attrS != nil {
var rplyEv AttrSProcessEventReply
if err = self.attrS.Call(utils.AttributeSv1ProcessEvent,
cdr.AsCGREvent(), &rplyEv); err != nil {
return
}
if err = cdr.UpdateFromCGREvent(rplyEv.CGREvent,
rplyEv.AlteredFields); err != nil {
return
}
}
if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched
continue
if err := LoadUserProfile(cdr, utils.EXTRA_FIELDS); err != nil {
return nil, err
}
dcRequestTypeFld, _ := utils.NewRSRField(dc.RequestTypeField)
dcTenantFld, _ := utils.NewRSRField(dc.TenantField)
dcCategoryFld, _ := utils.NewRSRField(dc.CategoryField)
dcAcntFld, _ := utils.NewRSRField(dc.AccountField)
dcSubjFld, _ := utils.NewRSRField(dc.SubjectField)
dcDstFld, _ := utils.NewRSRField(dc.DestinationField)
dcSTimeFld, _ := utils.NewRSRField(dc.SetupTimeField)
dcATimeFld, _ := utils.NewRSRField(dc.AnswerTimeField)
dcDurFld, _ := utils.NewRSRField(dc.UsageField)
dcRatedFld, _ := utils.NewRSRField(dc.PreRatedField)
dcCostFld, _ := utils.NewRSRField(dc.CostField)
if err := LoadAlias(&AttrMatchingAlias{
Destination: cdr.Destination,
Direction: utils.OUT,
Tenant: cdr.Tenant,
Category: cdr.Category,
Account: cdr.Account,
Subject: cdr.Subject,
Context: utils.MetaRating,
}, cdr, utils.EXTRA_FIELDS); err != nil && err != utils.ErrNotFound {
return nil, err
}
attrsDC := &utils.AttrDerivedChargers{Tenant: cdr.Tenant, Category: cdr.Category, Direction: utils.OUT,
Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination}
var dcs utils.DerivedChargers
if err := self.rals.Call("Responder.GetDerivedChargers", attrsDC, &dcs); err != nil {
utils.Logger.Err(fmt.Sprintf("Could not get derived charging for cgrid %s, error: %s", cdr.CGRID, err.Error()))
return nil, err
}
for _, dc := range dcs.Chargers {
runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP)
matchingAllFilters := true
for _, dcRunFilter := range runFilters {
if _, err := cdr.FieldAsString(dcRunFilter); err != nil {
matchingAllFilters = false
break
}
}
if !matchingAllFilters { // Do not process the derived charger further if not all filters were matched
continue
}
dcRequestTypeFld, _ := utils.NewRSRField(dc.RequestTypeField)
dcTenantFld, _ := utils.NewRSRField(dc.TenantField)
dcCategoryFld, _ := utils.NewRSRField(dc.CategoryField)
dcAcntFld, _ := utils.NewRSRField(dc.AccountField)
dcSubjFld, _ := utils.NewRSRField(dc.SubjectField)
dcDstFld, _ := utils.NewRSRField(dc.DestinationField)
dcSTimeFld, _ := utils.NewRSRField(dc.SetupTimeField)
dcATimeFld, _ := utils.NewRSRField(dc.AnswerTimeField)
dcDurFld, _ := utils.NewRSRField(dc.UsageField)
dcRatedFld, _ := utils.NewRSRField(dc.PreRatedField)
dcCostFld, _ := utils.NewRSRField(dc.CostField)
dcExtraFields := []*utils.RSRField{}
for key, _ := range cdr.ExtraFields {
dcExtraFields = append(dcExtraFields, &utils.RSRField{Id: key})
}
dcExtraFields := []*utils.RSRField{}
for key, _ := range cdr.ExtraFields {
dcExtraFields = append(dcExtraFields, &utils.RSRField{Id: key})
}
forkedCdr, err := cdr.ForkCdr(dc.RunID, dcRequestTypeFld, dcTenantFld, dcCategoryFld, dcAcntFld, dcSubjFld, dcDstFld,
dcSTimeFld, dcATimeFld, dcDurFld, dcRatedFld, dcCostFld, dcExtraFields, true, self.cgrCfg.DefaultTimezone)
if err != nil {
utils.Logger.Err(fmt.Sprintf("Could not fork CGR with cgrid %s, run: %s, error: %s", cdr.CGRID, dc.RunID, err.Error()))
continue // do not add it to the forked CDR list
forkedCdr, err := cdr.ForkCdr(dc.RunID, dcRequestTypeFld, dcTenantFld, dcCategoryFld, dcAcntFld, dcSubjFld, dcDstFld,
dcSTimeFld, dcATimeFld, dcDurFld, dcRatedFld, dcCostFld, dcExtraFields, true, self.cgrCfg.DefaultTimezone)
if err != nil {
utils.Logger.Err(fmt.Sprintf("Could not fork CGR with cgrid %s, run: %s, error: %s", cdr.CGRID, dc.RunID, err.Error()))
continue // do not add it to the forked CDR list
}
if !forkedCdr.PreRated {
forkedCdr.Cost = -1.0 // Make sure that un-rated CDRs start with Cost -1
}
cdrRuns = append(cdrRuns, forkedCdr)
}
if !forkedCdr.PreRated {
forkedCdr.Cost = -1.0 // Make sure that un-rated CDRs start with Cost -1
}
cdrRuns = append(cdrRuns, forkedCdr)
}
return cdrRuns, nil
return cdrRuns, nil
*/
return
}
// rateCDR will populate cost field

View File

@@ -367,7 +367,7 @@ func (rs *Responder) GetDerivedMaxSessionTime(ev *CDR, reply *float64) (err erro
runFilters, _ := utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP)
matchingAllFilters := true
for _, dcRunFilter := range runFilters {
if _, err := ev.FieldAsString(dcRunFilter); err != nil {
if _, err := ev.FieldAsStringWithRSRField(dcRunFilter); err != nil {
matchingAllFilters = false
break
}

View File

@@ -42,14 +42,14 @@ func NewSureTaxRequest(cdr *CDR, stCfg *config.SureTaxCfg) (*SureTaxRequest, err
}
aTimeLoc := cdr.AnswerTime.In(stCfg.Timezone)
revenue := utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE)
unts, err := strconv.ParseInt(cdr.FieldsAsString(stCfg.Units), 10, 64)
unts, err := strconv.ParseInt(cdr.FieldsAsStringWithRSRFields(stCfg.Units), 10, 64)
if err != nil {
return nil, err
}
taxExempt := []string{}
definedTaxExtempt := cdr.FieldsAsString(stCfg.TaxExemptionCodeList)
definedTaxExtempt := cdr.FieldsAsStringWithRSRFields(stCfg.TaxExemptionCodeList)
if len(definedTaxExtempt) != 0 {
taxExempt = strings.Split(cdr.FieldsAsString(stCfg.TaxExemptionCodeList), ",")
taxExempt = strings.Split(cdr.FieldsAsStringWithRSRFields(stCfg.TaxExemptionCodeList), ",")
}
stReq := new(STRequest)
stReq.ClientNumber = stCfg.ClientNumber
@@ -59,28 +59,28 @@ func NewSureTaxRequest(cdr *CDR, stCfg *config.SureTaxCfg) (*SureTaxRequest, err
stReq.DataMonth = strconv.Itoa(int(aTimeLoc.Month()))
stReq.TotalRevenue = revenue
stReq.ReturnFileCode = stCfg.ReturnFileCode
stReq.ClientTracking = cdr.FieldsAsString(stCfg.ClientTracking)
stReq.ClientTracking = cdr.FieldsAsStringWithRSRFields(stCfg.ClientTracking)
stReq.ResponseGroup = stCfg.ResponseGroup
stReq.ResponseType = stCfg.ResponseType
stReq.ItemList = []*STRequestItem{
&STRequestItem{
CustomerNumber: cdr.FieldsAsString(stCfg.CustomerNumber),
OrigNumber: cdr.FieldsAsString(stCfg.OrigNumber),
TermNumber: cdr.FieldsAsString(stCfg.TermNumber),
BillToNumber: cdr.FieldsAsString(stCfg.BillToNumber),
Zipcode: cdr.FieldsAsString(stCfg.Zipcode),
Plus4: cdr.FieldsAsString(stCfg.Plus4),
P2PZipcode: cdr.FieldsAsString(stCfg.P2PZipcode),
P2PPlus4: cdr.FieldsAsString(stCfg.P2PPlus4),
CustomerNumber: cdr.FieldsAsStringWithRSRFields(stCfg.CustomerNumber),
OrigNumber: cdr.FieldsAsStringWithRSRFields(stCfg.OrigNumber),
TermNumber: cdr.FieldsAsStringWithRSRFields(stCfg.TermNumber),
BillToNumber: cdr.FieldsAsStringWithRSRFields(stCfg.BillToNumber),
Zipcode: cdr.FieldsAsStringWithRSRFields(stCfg.Zipcode),
Plus4: cdr.FieldsAsStringWithRSRFields(stCfg.Plus4),
P2PZipcode: cdr.FieldsAsStringWithRSRFields(stCfg.P2PZipcode),
P2PPlus4: cdr.FieldsAsStringWithRSRFields(stCfg.P2PPlus4),
TransDate: aTimeLoc.Format("2006-01-02T15:04:05"),
Revenue: revenue,
Units: unts,
UnitType: cdr.FieldsAsString(stCfg.UnitType),
UnitType: cdr.FieldsAsStringWithRSRFields(stCfg.UnitType),
Seconds: int64(cdr.Usage.Seconds()),
TaxIncludedCode: cdr.FieldsAsString(stCfg.TaxIncluded),
TaxSitusRule: cdr.FieldsAsString(stCfg.TaxSitusRule),
TransTypeCode: cdr.FieldsAsString(stCfg.TransTypeCode),
SalesTypeCode: cdr.FieldsAsString(stCfg.SalesTypeCode),
TaxIncludedCode: cdr.FieldsAsStringWithRSRFields(stCfg.TaxIncluded),
TaxSitusRule: cdr.FieldsAsStringWithRSRFields(stCfg.TaxSitusRule),
TransTypeCode: cdr.FieldsAsStringWithRSRFields(stCfg.TransTypeCode),
SalesTypeCode: cdr.FieldsAsStringWithRSRFields(stCfg.SalesTypeCode),
RegulatoryCode: stCfg.RegulatoryCode,
TaxExemptionCodeList: taxExempt,
},