From 3bc9f0e4cd0dd02974f7654c4c511e57277ab179 Mon Sep 17 00:00:00 2001 From: DanB Date: Wed, 13 May 2015 23:42:29 +0200 Subject: [PATCH] DisconnecCause in StoredCdr and DerivedCharging --- data/storage/mysql/create_cdrs_tables.sql | 2 + .../mysql/create_tariffplan_tables.sql | 1 + data/storage/postgres/create_cdrs_tables.sql | 2 + .../postgres/create_tariffplan_tables.sql | 1 + .../prepaid1centpsec/DerivedChargers.csv | 10 +- data/tariffplans/tutorial/DerivedChargers.csv | 4 +- engine/cdrs.go | 3 +- engine/event.go | 1 + engine/loader_csv.go | 54 +-- engine/loader_csv_test.go | 14 +- engine/loader_db.go | 3 +- engine/loader_helpers.go | 4 +- engine/loader_helpers_test.go | 22 +- engine/models.go | 123 +++---- engine/responder_test.go | 6 +- engine/storage_mysql.go | 3 +- engine/storage_postgres.go | 35 +- engine/storage_sql.go | 90 +++-- engine/storedcdr.go | 167 +++++---- engine/storedcdr_test.go | 13 +- sessionmanager/fsevent.go | 70 ++-- sessionmanager/fsevent_test.go | 20 +- sessionmanager/kamevent.go | 9 + sessionmanager/kamevent_test.go | 8 +- sessionmanager/osipsevent.go | 9 + sessionmanager/osipsevent_test.go | 12 +- utils/apitpdata.go | 319 +++++++++--------- utils/apitpdata_test.go | 58 ++-- utils/consts.go | 4 +- utils/derivedchargers.go | 62 ++-- utils/derivedchargers_test.go | 68 ++-- 31 files changed, 660 insertions(+), 537 deletions(-) diff --git a/data/storage/mysql/create_cdrs_tables.sql b/data/storage/mysql/create_cdrs_tables.sql index e9264ea86..7755fb6ba 100644 --- a/data/storage/mysql/create_cdrs_tables.sql +++ b/data/storage/mysql/create_cdrs_tables.sql @@ -22,6 +22,7 @@ CREATE TABLE cdrs_primary ( answer_time datetime NOT NULL, `usage` DECIMAL(30,9) NOT NULL, supplier varchar(128) NOT NULL, + disconnect_cause varchar(64) NOT NULL, created_at TIMESTAMP, deleted_at TIMESTAMP, PRIMARY KEY (id), @@ -93,6 +94,7 @@ CREATE TABLE `rated_cdrs` ( answer_time datetime NOT NULL, `usage` DECIMAL(30,9) NOT NULL, supplier varchar(128) NOT NULL, + disconnect_cause varchar(64) NOT NULL, cost DECIMAL(20,4) DEFAULT NULL, extra_info text, created_at TIMESTAMP, diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index fddbcd2f2..28add37e4 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -290,6 +290,7 @@ CREATE TABLE tp_derived_chargers ( `answer_time_field` varchar(24) NOT NULL, `usage_field` varchar(24) NOT NULL, `supplier_field` varchar(24) NOT NULL, + `disconnect_cause_field` varchar(24) NOT NULL, `created_at` TIMESTAMP, PRIMARY KEY (`id`), KEY `tpid` (`tpid`) diff --git a/data/storage/postgres/create_cdrs_tables.sql b/data/storage/postgres/create_cdrs_tables.sql index e4e8b8fc5..25c2abada 100644 --- a/data/storage/postgres/create_cdrs_tables.sql +++ b/data/storage/postgres/create_cdrs_tables.sql @@ -22,6 +22,7 @@ CREATE TABLE cdrs_primary ( answer_time TIMESTAMP NOT NULL, usage NUMERIC(30,9) NOT NULL, supplier VARCHAR(128) NOT NULL, + disconnect_cause VARCHAR(64) NOT NULL, created_at TIMESTAMP, deleted_at TIMESTAMP, UNIQUE (cgrid) @@ -89,6 +90,7 @@ CREATE TABLE rated_cdrs ( answer_time TIMESTAMP NOT NULL, usage NUMERIC(30,9) NOT NULL, supplier VARCHAR(128) NOT NULL, + disconnect_cause VARCHAR(64) NOT NULL, cost NUMERIC(20,4) DEFAULT NULL, extra_info text, created_at TIMESTAMP, diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql index 9520d270d..dc5d85214 100644 --- a/data/storage/postgres/create_tariffplan_tables.sql +++ b/data/storage/postgres/create_tariffplan_tables.sql @@ -285,6 +285,7 @@ CREATE TABLE tp_derived_chargers ( answer_time_field VARCHAR(24) NOT NULL, usage_field VARCHAR(24) NOT NULL, supplier_field VARCHAR(24) NOT NULL, + disconnect_cause_field VARCHAR(24) NOT NULL, created_at TIMESTAMP ); CREATE INDEX tpderivedchargers_tpid_idx ON tp_derived_chargers (tpid); diff --git a/data/tariffplans/prepaid1centpsec/DerivedChargers.csv b/data/tariffplans/prepaid1centpsec/DerivedChargers.csv index 780806a40..e30555700 100644 --- a/data/tariffplans/prepaid1centpsec/DerivedChargers.csv +++ b/data/tariffplans/prepaid1centpsec/DerivedChargers.csv @@ -1,5 +1,5 @@ -#Direction,Tenant,Tor,Account,Subject,RunId,RunFilter,ReqTypeField,DirectionField,TenantField,TorField,AccountField,SubjectField,DestinationField,SetupTimeField,AnswerTimeField,UsageField,SupplierField -*out,cgrates.org,call,dan,dan,extra1,,^prepaid,,,,^rif,^rif,,,,^1s,*default -*out,cgrates.org,call,dan,dan,extra2,,,,,,^ivo,^ivo,,,,,*default -*out,cgrates.org,call,dan,dan,extra3,~filterhdr1:s/(.+)/special_run3/,,,,,^runusr3,^runusr3,,,,,*default -*out,cgrates.org,call,dan,*any,extra1,,,,,,^rif2,^rif2,,,,,*default +#Direction,Tenant,Tor,Account,Subject,RunId,RunFilter,ReqTypeField,DirectionField,TenantField,TorField,AccountField,SubjectField,DestinationField,SetupTimeField,AnswerTimeField,UsageField,SupplierField,DisconnectCause +*out,cgrates.org,call,dan,dan,extra1,,^prepaid,,,,^rif,^rif,,,,^1s,*default,*default +*out,cgrates.org,call,dan,dan,extra2,,,,,,^ivo,^ivo,,,,,*default,*default +*out,cgrates.org,call,dan,dan,extra3,~filterhdr1:s/(.+)/special_run3/,,,,,^runusr3,^runusr3,,,,,*default,*default +*out,cgrates.org,call,dan,*any,extra1,,,,,,^rif2,^rif2,,,,,*default,*default diff --git a/data/tariffplans/tutorial/DerivedChargers.csv b/data/tariffplans/tutorial/DerivedChargers.csv index 0eaa64b43..effad6e0b 100644 --- a/data/tariffplans/tutorial/DerivedChargers.csv +++ b/data/tariffplans/tutorial/DerivedChargers.csv @@ -1,2 +1,2 @@ -#Direction[0],Tenant[1],Category[2],Account[3],Subject[4],RunId[5],RunFilter[6],ReqTypeField[7],DirectionField[8],TenantField[9],CategoryField[10],AccountField[11],SubjectField[12],DestinationField[13],SetupTimeField[14],AnswerTimeField[15],UsageField[16],SupplierField[17] -*out,cgrates.org,call,1001,1001,derived_run1,,^*rated,*default,*default,*default,*default,^1002,*default,*default,*default,*default,*default +#Direction[0],Tenant[1],Category[2],Account[3],Subject[4],RunId[5],RunFilter[6],ReqTypeField[7],DirectionField[8],TenantField[9],CategoryField[10],AccountField[11],SubjectField[12],DestinationField[13],SetupTimeField[14],AnswerTimeField[15],UsageField[16],SupplierField[17],DisconnectCause[18] +*out,cgrates.org,call,1001,1001,derived_run1,,^*rated,*default,*default,*default,*default,^1002,*default,*default,*default,*default,*default,*default diff --git a/engine/cdrs.go b/engine/cdrs.go index b34708874..a7d34858d 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -272,8 +272,9 @@ func (self *CdrServer) deriveCdrs(storedCdr *StoredCdr) ([]*StoredCdr, error) { dcATimeFld, _ := utils.NewRSRField(dc.AnswerTimeField) dcDurFld, _ := utils.NewRSRField(dc.UsageField) dcSupplFld, _ := utils.NewRSRField(dc.SupplierField) + dcDCausseld, _ := utils.NewRSRField(dc.DisconnectCauseField) forkedCdr, err := storedCdr.ForkCdr(dc.RunId, dcReqTypeFld, dcDirFld, dcTenantFld, dcCategoryFld, dcAcntFld, dcSubjFld, dcDstFld, - dcSTimeFld, dcATimeFld, dcDurFld, dcSupplFld, []*utils.RSRField{}, true) + dcSTimeFld, dcATimeFld, dcDurFld, dcSupplFld, dcDCausseld, []*utils.RSRField{}, true) if err != nil { Logger.Err(fmt.Sprintf("Could not fork CGR with cgrid %s, run: %s, error: %s", storedCdr.CgrId, dc.RunId, err.Error())) continue // do not add it to the forked CDR list diff --git a/engine/event.go b/engine/event.go index db874e777..b88c82e91 100644 --- a/engine/event.go +++ b/engine/event.go @@ -41,6 +41,7 @@ type Event interface { GetEndTime() (time.Time, error) GetDuration(string) (time.Duration, error) GetSupplier(string) string + GetDisconnectCause(string) string GetOriginatorIP(string) string GetExtraFields() map[string]string MissingParameter() bool diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 934bcd44f..ef8dc60ea 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -922,19 +922,20 @@ func (csvr *CSVReader) LoadDerivedChargers() (err error) { tag := utils.DerivedChargersKey(record[0], record[1], record[2], record[3], record[4]) if _, found := csvr.derivedChargers[tag]; found { if csvr.derivedChargers[tag], err = csvr.derivedChargers[tag].Append(&utils.DerivedCharger{ - RunId: ValueOrDefault(record[5], "*default"), - RunFilters: record[6], - ReqTypeField: ValueOrDefault(record[7], "*default"), - DirectionField: ValueOrDefault(record[8], "*default"), - TenantField: ValueOrDefault(record[9], "*default"), - CategoryField: ValueOrDefault(record[10], "*default"), - AccountField: ValueOrDefault(record[11], "*default"), - SubjectField: ValueOrDefault(record[12], "*default"), - DestinationField: ValueOrDefault(record[13], "*default"), - SetupTimeField: ValueOrDefault(record[14], "*default"), - AnswerTimeField: ValueOrDefault(record[15], "*default"), - UsageField: ValueOrDefault(record[16], "*default"), - SupplierField: ValueOrDefault(record[17], "*default"), + RunId: ValueOrDefault(record[5], "*default"), + RunFilters: record[6], + ReqTypeField: ValueOrDefault(record[7], "*default"), + DirectionField: ValueOrDefault(record[8], "*default"), + TenantField: ValueOrDefault(record[9], "*default"), + CategoryField: ValueOrDefault(record[10], "*default"), + AccountField: ValueOrDefault(record[11], "*default"), + SubjectField: ValueOrDefault(record[12], "*default"), + DestinationField: ValueOrDefault(record[13], "*default"), + SetupTimeField: ValueOrDefault(record[14], "*default"), + AnswerTimeField: ValueOrDefault(record[15], "*default"), + UsageField: ValueOrDefault(record[16], "*default"), + SupplierField: ValueOrDefault(record[17], "*default"), + DisconnectCauseField: ValueOrDefault(record[18], "*default"), }); err != nil { return err } @@ -943,19 +944,20 @@ func (csvr *CSVReader) LoadDerivedChargers() (err error) { return errors.New("Reserved RunId") } csvr.derivedChargers[tag] = utils.DerivedChargers{&utils.DerivedCharger{ - RunId: ValueOrDefault(record[5], "*default"), - RunFilters: record[6], - ReqTypeField: ValueOrDefault(record[7], "*default"), - DirectionField: ValueOrDefault(record[8], "*default"), - TenantField: ValueOrDefault(record[9], "*default"), - CategoryField: ValueOrDefault(record[10], "*default"), - AccountField: ValueOrDefault(record[11], "*default"), - SubjectField: ValueOrDefault(record[12], "*default"), - DestinationField: ValueOrDefault(record[13], "*default"), - SetupTimeField: ValueOrDefault(record[14], "*default"), - AnswerTimeField: ValueOrDefault(record[15], "*default"), - UsageField: ValueOrDefault(record[16], "*default"), - SupplierField: ValueOrDefault(record[17], "*default"), + RunId: ValueOrDefault(record[5], "*default"), + RunFilters: record[6], + ReqTypeField: ValueOrDefault(record[7], "*default"), + DirectionField: ValueOrDefault(record[8], "*default"), + TenantField: ValueOrDefault(record[9], "*default"), + CategoryField: ValueOrDefault(record[10], "*default"), + AccountField: ValueOrDefault(record[11], "*default"), + SubjectField: ValueOrDefault(record[12], "*default"), + DestinationField: ValueOrDefault(record[13], "*default"), + SetupTimeField: ValueOrDefault(record[14], "*default"), + AnswerTimeField: ValueOrDefault(record[15], "*default"), + UsageField: ValueOrDefault(record[16], "*default"), + SupplierField: ValueOrDefault(record[17], "*default"), + DisconnectCauseField: ValueOrDefault(record[18], "*default"), }} } } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 1bda36a4c..62312c860 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -190,9 +190,9 @@ vdf,emptyY,*out,TOPUP_EMPTY_AT, derivedCharges = ` #Direction,Tenant,Category,Account,Subject,RunId,RunFilter,ReqTypeField,DirectionField,TenantField,TorField,AccountField,SubjectField,DestinationField,SetupTimeField,AnswerTimeField,UsageField -*out,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1/,^prepaid,,,,rif,rif,,,,, -*out,cgrates.org,call,dan,dan,extra2,,,,,,ivo,ivo,,,,, -*out,cgrates.org,call,dan,*any,extra1,,,,,,rif2,rif2,,,,, +*out,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1/,^prepaid,,,,rif,rif,,,,,, +*out,cgrates.org,call,dan,dan,extra2,,,,,,ivo,ivo,,,,,, +*out,cgrates.org,call,dan,*any,extra1,,,,,,rif2,rif2,,,,,, ` cdrStats = ` #Id,QueueLength,TimeWindow,Metrics,SetupInterval,TOR,CdrHost,CdrSource,ReqType,Direction,Tenant,Category,Account,Subject,DestinationPrefix,UsageInterval,Supplier,MediationRunIds,RatedAccount,RatedSubject,CostInterval,Triggers @@ -973,14 +973,16 @@ func TestLoadDerivedChargers(t *testing.T) { expCharger1 := utils.DerivedChargers{ &utils.DerivedCharger{RunId: "extra1", RunFilters: "^filteredHeader1/filterValue1/", ReqTypeField: "^prepaid", DirectionField: utils.META_DEFAULT, TenantField: utils.META_DEFAULT, CategoryField: utils.META_DEFAULT, AccountField: "rif", SubjectField: "rif", DestinationField: utils.META_DEFAULT, - SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT}, + SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT, + DisconnectCauseField: utils.META_DEFAULT}, &utils.DerivedCharger{RunId: "extra2", ReqTypeField: utils.META_DEFAULT, DirectionField: utils.META_DEFAULT, TenantField: utils.META_DEFAULT, CategoryField: utils.META_DEFAULT, AccountField: "ivo", SubjectField: "ivo", DestinationField: utils.META_DEFAULT, - SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT}, + SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT, + DisconnectCauseField: utils.META_DEFAULT}, } keyCharger1 := utils.DerivedChargersKey("*out", "cgrates.org", "call", "dan", "dan") if !reflect.DeepEqual(csvr.derivedChargers[keyCharger1], expCharger1) { - t.Error("Unexpected charger", csvr.derivedChargers[keyCharger1]) + t.Errorf("Unexpected charger %+v", csvr.derivedChargers[keyCharger1][0]) } } func TestLoadCdrStats(t *testing.T) { diff --git a/engine/loader_db.go b/engine/loader_db.go index bf7943f1a..2d057b882 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -908,7 +908,8 @@ func (dbr *DbReader) LoadDerivedChargersFiltered(filter *utils.TPDerivedChargers } for _, tpDc := range tpDcs.DerivedChargers { if dc, err := utils.NewDerivedCharger(tpDc.RunId, tpDc.RunFilters, tpDc.ReqTypeField, tpDc.DirectionField, tpDc.TenantField, tpDc.CategoryField, - tpDc.AccountField, tpDc.SubjectField, tpDc.DestinationField, tpDc.SetupTimeField, tpDc.AnswerTimeField, tpDc.UsageField, tpDc.SupplierField); err != nil { + tpDc.AccountField, tpDc.SubjectField, tpDc.DestinationField, tpDc.SetupTimeField, tpDc.AnswerTimeField, tpDc.UsageField, tpDc.SupplierField, + tpDc.DisconnectCauseField); err != nil { return err } else { dbr.derivedChargers[tag] = append(dbr.derivedChargers[tag], dc) diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 7a73612d5..d39eaf960 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -420,8 +420,8 @@ var FileValidators = map[string]*FileLineRegexValidator{ regexp.MustCompile(`(?:\w+\s*),(?:(\w+;?)+\s*),(?:\*out\s*),(?:\w+\s*),(?:\w+\s*)$`), "Tenant([0-9A-Za-z_]),Account([0-9A-Za-z_.]),Direction(*out),ActionTimingsTag([0-9A-Za-z_]),ActionTriggersTag([0-9A-Za-z_])"}, utils.DERIVED_CHARGERS_CSV: &FileLineRegexValidator{utils.DERIVED_CHARGERS_NRCOLS, - regexp.MustCompile(`^(?:\*out),(?:[0-9A-Za-z_\.]+\s*),(?:\w+\s*),(?:\w+\s*),(?:\*any\s*|\w+\s*),(?:\w+\s*),(?:[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^*]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?$`), - "Direction(*out),Tenant[0-9A-Za-z_],Category([0-9A-Za-z_]),Account[0-9A-Za-z_],Subject([0-9A-Za-z_]|*any),RunId([0-9A-Za-z_]),RunFilter([^~]*[0-9A-Za-z_/]),ReqTypeField([^~]*[0-9A-Za-z_/]|*default),DirectionField([^~]*[0-9A-Za-z_/]|*default),TenantField([^~]*[0-9A-Za-z_/]|*default),TorField([^~]*[0-9A-Za-z_/]|*default),AccountField([^~]*[0-9A-Za-z_/]|*default),SubjectField([^~]*[0-9A-Za-z_/]|*default),DestinationField([^~]*[0-9A-Za-z_/]|*default),SetupTimeField([^~]*[0-9A-Za-z_/]|*default),AnswerTimeField([^~]*[0-9A-Za-z_/]|*default),UsageField([^~]*[0-9A-Za-z_/]|*default),SupplierField([^~]*[0-9A-Za-z_/]|*default)"}, + regexp.MustCompile(`^(?:\*out),(?:[0-9A-Za-z_\.]+\s*),(?:\w+\s*),(?:\w+\s*),(?:\*any\s*|\w+\s*),(?:\w+\s*),(?:[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^*]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?,(?:\*default\s*|[~^]*[0-9A-Za-z_/:().+]+\s*)?$`), + "Direction(*out),Tenant[0-9A-Za-z_],Category([0-9A-Za-z_]),Account[0-9A-Za-z_],Subject([0-9A-Za-z_]|*any),RunId([0-9A-Za-z_]),RunFilter([^~]*[0-9A-Za-z_/]),ReqTypeField([^~]*[0-9A-Za-z_/]|*default),DirectionField([^~]*[0-9A-Za-z_/]|*default),TenantField([^~]*[0-9A-Za-z_/]|*default),TorField([^~]*[0-9A-Za-z_/]|*default),AccountField([^~]*[0-9A-Za-z_/]|*default),SubjectField([^~]*[0-9A-Za-z_/]|*default),DestinationField([^~]*[0-9A-Za-z_/]|*default),SetupTimeField([^~]*[0-9A-Za-z_/]|*default),AnswerTimeField([^~]*[0-9A-Za-z_/]|*default),UsageField([^~]*[0-9A-Za-z_/]|*default),SupplierField([^~]*[0-9A-Za-z_/]|*default),DisconnectCauseField([^~]*[0-9A-Za-z_/]|*default)"}, utils.CDR_STATS_CSV: &FileLineRegexValidator{utils.CDR_STATS_NRCOLS, regexp.MustCompile(`.+`), //ToDo: Fix me with proper rules "Id,QueueLength,TimeWindow,Metric,SetupInterval,TOR,CdrHost,CdrSource,ReqType,Direction,Tenant,Category,Account,Subject,DestinationPrefix,UsageInterval,Supplier,MediationRunIds,RatedAccount,RatedSubject,CostInterval,Triggers(*?[0-9A-Za-z_]),Strategy(*[0-9A-Za-z_]),RatingSubject(*?[0-9A-Za-z_])"}, diff --git a/engine/loader_helpers_test.go b/engine/loader_helpers_test.go index a6caf3e12..9d2ceefa5 100644 --- a/engine/loader_helpers_test.go +++ b/engine/loader_helpers_test.go @@ -96,19 +96,19 @@ DUMMY,INVALID;DATA cgrates.org,1002;1006,*out,PACKAGE_10,STANDARD_TRIGGERS ` var derivedChargesSample = `#Direction,Tenant,Tor,Account,Subject,RunId,ReqTypeField,DirectionField,TenantField,TorField,AccountField,SubjectField,DestinationField,SetupTimeField,AnswerTimeField,DurationField -*out,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,,,*default -*out,cgrates.org,,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,, -*in,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,, +*out,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,,,*default,*default +*out,cgrates.org,,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,,, +*in,cgrates.org,call,dan,dan,extra1,^filteredHeader1/filterValue1,^prepaid,,,,rif,rif,,,,, DUMMY_DATA -*out,cgrates.org,call,dan,dan,extra2,,,,,,ivo,ivo,,,,, -*out,cgrates.org,call,dan,*any,extra1,,,,,,rif2,rif2,,,,, +*out,cgrates.org,call,dan,dan,extra2,,,,,,ivo,ivo,,,,,, +*out,cgrates.org,call,dan,*any,extra1,,,,,,rif2,rif2,,,,,, *out,cgrates.org,call,dan,*any,*any,,,,,,rif2,rif2,,,, -*out,cgrates.org,call,dan,*any,*default,,*default,*default,*default,*default,rif2,rif2,*default,*default,*default,*default -*out,cgrates.org,call,dan,*any,test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test -*out,cgrates.org,call,dan,*any,run1,,,,,,,,,,,, -*out,cgrates.org,call,dan,*default,,,,,,,,,,,, -*out,cgrates.org,call,dan,dan,extra3,~filterhdr1:s/(.+)/special_run3/,,,,,^runusr3,^runusr3,,,,, -*out,cgrates.org,call,1001,1001,derived_run1,,^*rated,*default,*default,*default,*default,^1002,*default,*default,*default,*default,*default +*out,cgrates.org,call,dan,*any,*default,,*default,*default,*default,*default,rif2,rif2,*default,*default,*default,*default,*default +*out,cgrates.org,call,dan,*any,test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test,^test +*out,cgrates.org,call,dan,*any,run1,,,,,,,,,,,,, +*out,cgrates.org,call,dan,*default,,,,,,,,,,,,, +*out,cgrates.org,call,dan,dan,extra3,~filterhdr1:s/(.+)/special_run3/,,,,,^runusr3,^runusr3,,,,,, +*out,cgrates.org,call,1001,1001,derived_run1,,^*rated,*default,*default,*default,*default,^1002,*default,*default,*default,*default,*default,*default ` func TestTimingsValidator(t *testing.T) { diff --git a/engine/models.go b/engine/models.go index 94e0c3405..74becf2f5 100644 --- a/engine/models.go +++ b/engine/models.go @@ -218,28 +218,29 @@ type TpSharedGroup struct { } type TpDerivedCharger struct { - Id int64 - Tpid string - Loadid string - Direction string - Tenant string - Category string - Account string - Subject string - Runid string - RunFilters string - ReqTypeField string - DirectionField string - TenantField string - CategoryField string - AccountField string - SubjectField string - DestinationField string - SetupTimeField string - AnswerTimeField string - UsageField string - SupplierField string - CreatedAt time.Time + Id int64 + Tpid string + Loadid string + Direction string + Tenant string + Category string + Account string + Subject string + Runid string + RunFilters string + ReqTypeField string + DirectionField string + TenantField string + CategoryField string + AccountField string + SubjectField string + DestinationField string + SetupTimeField string + AnswerTimeField string + UsageField string + SupplierField string + DisconnectCauseField string + CreatedAt time.Time } func (tpdc *TpDerivedCharger) SetDerivedChargersId(id string) error { @@ -285,25 +286,26 @@ type TpCdrStat struct { } type TblCdrsPrimary struct { - Id int64 - Cgrid string - Tor string - Accid string - Cdrhost string - Cdrsource string - Reqtype string - Direction string - Tenant string - Category string - Account string - Subject string - Destination string - SetupTime time.Time - AnswerTime time.Time - Usage float64 - Supplier string - CreatedAt time.Time - DeletedAt time.Time + Id int64 + Cgrid string + Tor string + Accid string + Cdrhost string + Cdrsource string + Reqtype string + Direction string + Tenant string + Category string + Account string + Subject string + Destination string + SetupTime time.Time + AnswerTime time.Time + Usage float64 + Supplier string + DisconnectCause string + CreatedAt time.Time + DeletedAt time.Time } func (t TblCdrsPrimary) TableName() string { @@ -346,25 +348,26 @@ func (t TblCostDetail) TableName() string { } type TblRatedCdr struct { - Id int64 - Cgrid string - Runid string - Reqtype string - Direction string - Tenant string - Category string - Account string - Subject string - Destination string - SetupTime time.Time - AnswerTime time.Time - Usage float64 - Supplier string - Cost float64 - ExtraInfo string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt time.Time + Id int64 + Cgrid string + Runid string + Reqtype string + Direction string + Tenant string + Category string + Account string + Subject string + Destination string + SetupTime time.Time + AnswerTime time.Time + Usage float64 + Supplier string + DisconnectCause string + Cost float64 + ExtraInfo string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time } func (t TblRatedCdr) TableName() string { diff --git a/engine/responder_test.go b/engine/responder_test.go index d47b61454..e3b22a6a0 100644 --- a/engine/responder_test.go +++ b/engine/responder_test.go @@ -131,7 +131,8 @@ func TestGetSessionRuns(t *testing.T) { keyCharger1 := utils.ConcatenatedKey("*out", testTenant, "call", "dan2", "dan2") dfDC := &utils.DerivedCharger{RunId: utils.DEFAULT_RUNID, ReqTypeField: utils.META_DEFAULT, DirectionField: utils.META_DEFAULT, TenantField: utils.META_DEFAULT, CategoryField: utils.META_DEFAULT, AccountField: utils.META_DEFAULT, SubjectField: utils.META_DEFAULT, DestinationField: utils.META_DEFAULT, - SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT} + SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT, + DisconnectCauseField: utils.META_DEFAULT} extra1DC := &utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^" + utils.META_PREPAID, DirectionField: utils.META_DEFAULT, TenantField: utils.META_DEFAULT, CategoryField: "^0", AccountField: "^minitsboy", SubjectField: "^rif", DestinationField: "^0256", SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT} @@ -140,7 +141,8 @@ func TestGetSessionRuns(t *testing.T) { SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT} extra3DC := &utils.DerivedCharger{RunId: "extra3", ReqTypeField: "^" + utils.META_PSEUDOPREPAID, DirectionField: utils.META_DEFAULT, TenantField: utils.META_DEFAULT, CategoryField: "^0", AccountField: "^minu", SubjectField: "^rif", DestinationField: "^0256", - SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT} + SetupTimeField: utils.META_DEFAULT, AnswerTimeField: utils.META_DEFAULT, UsageField: utils.META_DEFAULT, SupplierField: utils.META_DEFAULT, + DisconnectCauseField: utils.META_DEFAULT} charger1 := utils.DerivedChargers{extra1DC, extra2DC, extra3DC} if err := accountingStorage.SetDerivedChargers(keyCharger1, charger1); err != nil { t.Error("Error on setting DerivedChargers", err.Error()) diff --git a/engine/storage_mysql.go b/engine/storage_mysql.go index 5ef249472..d7aad5e32 100644 --- a/engine/storage_mysql.go +++ b/engine/storage_mysql.go @@ -106,7 +106,7 @@ func (self *MySQLStorage) LogCallCost(cgrid, source, runid string, cc *CallCost) } func (self *MySQLStorage) SetRatedCdr(storedCdr *StoredCdr) (err error) { - _, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s (cgrid,runid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,`usage`,supplier,cost,extra_info,created_at) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s',%v,'%s',%f,'%s','%s') ON DUPLICATE KEY UPDATE reqtype=values(reqtype),direction=values(direction),tenant=values(tenant),category=values(category),account=values(account),subject=values(subject),destination=values(destination),setup_time=values(setup_time),answer_time=values(answer_time),`usage`=values(`usage`),cost=values(cost),supplier=values(supplier),extra_info=values(extra_info), updated_at='%s'", + _, err = self.Db.Exec(fmt.Sprintf("INSERT INTO %s (cgrid,runid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,`usage`,supplier,disconnect_cause,cost,extra_info,created_at) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s',%v,'%s','%s',%f,'%s','%s') ON DUPLICATE KEY UPDATE reqtype=values(reqtype),direction=values(direction),tenant=values(tenant),category=values(category),account=values(account),subject=values(subject),destination=values(destination),setup_time=values(setup_time),answer_time=values(answer_time),`usage`=values(`usage`),cost=values(cost),supplier=values(supplier),disconnect_cause=values(disconnect_cause),extra_info=values(extra_info), updated_at='%s'", utils.TBL_RATED_CDRS, storedCdr.CgrId, storedCdr.MediationRunId, @@ -121,6 +121,7 @@ func (self *MySQLStorage) SetRatedCdr(storedCdr *StoredCdr) (err error) { storedCdr.AnswerTime, storedCdr.Usage.Seconds(), storedCdr.Supplier, + storedCdr.DisconnectCause, storedCdr.Cost, storedCdr.ExtraInfo, time.Now().Format(time.RFC3339), diff --git a/engine/storage_postgres.go b/engine/storage_postgres.go index 1c8715ea5..3c7b6c324 100644 --- a/engine/storage_postgres.go +++ b/engine/storage_postgres.go @@ -126,29 +126,30 @@ func (self *PostgresStorage) LogCallCost(cgrid, source, runid string, cc *CallCo func (self *PostgresStorage) SetRatedCdr(cdr *StoredCdr) (err error) { tx := self.db.Begin() saved := tx.Save(&TblRatedCdr{ - Cgrid: cdr.CgrId, - Runid: cdr.MediationRunId, - Reqtype: cdr.ReqType, - Direction: cdr.Direction, - Tenant: cdr.Tenant, - Category: cdr.Category, - Account: cdr.Account, - Subject: cdr.Subject, - Destination: cdr.Destination, - SetupTime: cdr.SetupTime, - AnswerTime: cdr.AnswerTime, - Usage: cdr.Usage.Seconds(), - Supplier: cdr.Supplier, - Cost: cdr.Cost, - ExtraInfo: cdr.ExtraInfo, - CreatedAt: time.Now(), + Cgrid: cdr.CgrId, + Runid: cdr.MediationRunId, + Reqtype: cdr.ReqType, + Direction: cdr.Direction, + Tenant: cdr.Tenant, + Category: cdr.Category, + Account: cdr.Account, + Subject: cdr.Subject, + Destination: cdr.Destination, + SetupTime: cdr.SetupTime, + AnswerTime: cdr.AnswerTime, + Usage: cdr.Usage.Seconds(), + Supplier: cdr.Supplier, + DisconnectCause: cdr.DisconnectCause, + Cost: cdr.Cost, + ExtraInfo: cdr.ExtraInfo, + CreatedAt: time.Now(), }) if saved.Error != nil { tx.Rollback() tx = self.db.Begin() updated := tx.Model(TblRatedCdr{}).Where(&TblRatedCdr{Cgrid: cdr.CgrId, Runid: cdr.MediationRunId}).Updates(&TblRatedCdr{Reqtype: cdr.ReqType, Direction: cdr.Direction, Tenant: cdr.Tenant, Category: cdr.Category, Account: cdr.Account, Subject: cdr.Subject, Destination: cdr.Destination, - SetupTime: cdr.SetupTime, AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Supplier: cdr.Supplier, Cost: cdr.Cost, ExtraInfo: cdr.ExtraInfo, + SetupTime: cdr.SetupTime, AnswerTime: cdr.AnswerTime, Usage: cdr.Usage.Seconds(), Supplier: cdr.Supplier, DisconnectCause: cdr.DisconnectCause, Cost: cdr.Cost, ExtraInfo: cdr.ExtraInfo, UpdatedAt: time.Now()}) if updated.Error != nil { tx.Rollback() diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 819ec9e5e..76219ff45 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -726,23 +726,24 @@ func (self *SQLStorage) SetCdr(cdr *StoredCdr) error { } tx := self.db.Begin() saved := tx.Save(&TblCdrsPrimary{ - Cgrid: cdr.CgrId, - Tor: cdr.TOR, - Accid: cdr.AccId, - Cdrhost: cdr.CdrHost, - Cdrsource: cdr.CdrSource, - Reqtype: cdr.ReqType, - Direction: cdr.Direction, - Tenant: cdr.Tenant, - Category: cdr.Category, - Account: cdr.Account, - Subject: cdr.Subject, - Destination: cdr.Destination, - SetupTime: cdr.SetupTime, - AnswerTime: cdr.AnswerTime, - Usage: cdr.Usage.Seconds(), - Supplier: cdr.Supplier, - CreatedAt: time.Now()}) + Cgrid: cdr.CgrId, + Tor: cdr.TOR, + Accid: cdr.AccId, + Cdrhost: cdr.CdrHost, + Cdrsource: cdr.CdrSource, + Reqtype: cdr.ReqType, + Direction: cdr.Direction, + Tenant: cdr.Tenant, + Category: cdr.Category, + Account: cdr.Account, + Subject: cdr.Subject, + Destination: cdr.Destination, + SetupTime: cdr.SetupTime, + AnswerTime: cdr.AnswerTime, + Usage: cdr.Usage.Seconds(), + Supplier: cdr.Supplier, + DisconnectCause: cdr.DisconnectCause, + CreatedAt: time.Now()}) if saved.Error != nil { tx.Rollback() return saved.Error @@ -765,15 +766,15 @@ func (self *SQLStorage) GetStoredCdrs(qryFltr *utils.CdrsFilter) ([]*StoredCdr, // Select string var selectStr string if qryFltr.FilterOnDerived { // We use different tables to query account data in case of derived - selectStr = fmt.Sprintf("%s.cgrid,%s.id,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.usage,%s.supplier,%s.extra_fields,%s.runid,%s.cost,%s.tor,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.cost,%s.timespans", + selectStr = fmt.Sprintf("%s.cgrid,%s.id,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.usage,%s.supplier,%s.disconnect_cause,%s.extra_fields,%s.runid,%s.cost,%s.tor,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.cost,%s.timespans", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, - utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, + utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS) } else { - selectStr = fmt.Sprintf("%s.cgrid,%s.id,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.usage,%s.supplier,%s.extra_fields,%s.runid,%s.cost,%s.tor,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.cost,%s.timespans", + selectStr = fmt.Sprintf("%s.cgrid,%s.id,%s.tor,%s.accid,%s.cdrhost,%s.cdrsource,%s.reqtype,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.setup_time,%s.answer_time,%s.usage,%s.supplier,%s.disconnect_cause,%s.extra_fields,%s.runid,%s.cost,%s.tor,%s.direction,%s.tenant,%s.category,%s.account,%s.subject,%s.destination,%s.cost,%s.timespans", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, - utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, + utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_RATED_CDRS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS, utils.TBL_COST_DETAILS) @@ -945,6 +946,20 @@ func (self *SQLStorage) GetStoredCdrs(qryFltr *utils.CdrsFilter) ([]*StoredCdr, } q = q.Where(tblName+".supplier not in (?)", qryFltr.NotSubjects) } + if len(qryFltr.DisconnectCauses) != 0 { + tblName := utils.TBL_CDRS_PRIMARY + if qryFltr.FilterOnDerived { + tblName = utils.TBL_RATED_CDRS + } + q = q.Where(tblName+".disconnect_cause in (?)", qryFltr.DisconnectCauses) + } + if len(qryFltr.NotDisconnectCauses) != 0 { + tblName := utils.TBL_CDRS_PRIMARY + if qryFltr.FilterOnDerived { + tblName = utils.TBL_RATED_CDRS + } + q = q.Where(tblName+".disconnect_cause not in (?)", qryFltr.NotDisconnectCauses) + } if len(qryFltr.RatedAccounts) != 0 { q = q.Where(utils.TBL_COST_DETAILS+".account in (?)", qryFltr.RatedAccounts) } @@ -1104,7 +1119,7 @@ func (self *SQLStorage) GetStoredCdrs(qryFltr *utils.CdrsFilter) ([]*StoredCdr, } for rows.Next() { var cgrid, tor, accid, cdrhost, cdrsrc, reqtype, direction, tenant, category, account, subject, destination, runid, ccTor, - ccDirection, ccTenant, ccCategory, ccAccount, ccSubject, ccDestination, ccSupplier sql.NullString + ccDirection, ccTenant, ccCategory, ccAccount, ccSubject, ccDestination, ccSupplier, ccDisconnectCause sql.NullString var extraFields, ccTimespansBytes []byte var setupTime, answerTime mysql.NullTime var orderid int64 @@ -1112,7 +1127,7 @@ func (self *SQLStorage) GetStoredCdrs(qryFltr *utils.CdrsFilter) ([]*StoredCdr, var extraFieldsMp map[string]string var ccTimespans TimeSpans if err := rows.Scan(&cgrid, &orderid, &tor, &accid, &cdrhost, &cdrsrc, &reqtype, &direction, &tenant, &category, &account, &subject, &destination, - &setupTime, &answerTime, &usage, &ccSupplier, + &setupTime, &answerTime, &usage, &ccSupplier, &ccDisconnectCause, &extraFields, &runid, &cost, &ccTor, &ccDirection, &ccTenant, &ccCategory, &ccAccount, &ccSubject, &ccDestination, &ccCost, &ccTimespansBytes); err != nil { return nil, 0, err } @@ -1131,7 +1146,7 @@ func (self *SQLStorage) GetStoredCdrs(qryFltr *utils.CdrsFilter) ([]*StoredCdr, CgrId: cgrid.String, OrderId: orderid, TOR: tor.String, AccId: accid.String, CdrHost: cdrhost.String, CdrSource: cdrsrc.String, ReqType: reqtype.String, Direction: direction.String, Tenant: tenant.String, Category: category.String, Account: account.String, Subject: subject.String, Destination: destination.String, - SetupTime: setupTime.Time, AnswerTime: answerTime.Time, Usage: usageDur, Supplier: ccSupplier.String, + SetupTime: setupTime.Time, AnswerTime: answerTime.Time, Usage: usageDur, Supplier: ccSupplier.String, DisconnectCause: ccDisconnectCause.String, ExtraFields: extraFieldsMp, MediationRunId: runid.String, RatedAccount: ccAccount.String, RatedSubject: ccSubject.String, Cost: cost.Float64, } if ccTimespans != nil { @@ -1471,19 +1486,20 @@ func (self *SQLStorage) GetTpDerivedChargers(dc *utils.TPDerivedChargers) (map[s dcs[tag] = tpDc } dcs[tag].DerivedChargers = append(dcs[tag].DerivedChargers, &utils.TPDerivedCharger{ - RunId: tpDcMdl.Runid, - RunFilters: tpDcMdl.RunFilters, - ReqTypeField: tpDcMdl.ReqTypeField, - DirectionField: tpDcMdl.DirectionField, - TenantField: tpDcMdl.TenantField, - CategoryField: tpDcMdl.CategoryField, - AccountField: tpDcMdl.AccountField, - SubjectField: tpDcMdl.SubjectField, - DestinationField: tpDcMdl.DestinationField, - SetupTimeField: tpDcMdl.SetupTimeField, - AnswerTimeField: tpDcMdl.AnswerTimeField, - UsageField: tpDcMdl.UsageField, - SupplierField: tpDcMdl.SupplierField, + RunId: tpDcMdl.Runid, + RunFilters: tpDcMdl.RunFilters, + ReqTypeField: tpDcMdl.ReqTypeField, + DirectionField: tpDcMdl.DirectionField, + TenantField: tpDcMdl.TenantField, + CategoryField: tpDcMdl.CategoryField, + AccountField: tpDcMdl.AccountField, + SubjectField: tpDcMdl.SubjectField, + DestinationField: tpDcMdl.DestinationField, + SetupTimeField: tpDcMdl.SetupTimeField, + AnswerTimeField: tpDcMdl.AnswerTimeField, + UsageField: tpDcMdl.UsageField, + SupplierField: tpDcMdl.SupplierField, + DisconnectCauseField: tpDcMdl.DisconnectCauseField, }) } return dcs, nil diff --git a/engine/storedcdr.go b/engine/storedcdr.go index 6ffe9eda3..866c037d3 100644 --- a/engine/storedcdr.go +++ b/engine/storedcdr.go @@ -35,8 +35,8 @@ func NewStoredCdrFromExternalCdr(extCdr *ExternalCdr) (*StoredCdr, error) { var err error storedCdr := &StoredCdr{CgrId: extCdr.CgrId, OrderId: extCdr.OrderId, TOR: extCdr.TOR, AccId: extCdr.AccId, CdrHost: extCdr.CdrHost, CdrSource: extCdr.CdrSource, ReqType: extCdr.ReqType, Direction: extCdr.Direction, Tenant: extCdr.Tenant, Category: extCdr.Category, Account: extCdr.Account, Subject: extCdr.Subject, - Destination: extCdr.Destination, Supplier: extCdr.Supplier, ExtraFields: extCdr.ExtraFields, MediationRunId: extCdr.MediationRunId, - RatedAccount: extCdr.RatedAccount, RatedSubject: extCdr.RatedSubject, Cost: extCdr.Cost, Rated: extCdr.Rated} + Destination: extCdr.Destination, Supplier: extCdr.Supplier, DisconnectCause: extCdr.DisconnectCause, ExtraFields: extCdr.ExtraFields, + MediationRunId: extCdr.MediationRunId, RatedAccount: extCdr.RatedAccount, RatedSubject: extCdr.RatedSubject, Cost: extCdr.Cost, Rated: extCdr.Rated} if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(extCdr.SetupTime); err != nil { return nil, err } @@ -56,31 +56,32 @@ func NewStoredCdrFromExternalCdr(extCdr *ExternalCdr) (*StoredCdr, error) { // Kinda standard of internal CDR, complies to CDR interface also type StoredCdr struct { - CgrId string - OrderId int64 // Stor order id used as export order id - TOR string // type of record, meta-field, should map to one of the TORs hardcoded inside the server <*voice|*data|*sms> - AccId string // represents the unique accounting id given by the telecom switch generating the CDR - CdrHost string // represents the IP address of the host generating the CDR (automatically populated by the server) - CdrSource string // formally identifies the source of the CDR (free form field) - ReqType string // matching the supported request types by the **CGRateS**, accepted values are hardcoded in the server . - Direction string // matching the supported direction identifiers of the CGRateS <*out> - Tenant string // tenant whom this record belongs - Category string // free-form filter for this record, matching the category defined in rating profiles. - Account string // account id (accounting subsystem) the record should be attached to - Subject string // rating subject (rating subsystem) this record should be attached to - Destination string // destination to be charged - SetupTime time.Time // set-up time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp. - AnswerTime time.Time // answer time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp. - Usage time.Duration // event usage information (eg: in case of tor=*voice this will represent the total duration of a call) - Supplier string // Supplier information when available - ExtraFields map[string]string // Extra fields to be stored in CDR - MediationRunId string - RatedAccount string // Populated out of rating data - RatedSubject string - Cost float64 - ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation - CostDetails *CallCost // Attach the cost details to CDR when possible - Rated bool // Mark the CDR as rated so we do not process it during mediation + CgrId string + OrderId int64 // Stor order id used as export order id + TOR string // type of record, meta-field, should map to one of the TORs hardcoded inside the server <*voice|*data|*sms> + AccId string // represents the unique accounting id given by the telecom switch generating the CDR + CdrHost string // represents the IP address of the host generating the CDR (automatically populated by the server) + CdrSource string // formally identifies the source of the CDR (free form field) + ReqType string // matching the supported request types by the **CGRateS**, accepted values are hardcoded in the server . + Direction string // matching the supported direction identifiers of the CGRateS <*out> + Tenant string // tenant whom this record belongs + Category string // free-form filter for this record, matching the category defined in rating profiles. + Account string // account id (accounting subsystem) the record should be attached to + Subject string // rating subject (rating subsystem) this record should be attached to + Destination string // destination to be charged + SetupTime time.Time // set-up time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp. + AnswerTime time.Time // answer time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp. + Usage time.Duration // event usage information (eg: in case of tor=*voice this will represent the total duration of a call) + Supplier string // Supplier information when available + DisconnectCause string // Disconnect cause of the event + ExtraFields map[string]string // Extra fields to be stored in CDR + MediationRunId string + RatedAccount string // Populated out of rating data + RatedSubject string + Cost float64 + ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation + CostDetails *CallCost // Attach the cost details to CDR when possible + Rated bool // Mark the CDR as rated so we do not process it during mediation } func (storedCdr *StoredCdr) CostDetailsJson() string { @@ -158,6 +159,8 @@ func (storedCdr *StoredCdr) FieldAsString(rsrFld *utils.RSRField) string { return strconv.FormatFloat(utils.Round(storedCdr.Usage.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64) case utils.SUPPLIER: return rsrFld.ParseValue(storedCdr.Supplier) + case utils.DISCONNECT_CAUSE: + return rsrFld.ParseValue(storedCdr.DisconnectCause) case utils.MEDI_RUNID: return rsrFld.ParseValue(storedCdr.MediationRunId) case utils.RATED_ACCOUNT: @@ -218,6 +221,7 @@ func (storedCdr *StoredCdr) AsHttpForm() url.Values { v.Set(utils.ANSWER_TIME, storedCdr.AnswerTime.Format(time.RFC3339)) v.Set(utils.USAGE, storedCdr.FormatUsage(utils.SECONDS)) v.Set(utils.SUPPLIER, storedCdr.Supplier) + v.Set(utils.DISCONNECT_CAUSE, storedCdr.DisconnectCause) if storedCdr.CostDetails != nil { v.Set(utils.COST_DETAILS, storedCdr.CostDetailsJson()) } @@ -226,7 +230,7 @@ func (storedCdr *StoredCdr) AsHttpForm() url.Values { // Used in mediation, primaryMandatory marks whether missing field out of request represents error or can be ignored func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tenantFld, categFld, accountFld, subjectFld, destFld, setupTimeFld, - answerTimeFld, durationFld, supplierFld *utils.RSRField, + answerTimeFld, durationFld, supplierFld, disconnectCauseFld *utils.RSRField, extraFlds []*utils.RSRField, primaryMandatory bool) (*StoredCdr, error) { if reqTypeFld == nil { reqTypeFld, _ = utils.NewRSRField(utils.META_DEFAULT) @@ -294,6 +298,12 @@ func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tena if supplierFld.Id == utils.META_DEFAULT { supplierFld.Id = utils.SUPPLIER } + if disconnectCauseFld == nil { + disconnectCauseFld, _ = utils.NewRSRField(utils.META_DEFAULT) + } + if disconnectCauseFld.Id == utils.META_DEFAULT { + disconnectCauseFld.Id = utils.DISCONNECT_CAUSE + } var err error frkStorCdr := new(StoredCdr) frkStorCdr.CgrId = storedCdr.CgrId @@ -350,6 +360,7 @@ func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tena return nil, err } frkStorCdr.Supplier = storedCdr.FieldAsString(supplierFld) + frkStorCdr.DisconnectCause = storedCdr.FieldAsString(disconnectCauseFld) frkStorCdr.ExtraFields = make(map[string]string, len(extraFlds)) for _, fld := range extraFlds { frkStorCdr.ExtraFields[fld.Id] = storedCdr.FieldAsString(fld) @@ -359,28 +370,29 @@ func (storedCdr *StoredCdr) ForkCdr(runId string, reqTypeFld, directionFld, tena func (storedCdr *StoredCdr) AsExternalCdr() *ExternalCdr { return &ExternalCdr{CgrId: storedCdr.CgrId, - OrderId: storedCdr.OrderId, - TOR: storedCdr.TOR, - AccId: storedCdr.AccId, - CdrHost: storedCdr.CdrHost, - CdrSource: storedCdr.CdrSource, - ReqType: storedCdr.ReqType, - Direction: storedCdr.Direction, - Tenant: storedCdr.Tenant, - Category: storedCdr.Category, - Account: storedCdr.Account, - Subject: storedCdr.Subject, - Destination: storedCdr.Destination, - SetupTime: storedCdr.SetupTime.Format(time.RFC3339), - AnswerTime: storedCdr.AnswerTime.Format(time.RFC3339), - Usage: storedCdr.FormatUsage(utils.SECONDS), - Supplier: storedCdr.Supplier, - ExtraFields: storedCdr.ExtraFields, - MediationRunId: storedCdr.MediationRunId, - RatedAccount: storedCdr.RatedAccount, - RatedSubject: storedCdr.RatedSubject, - Cost: storedCdr.Cost, - CostDetails: storedCdr.CostDetailsJson(), + OrderId: storedCdr.OrderId, + TOR: storedCdr.TOR, + AccId: storedCdr.AccId, + CdrHost: storedCdr.CdrHost, + CdrSource: storedCdr.CdrSource, + ReqType: storedCdr.ReqType, + Direction: storedCdr.Direction, + Tenant: storedCdr.Tenant, + Category: storedCdr.Category, + Account: storedCdr.Account, + Subject: storedCdr.Subject, + Destination: storedCdr.Destination, + SetupTime: storedCdr.SetupTime.Format(time.RFC3339), + AnswerTime: storedCdr.AnswerTime.Format(time.RFC3339), + Usage: storedCdr.FormatUsage(utils.SECONDS), + Supplier: storedCdr.Supplier, + DisconnectCause: storedCdr.DisconnectCause, + ExtraFields: storedCdr.ExtraFields, + MediationRunId: storedCdr.MediationRunId, + RatedAccount: storedCdr.RatedAccount, + RatedSubject: storedCdr.RatedSubject, + Cost: storedCdr.Cost, + CostDetails: storedCdr.CostDetailsJson(), } } @@ -517,6 +529,12 @@ func (storedCdr *StoredCdr) GetSupplier(fieldName string) string { } return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) } +func (storedCdr *StoredCdr) GetDisconnectCause(fieldName string) string { + if utils.IsSliceMember([]string{utils.DISCONNECT_CAUSE, utils.META_DEFAULT}, fieldName) { + return storedCdr.DisconnectCause + } + return storedCdr.FieldAsString(&utils.RSRField{Id: fieldName}) +} func (storedCdr *StoredCdr) GetOriginatorIP(fieldName string) string { if utils.IsSliceMember([]string{utils.CDRHOST, utils.META_DEFAULT}, fieldName) { return storedCdr.CdrHost @@ -542,30 +560,31 @@ func (storedCdr *StoredCdr) String() string { } type ExternalCdr struct { - CgrId string - OrderId int64 - TOR string - AccId string - CdrHost string - CdrSource string - ReqType string - Direction string - Tenant string - Category string - Account string - Subject string - Destination string - SetupTime string - AnswerTime string - Usage string - Supplier string - ExtraFields map[string]string - MediationRunId string - RatedAccount string - RatedSubject string - Cost float64 - CostDetails string - Rated bool // Mark the CDR as rated so we do not process it during mediation + CgrId string + OrderId int64 + TOR string + AccId string + CdrHost string + CdrSource string + ReqType string + Direction string + Tenant string + Category string + Account string + Subject string + Destination string + SetupTime string + AnswerTime string + Usage string + Supplier string + DisconnectCause string + ExtraFields map[string]string + MediationRunId string + RatedAccount string + RatedSubject string + Cost float64 + CostDetails string + Rated bool // Mark the CDR as rated so we do not process it during mediation } // Used when authorizing requests from outside, eg ApierV1.GetMaxSessionTime diff --git a/engine/storedcdr_test.go b/engine/storedcdr_test.go index b06668d24..bb58cfadb 100644 --- a/engine/storedcdr_test.go +++ b/engine/storedcdr_test.go @@ -342,7 +342,7 @@ func TestStoredCdrForkCdr(t *testing.T) { rtSampleCdrOut, err := storCdr.ForkCdr("sample_run1", &utils.RSRField{Id: utils.REQTYPE}, &utils.RSRField{Id: utils.DIRECTION}, &utils.RSRField{Id: utils.TENANT}, &utils.RSRField{Id: utils.CATEGORY}, &utils.RSRField{Id: utils.ACCOUNT}, &utils.RSRField{Id: utils.SUBJECT}, &utils.RSRField{Id: utils.DESTINATION}, &utils.RSRField{Id: utils.SETUP_TIME}, &utils.RSRField{Id: utils.ANSWER_TIME}, &utils.RSRField{Id: utils.USAGE}, &utils.RSRField{Id: utils.SUPPLIER}, - []*utils.RSRField{&utils.RSRField{Id: "field_extr1"}, &utils.RSRField{Id: "field_extr2"}}, true) + &utils.RSRField{Id: utils.DISCONNECT_CAUSE}, []*utils.RSRField{&utils.RSRField{Id: "field_extr1"}, &utils.RSRField{Id: "field_extr2"}}, true) if err != nil { t.Error("Unexpected error received", err) } @@ -373,15 +373,16 @@ func TestStoredCdrForkCdrStaticVals(t *testing.T) { rsrStAT, _ := utils.NewRSRField("^2013-12-07T08:42:26Z") rsrStDur, _ := utils.NewRSRField("^12s") rsrStSuppl, _ := utils.NewRSRField("^supplier1") + rsrStDCause, _ := utils.NewRSRField("^HANGUP_COMPLETE") rtCdrOut2, err := storCdr.ForkCdr("wholesale_run", rsrStPostpaid, rsrStIn, rsrStCgr, rsrStPC, rsrStFA, rsrStFS, &utils.RSRField{Id: "destination"}, - rsrStST, rsrStAT, rsrStDur, rsrStSuppl, []*utils.RSRField{}, true) + rsrStST, rsrStAT, rsrStDur, rsrStSuppl, rsrStDCause, []*utils.RSRField{}, true) if err != nil { t.Error("Unexpected error received", err) } expctRatedCdr2 := &StoredCdr{CgrId: storCdr.CgrId, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_POSTPAID, Direction: "*in", Tenant: "cgrates.com", Category: "premium_call", Account: "first_account", Subject: "first_subject", Destination: "1002", SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), - AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Usage: time.Duration(12) * time.Second, Supplier: "supplier1", + AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Usage: time.Duration(12) * time.Second, Supplier: "supplier1", DisconnectCause: "HANGUP_COMPLETE", ExtraFields: map[string]string{}, MediationRunId: "wholesale_run", Cost: -1} if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) { t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2) @@ -389,7 +390,7 @@ func TestStoredCdrForkCdrStaticVals(t *testing.T) { _, err = storCdr.ForkCdr("wholesale_run", &utils.RSRField{Id: "dummy_header"}, &utils.RSRField{Id: "direction"}, &utils.RSRField{Id: "tenant"}, &utils.RSRField{Id: "tor"}, &utils.RSRField{Id: "account"}, &utils.RSRField{Id: "subject"}, &utils.RSRField{Id: "destination"}, &utils.RSRField{Id: "setup_time"}, &utils.RSRField{Id: "answer_time"}, &utils.RSRField{Id: "duration"}, &utils.RSRField{Id: utils.SUPPLIER}, - []*utils.RSRField{}, true) + &utils.RSRField{Id: utils.DISCONNECT_CAUSE}, []*utils.RSRField{}, true) if err == nil { t.Error("Failed to detect missing header") } @@ -410,7 +411,7 @@ func TestStoredCdrForkCdrFromMetaDefaults(t *testing.T) { cdrOut, err := storCdr.ForkCdr("wholesale_run", &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, &utils.RSRField{Id: utils.META_DEFAULT}, - []*utils.RSRField{&utils.RSRField{Id: "field_extr1"}, &utils.RSRField{Id: "fieldextr2"}}, true) + &utils.RSRField{Id: utils.META_DEFAULT}, []*utils.RSRField{&utils.RSRField{Id: "field_extr1"}, &utils.RSRField{Id: "fieldextr2"}}, true) if err != nil { t.Fatal("Unexpected error received", err) } @@ -419,7 +420,7 @@ func TestStoredCdrForkCdrFromMetaDefaults(t *testing.T) { t.Errorf("Expected: %v, received: %v", expctCdr, cdrOut) } // Should also accept nil as defaults - if cdrOut, err := storCdr.ForkCdr("wholesale_run", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + if cdrOut, err := storCdr.ForkCdr("wholesale_run", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []*utils.RSRField{&utils.RSRField{Id: "field_extr1"}, &utils.RSRField{Id: "fieldextr2"}}, true); err != nil { t.Fatal("Unexpected error received", err) } else if !reflect.DeepEqual(expctCdr, cdrOut) { diff --git a/sessionmanager/fsevent.go b/sessionmanager/fsevent.go index 27cf69261..6b881e8ee 100644 --- a/sessionmanager/fsevent.go +++ b/sessionmanager/fsevent.go @@ -37,35 +37,37 @@ type FSEvent map[string]string const ( // Freswitch event proprities names - DIRECTION = "Call-Direction" - SUBJECT = "variable_cgr_subject" - ACCOUNT = "variable_cgr_account" - DESTINATION = "variable_cgr_destination" - REQTYPE = "variable_cgr_reqtype" //prepaid or postpaid - Category = "variable_cgr_category" - VAR_CGR_SUPPLIER = "variable_" + utils.CGR_SUPPLIER - UUID = "Unique-ID" // -Unique ID for this call leg - CSTMID = "variable_cgr_tenant" - CALL_DEST_NR = "Caller-Destination-Number" - SIP_REQ_USER = "variable_sip_req_user" - PARK_TIME = "Caller-Profile-Created-Time" - SETUP_TIME = "Caller-Channel-Created-Time" - ANSWER_TIME = "Caller-Channel-Answered-Time" - END_TIME = "Caller-Channel-Hangup-Time" - DURATION = "variable_billsec" - NAME = "Event-Name" - HEARTBEAT = "HEARTBEAT" - ANSWER = "CHANNEL_ANSWER" - HANGUP = "CHANNEL_HANGUP_COMPLETE" - PARK = "CHANNEL_PARK" - AUTH_OK = "+AUTH_OK" - DISCONNECT = "+SWITCH DISCONNECT" - INSUFFICIENT_FUNDS = "-INSUFFICIENT_FUNDS" - MISSING_PARAMETER = "-MISSING_PARAMETER" - SYSTEM_ERROR = "-SYSTEM_ERROR" - MANAGER_REQUEST = "+MANAGER_REQUEST" - USERNAME = "Caller-Username" - FS_IPv4 = "FreeSWITCH-IPv4" + DIRECTION = "Call-Direction" + SUBJECT = "variable_cgr_subject" + ACCOUNT = "variable_cgr_account" + DESTINATION = "variable_cgr_destination" + REQTYPE = "variable_cgr_reqtype" //prepaid or postpaid + Category = "variable_cgr_category" + VAR_CGR_SUPPLIER = "variable_" + utils.CGR_SUPPLIER + UUID = "Unique-ID" // -Unique ID for this call leg + CSTMID = "variable_cgr_tenant" + CALL_DEST_NR = "Caller-Destination-Number" + SIP_REQ_USER = "variable_sip_req_user" + PARK_TIME = "Caller-Profile-Created-Time" + SETUP_TIME = "Caller-Channel-Created-Time" + ANSWER_TIME = "Caller-Channel-Answered-Time" + END_TIME = "Caller-Channel-Hangup-Time" + DURATION = "variable_billsec" + NAME = "Event-Name" + HEARTBEAT = "HEARTBEAT" + ANSWER = "CHANNEL_ANSWER" + HANGUP = "CHANNEL_HANGUP_COMPLETE" + PARK = "CHANNEL_PARK" + AUTH_OK = "+AUTH_OK" + DISCONNECT = "+SWITCH DISCONNECT" + INSUFFICIENT_FUNDS = "-INSUFFICIENT_FUNDS" + MISSING_PARAMETER = "-MISSING_PARAMETER" + SYSTEM_ERROR = "-SYSTEM_ERROR" + MANAGER_REQUEST = "+MANAGER_REQUEST" + USERNAME = "Caller-Username" + FS_IPv4 = "FreeSWITCH-IPv4" + HANGUP_CAUSE = "Hangup-Cause" + VAR_CGR_DISCONNECT_CAUSE = "variable_" + utils.CGR_DISCONNECT_CAUSE ) // Nice printing for the event object. @@ -219,6 +221,13 @@ func (fsev FSEvent) GetSupplier(fieldName string) string { return utils.FirstNonEmpty(fsev[fieldName], fsev[VAR_CGR_SUPPLIER]) } +func (fsev FSEvent) GetDisconnectCause(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(fsev[fieldName], fsev[VAR_CGR_DISCONNECT_CAUSE], fsev[HANGUP_CAUSE]) +} + func (fsev FSEvent) GetOriginatorIP(fieldName string) string { if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value return fieldName[len(utils.STATIC_VALUE_PREFIX):] @@ -274,6 +283,8 @@ func (fsev FSEvent) ParseEventValue(rsrFld *utils.RSRField) string { return rsrFld.ParseValue(strconv.FormatInt(dur.Nanoseconds(), 10)) case utils.SUPPLIER: return rsrFld.ParseValue(fsev.GetSupplier("")) + case utils.DISCONNECT_CAUSE: + return rsrFld.ParseValue(fsev.GetDisconnectCause("")) case utils.MEDI_RUNID: return rsrFld.ParseValue(utils.DEFAULT_RUNID) case utils.COST: @@ -324,6 +335,7 @@ func (fsev FSEvent) AsStoredCdr() *engine.StoredCdr { storCdr.ExtraFields = fsev.GetExtraFields() storCdr.Cost = -1 storCdr.Supplier = fsev.GetSupplier(utils.META_DEFAULT) + storCdr.DisconnectCause = fsev.GetDisconnectCause(utils.META_DEFAULT) return storCdr } diff --git a/sessionmanager/fsevent_test.go b/sessionmanager/fsevent_test.go index d7daf0629..52c193a90 100644 --- a/sessionmanager/fsevent_test.go +++ b/sessionmanager/fsevent_test.go @@ -386,7 +386,8 @@ func TestEventParseStatic(t *testing.T) { setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || dur != time.Duration(60)*time.Second || - ev.GetSupplier("^test") != "test" { + ev.GetSupplier("^test") != "test" || + ev.GetDisconnectCause("^test") != "test" { t.Error("Values out of static not matching", ev.GetReqType("^test") != "test", ev.GetDirection("^test") != "test", @@ -398,7 +399,8 @@ func TestEventParseStatic(t *testing.T) { setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), dur != time.Duration(60)*time.Second, - ev.GetSupplier("^test") != "test") + ev.GetSupplier("^test") != "test", + ev.GetDisconnectCause("^test") != "test") } } @@ -437,7 +439,8 @@ Task-Runtime: 1349437318` setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC) || dur != time.Duration(65)*time.Second || - ev.GetSupplier("FreeSWITCH-Hostname") != "h1.ip-switch.net" { + ev.GetSupplier("FreeSWITCH-Hostname") != "h1.ip-switch.net" || + ev.GetDisconnectCause("FreeSWITCH-Hostname") != "h1.ip-switch.net" { t.Error("Values out of static not matching", ev.GetReqType("FreeSWITCH-Hostname") != "h1.ip-switch.net", ev.GetDirection("FreeSWITCH-Hostname") != "*out", @@ -449,7 +452,8 @@ Task-Runtime: 1349437318` setupTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), answerTime != time.Date(2012, 10, 5, 13, 41, 38, 0, time.UTC), dur != time.Duration(65)*time.Second, - ev.GetSupplier("FreeSWITCH-Hostname") != "h1.ip-switch.net") + ev.GetSupplier("FreeSWITCH-Hostname") != "h1.ip-switch.net", + ev.GetDisconnectCause("FreeSWITCH-Hostname") != "h1.ip-switch.net") } } @@ -493,7 +497,8 @@ func TestParseFsHangup(t *testing.T) { setupTime.UTC() != time.Date(2014, 4, 25, 16, 8, 27, 0, time.UTC) || answerTime.UTC() != time.Date(2014, 4, 25, 16, 8, 40, 0, time.UTC) || dur != time.Duration(5)*time.Second || - ev.GetSupplier(utils.META_DEFAULT) != "supplier1" { + ev.GetSupplier(utils.META_DEFAULT) != "supplier1" || + ev.GetDisconnectCause(utils.META_DEFAULT) != "NORMAL_CLEARING" { t.Error("Default values not matching", ev.GetReqType(utils.META_DEFAULT) != utils.META_PSEUDOPREPAID, ev.GetDirection(utils.META_DEFAULT) != "*out", @@ -505,7 +510,8 @@ func TestParseFsHangup(t *testing.T) { setupTime.UTC() != time.Date(2014, 4, 25, 17, 8, 27, 0, time.UTC), answerTime.UTC() != time.Date(2014, 4, 25, 17, 8, 40, 0, time.UTC), dur != time.Duration(5)*time.Second, - ev.GetSupplier(utils.META_DEFAULT) != "supplier1") + ev.GetSupplier(utils.META_DEFAULT) != "supplier1", + ev.GetDisconnectCause(utils.META_DEFAULT) != "NORMAL_CLEARING") } } @@ -633,7 +639,7 @@ func TestFsEvAsStoredCdr(t *testing.T) { TOR: utils.VOICE, AccId: "37e9b766-5256-4e4b-b1ed-3767b930fec8", CdrHost: "10.0.2.15", CdrSource: "FS_CHANNEL_HANGUP_COMPLETE", ReqType: utils.META_PSEUDOPREPAID, Direction: utils.OUT, Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1002", SetupTime: setupTime, AnswerTime: aTime, - Usage: time.Duration(5) * time.Second, Supplier: "supplier1", ExtraFields: make(map[string]string), Cost: -1} + Usage: time.Duration(5) * time.Second, Supplier: "supplier1", DisconnectCause: "NORMAL_CLEARING", ExtraFields: make(map[string]string), Cost: -1} if storedCdr := ev.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } diff --git a/sessionmanager/kamevent.go b/sessionmanager/kamevent.go index b78c0f1a1..acb6478f3 100644 --- a/sessionmanager/kamevent.go +++ b/sessionmanager/kamevent.go @@ -178,6 +178,13 @@ func (kev KamEvent) GetSupplier(fieldName string) string { return utils.FirstNonEmpty(kev[fieldName], kev[utils.CGR_SUPPLIER]) } +func (kev KamEvent) GetDisconnectCause(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(kev[fieldName], kev[utils.CGR_DISCONNECT_CAUSE]) +} + //ToDo: extract the IP of the kamailio server generating the event func (kev KamEvent) GetOriginatorIP(string) string { return "127.0.0.1" @@ -261,6 +268,8 @@ func (kev KamEvent) ParseEventValue(rsrFld *utils.RSRField) string { return rsrFld.ParseValue(strconv.FormatFloat(utils.Round(duration.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64)) case utils.SUPPLIER: return rsrFld.ParseValue(kev.GetSupplier(utils.META_DEFAULT)) + case utils.DISCONNECT_CAUSE: + return rsrFld.ParseValue(kev.GetDisconnectCause(utils.META_DEFAULT)) case utils.MEDI_RUNID: return rsrFld.ParseValue(utils.META_DEFAULT) case utils.COST: diff --git a/sessionmanager/kamevent_test.go b/sessionmanager/kamevent_test.go index c5223b3bd..619e6ba7d 100644 --- a/sessionmanager/kamevent_test.go +++ b/sessionmanager/kamevent_test.go @@ -28,7 +28,7 @@ import ( var kamEv = KamEvent{KAM_TR_INDEX: "29223", KAM_TR_LABEL: "698469260", "callid": "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", "from_tag": "eb082607", "to_tag": "4ea9687f", "cgr_account": "dan", "cgr_reqtype": utils.META_PREPAID, "cgr_subject": "dan", "cgr_destination": "+4986517174963", "cgr_tenant": "itsyscom.com", - "cgr_duration": "20", utils.CGR_SUPPLIER: "suppl2", "extra1": "val1", "extra2": "val2"} + "cgr_duration": "20", utils.CGR_SUPPLIER: "suppl2", utils.CGR_DISCONNECT_CAUSE: "200", "extra1": "val1", "extra2": "val2"} func TestKamailioEventInterface(t *testing.T) { var _ engine.Event = engine.Event(kamEv) @@ -44,9 +44,11 @@ func TestNewKamEvent(t *testing.T) { "cgr_destination":"1002", "cgr_answertime":"1419839310", "cgr_duration":"3", - "cgr_supplier":"supplier2"}` + "cgr_supplier":"supplier2", + "cgr_disconnect_cause": "200"}` eKamEv := KamEvent{"event": "CGR_CALL_END", "callid": "46c01a5c249b469e76333fc6bfa87f6a@0:0:0:0:0:0:0:0", "from_tag": "bf71ad59", "to_tag": "7351fecf", - "cgr_reqtype": utils.META_POSTPAID, "cgr_account": "1001", "cgr_destination": "1002", "cgr_answertime": "1419839310", "cgr_duration": "3", utils.CGR_SUPPLIER: "supplier2"} + "cgr_reqtype": utils.META_POSTPAID, "cgr_account": "1001", "cgr_destination": "1002", "cgr_answertime": "1419839310", "cgr_duration": "3", utils.CGR_SUPPLIER: "supplier2", + utils.CGR_DISCONNECT_CAUSE: "200"} if kamEv, err := NewKamEvent([]byte(evStr)); err != nil { t.Error(err) } else if !reflect.DeepEqual(eKamEv, kamEv) { diff --git a/sessionmanager/osipsevent.go b/sessionmanager/osipsevent.go index 97978f580..e5352dfcb 100644 --- a/sessionmanager/osipsevent.go +++ b/sessionmanager/osipsevent.go @@ -51,6 +51,7 @@ const ( OSIPS_AUTH_OK = "AUTH_OK" OSIPS_INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS" OSIPS_DIALOG_ID = "dialog_id" + OSIPS_SIPCODE = "sip_code" ) func NewOsipsEvent(osipsDagramEvent *osipsdagram.OsipsEvent) (*OsipsEvent, error) { @@ -180,6 +181,12 @@ func (osipsev *OsipsEvent) GetSupplier(fieldName string) string { } return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[utils.CGR_SUPPLIER]) } +func (osipsev *OsipsEvent) GetDisconnectCause(fieldName string) string { + if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value + return fieldName[len(utils.STATIC_VALUE_PREFIX):] + } + return utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS_SIPCODE], osipsev.osipsEvent.AttrValues[utils.DISCONNECT_CAUSE]) +} func (osipsEv *OsipsEvent) GetOriginatorIP(fieldName string) string { if osipsEv.osipsEvent == nil || osipsEv.osipsEvent.OriginatorAddress == nil { return "" @@ -261,6 +268,7 @@ func (osipsEv *OsipsEvent) AsStoredCdr() *engine.StoredCdr { storCdr.AnswerTime, _ = osipsEv.GetAnswerTime(utils.META_DEFAULT) storCdr.Usage, _ = osipsEv.GetDuration(utils.META_DEFAULT) storCdr.Supplier = osipsEv.GetSupplier(utils.META_DEFAULT) + storCdr.DisconnectCause = osipsEv.GetDisconnectCause(utils.META_DEFAULT) storCdr.ExtraFields = osipsEv.GetExtraFields() storCdr.Cost = -1 return storCdr @@ -275,5 +283,6 @@ func (osipsEv *OsipsEvent) updateDurationFromEvent(updatedOsipsEv *OsipsEvent) e answerTime, err := osipsEv.GetAnswerTime(utils.META_DEFAULT) osipsEv.osipsEvent.AttrValues[OSIPS_DURATION] = endTime.Sub(answerTime).String() osipsEv.osipsEvent.AttrValues["method"] = "UPDATE" // So we can know it is an end event + osipsEv.osipsEvent.AttrValues[OSIPS_SIPCODE] = updatedOsipsEv.osipsEvent.AttrValues[OSIPS_SIPCODE] return nil } diff --git a/sessionmanager/osipsevent_test.go b/sessionmanager/osipsevent_test.go index 8c48f9f76..daa097752 100644 --- a/sessionmanager/osipsevent_test.go +++ b/sessionmanager/osipsevent_test.go @@ -54,7 +54,8 @@ func TestOsipsEventParseStatic(t *testing.T) { setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC) || dur != time.Duration(60)*time.Second || - osipsEv.GetSupplier("^test") != "test" { + osipsEv.GetSupplier("^test") != "test" || + osipsEv.GetDisconnectCause("^test") != "test" { t.Error("Values out of static not matching", osipsEv.GetReqType("^test") != "test", osipsEv.GetDirection("^test") != "test", @@ -66,7 +67,8 @@ func TestOsipsEventParseStatic(t *testing.T) { setupTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), answerTime != time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), dur != time.Duration(60)*time.Second, - osipsEv.GetSupplier("^test") != "test") + osipsEv.GetSupplier("^test") != "test", + osipsEv.GetDisconnectCause("^test") != "test") } } @@ -95,6 +97,7 @@ func TestOsipsEventGetValues(t *testing.T) { !endTime.Equal(eAnswerTime.Add(dur)) || dur != time.Duration(20*time.Second) || osipsEv.GetSupplier(utils.META_DEFAULT) != "supplier3" || + osipsEv.GetDisconnectCause(utils.META_DEFAULT) != "200" || osipsEv.GetOriginatorIP(utils.META_DEFAULT) != "172.16.254.77" { t.Error("GetValues not matching: ", osipsEv.GetName() != "E_ACC_CDR", osipsEv.GetCgrId() != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", setupTime.UTC().String()), @@ -112,6 +115,7 @@ func TestOsipsEventGetValues(t *testing.T) { !endTime.Equal(time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local)), dur != time.Duration(20*time.Second), osipsEv.GetSupplier(utils.META_DEFAULT) != "supplier3", + osipsEv.GetDisconnectCause(utils.META_DEFAULT) != "200", osipsEv.GetOriginatorIP(utils.META_DEFAULT) != "172.16.254.77", ) } @@ -138,7 +142,7 @@ func TestOsipsEventAsStoredCdr(t *testing.T) { ReqType: utils.META_PREPAID, Direction: utils.OUT, Tenant: "itsyscom.com", Category: "call", Account: "dan", Subject: "dan", Destination: "+4986517174963", SetupTime: setupTime, AnswerTime: answerTime, - Usage: time.Duration(20) * time.Second, Supplier: "supplier3", ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} + Usage: time.Duration(20) * time.Second, Supplier: "supplier3", DisconnectCause: "200", ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} if storedCdr := osipsEv.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } @@ -155,7 +159,7 @@ func TestOsipsAccMissedToStoredCdr(t *testing.T) { eStoredCdr := &engine.StoredCdr{CgrId: utils.Sha1("27b1e6679ad0109b5d756e42bb4c9c28@0:0:0:0:0:0:0:0", setupTime.UTC().String()), TOR: utils.VOICE, AccId: "27b1e6679ad0109b5d756e42bb4c9c28@0:0:0:0:0:0:0:0", CdrHost: "172.16.254.77", CdrSource: "OSIPS_E_ACC_MISSED_EVENT", ReqType: utils.META_PSEUDOPREPAID, Direction: utils.OUT, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Supplier: "supplier1", - Destination: "1002", SetupTime: setupTime, AnswerTime: setupTime, + DisconnectCause: "404", Destination: "1002", SetupTime: setupTime, AnswerTime: setupTime, Usage: time.Duration(0), ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} if storedCdr := osipsEv.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 8c50bef0d..23636b0d0 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -442,7 +442,7 @@ func (self *TPDerivedChargers) AsExportSlice() [][]string { for idx, dc := range self.DerivedChargers { retSlice[idx] = []string{self.Direction, self.Tenant, self.Category, self.Account, self.Subject, dc.RunId, dc.RunFilters, dc.ReqTypeField, dc.DirectionField, dc.TenantField, dc.CategoryField, dc.AccountField, dc.SubjectField, dc.DestinationField, dc.SetupTimeField, dc.AnswerTimeField, - dc.UsageField, dc.SupplierField} + dc.UsageField, dc.SupplierField, dc.DisconnectCauseField} } return retSlice } @@ -482,19 +482,20 @@ func (tpdc *TPDerivedChargers) SetDerivedChargersId(id string) error { } type TPDerivedCharger struct { - RunId string - RunFilters string - ReqTypeField string - DirectionField string - TenantField string - CategoryField string - AccountField string - SubjectField string - DestinationField string - SetupTimeField string - AnswerTimeField string - UsageField string - SupplierField string + RunId string + RunFilters string + ReqTypeField string + DirectionField string + TenantField string + CategoryField string + AccountField string + SubjectField string + DestinationField string + SetupTimeField string + AnswerTimeField string + UsageField string + SupplierField string + DisconnectCauseField string } type TPActionPlan struct { @@ -905,157 +906,163 @@ type ExportedTPStats struct { // Filter used in engine.GetStoredCdrs type CdrsFilter struct { - CgrIds []string // If provided, it will filter based on the cgrids present in list - NotCgrIds []string // Filter specific CgrIds out - RunIds []string // If provided, it will filter on mediation runid - NotRunIds []string // Filter specific runIds out - Tors []string // If provided, filter on TypeOfRecord - NotTors []string // Filter specific TORs out - CdrHosts []string // If provided, it will filter cdrhost - NotCdrHosts []string // Filter out specific cdr hosts - CdrSources []string // If provided, it will filter cdrsource - NotCdrSources []string // Filter out specific CDR sources - ReqTypes []string // If provided, it will fiter reqtype - NotReqTypes []string // Filter out specific request types - Directions []string // If provided, it will fiter direction - NotDirections []string // Filter out specific directions - Tenants []string // If provided, it will filter tenant - NotTenants []string // If provided, it will filter tenant - Categories []string // If provided, it will filter çategory - NotCategories []string // Filter out specific categories - Accounts []string // If provided, it will filter account - NotAccounts []string // Filter out specific Accounts - Subjects []string // If provided, it will filter the rating subject - NotSubjects []string // Filter out specific subjects - DestPrefixes []string // If provided, it will filter on destination prefix - NotDestPrefixes []string // Filter out specific destination prefixes - Suppliers []string // If provided, it will filter the supplier - NotSuppliers []string // Filter out specific suppliers - RatedAccounts []string // If provided, it will filter ratedaccount - NotRatedAccounts []string // Filter out specific RatedAccounts - RatedSubjects []string // If provided, it will filter the ratedsubject - NotRatedSubjects []string // Filter out specific RatedSubjects - Costs []float64 // Query based on costs specified - NotCosts []float64 // Filter out specific costs out from result - ExtraFields map[string]string // Query based on extra fields content - NotExtraFields map[string]string // Filter out based on extra fields content - OrderIdStart int64 // Export from this order identifier - OrderIdEnd int64 // Export smaller than this order identifier - SetupTimeStart *time.Time // Start of interval, bigger or equal than configured - SetupTimeEnd *time.Time // End interval, smaller than setupTime - AnswerTimeStart *time.Time // Start of interval, bigger or equal than configured - AnswerTimeEnd *time.Time // End interval, smaller than answerTime - CreatedAtStart *time.Time // Start of interval, bigger or equal than configured - CreatedAtEnd *time.Time // End interval, smaller than - UpdatedAtStart *time.Time // Start of interval, bigger or equal than configured - UpdatedAtEnd *time.Time // End interval, smaller than - UsageStart *float64 // Start of the usage interval (>=) - UsageEnd *float64 // End of the usage interval (<) - CostStart *float64 // Start of the cost interval (>=) - CostEnd *float64 // End of the usage interval (<) - FilterOnDerived bool // Do not consider derived CDRs but original one - Count bool // If true count the items instead of returning data + CgrIds []string // If provided, it will filter based on the cgrids present in list + NotCgrIds []string // Filter specific CgrIds out + RunIds []string // If provided, it will filter on mediation runid + NotRunIds []string // Filter specific runIds out + Tors []string // If provided, filter on TypeOfRecord + NotTors []string // Filter specific TORs out + CdrHosts []string // If provided, it will filter cdrhost + NotCdrHosts []string // Filter out specific cdr hosts + CdrSources []string // If provided, it will filter cdrsource + NotCdrSources []string // Filter out specific CDR sources + ReqTypes []string // If provided, it will fiter reqtype + NotReqTypes []string // Filter out specific request types + Directions []string // If provided, it will fiter direction + NotDirections []string // Filter out specific directions + Tenants []string // If provided, it will filter tenant + NotTenants []string // If provided, it will filter tenant + Categories []string // If provided, it will filter çategory + NotCategories []string // Filter out specific categories + Accounts []string // If provided, it will filter account + NotAccounts []string // Filter out specific Accounts + Subjects []string // If provided, it will filter the rating subject + NotSubjects []string // Filter out specific subjects + DestPrefixes []string // If provided, it will filter on destination prefix + NotDestPrefixes []string // Filter out specific destination prefixes + Suppliers []string // If provided, it will filter the supplier + NotSuppliers []string // Filter out specific suppliers + DisconnectCauses []string // Filter for disconnect Cause + NotDisconnectCauses []string // Filter out specific disconnect causes + RatedAccounts []string // If provided, it will filter ratedaccount + NotRatedAccounts []string // Filter out specific RatedAccounts + RatedSubjects []string // If provided, it will filter the ratedsubject + NotRatedSubjects []string // Filter out specific RatedSubjects + Costs []float64 // Query based on costs specified + NotCosts []float64 // Filter out specific costs out from result + ExtraFields map[string]string // Query based on extra fields content + NotExtraFields map[string]string // Filter out based on extra fields content + OrderIdStart int64 // Export from this order identifier + OrderIdEnd int64 // Export smaller than this order identifier + SetupTimeStart *time.Time // Start of interval, bigger or equal than configured + SetupTimeEnd *time.Time // End interval, smaller than setupTime + AnswerTimeStart *time.Time // Start of interval, bigger or equal than configured + AnswerTimeEnd *time.Time // End interval, smaller than answerTime + CreatedAtStart *time.Time // Start of interval, bigger or equal than configured + CreatedAtEnd *time.Time // End interval, smaller than + UpdatedAtStart *time.Time // Start of interval, bigger or equal than configured + UpdatedAtEnd *time.Time // End interval, smaller than + UsageStart *float64 // Start of the usage interval (>=) + UsageEnd *float64 // End of the usage interval (<) + CostStart *float64 // Start of the cost interval (>=) + CostEnd *float64 // End of the usage interval (<) + FilterOnDerived bool // Do not consider derived CDRs but original one + Count bool // If true count the items instead of returning data Paginator } // Used in Rpc calls, slightly different than CdrsFilter by using string instead of Time filters type RpcCdrsFilter struct { - CgrIds []string // If provided, it will filter based on the cgrids present in list - NotCgrIds []string // Filter specific CgrIds out - RunIds []string // If provided, it will filter on mediation runid - NotRunIds []string // Filter specific runIds out - Tors []string // If provided, filter on TypeOfRecord - NotTors []string // Filter specific TORs out - CdrHosts []string // If provided, it will filter cdrhost - NotCdrHosts []string // Filter out specific cdr hosts - CdrSources []string // If provided, it will filter cdrsource - NotCdrSources []string // Filter out specific CDR sources - ReqTypes []string // If provided, it will fiter reqtype - NotReqTypes []string // Filter out specific request types - Directions []string // If provided, it will fiter direction - NotDirections []string // Filter out specific directions - Tenants []string // If provided, it will filter tenant - NotTenants []string // If provided, it will filter tenant - Categories []string // If provided, it will filter çategory - NotCategories []string // Filter out specific categories - Accounts []string // If provided, it will filter account - NotAccounts []string // Filter out specific Accounts - Subjects []string // If provided, it will filter the rating subject - NotSubjects []string // Filter out specific subjects - DestPrefixes []string // If provided, it will filter on destination prefix - NotDestPrefixes []string // Filter out specific destination prefixes - Suppliers []string // If provided, it will filter the supplier - NotSuppliers []string // Filter out specific suppliers - RatedAccounts []string // If provided, it will filter ratedaccount - NotRatedAccounts []string // Filter out specific RatedAccounts - RatedSubjects []string // If provided, it will filter the ratedsubject - NotRatedSubjects []string // Filter out specific RatedSubjects - Costs []float64 // Query based on costs specified - NotCosts []float64 // Filter out specific costs out from result - ExtraFields map[string]string // Query based on extra fields content - NotExtraFields map[string]string // Filter out based on extra fields content - OrderIdStart int64 // Export from this order identifier - OrderIdEnd int64 // Export smaller than this order identifier - SetupTimeStart string // Start of interval, bigger or equal than configured - SetupTimeEnd string // End interval, smaller than setupTime - AnswerTimeStart string // Start of interval, bigger or equal than configured - AnswerTimeEnd string // End interval, smaller than answerTime - CreatedAtStart string // Start of interval, bigger or equal than configured - CreatedAtEnd string // End interval, smaller than - UpdatedAtStart string // Start of interval, bigger or equal than configured - UpdatedAtEnd string // End interval, smaller than - UsageStart *float64 // Start of the usage interval (>=) - UsageEnd *float64 // End of the usage interval (<) - CostStart *float64 // Start of the cost interval (>=) - CostEnd *float64 // End of the usage interval (<) - FilterOnDerived bool // Do not consider derived CDRs but original one - Paginator // Add pagination + CgrIds []string // If provided, it will filter based on the cgrids present in list + NotCgrIds []string // Filter specific CgrIds out + RunIds []string // If provided, it will filter on mediation runid + NotRunIds []string // Filter specific runIds out + Tors []string // If provided, filter on TypeOfRecord + NotTors []string // Filter specific TORs out + CdrHosts []string // If provided, it will filter cdrhost + NotCdrHosts []string // Filter out specific cdr hosts + CdrSources []string // If provided, it will filter cdrsource + NotCdrSources []string // Filter out specific CDR sources + ReqTypes []string // If provided, it will fiter reqtype + NotReqTypes []string // Filter out specific request types + Directions []string // If provided, it will fiter direction + NotDirections []string // Filter out specific directions + Tenants []string // If provided, it will filter tenant + NotTenants []string // If provided, it will filter tenant + Categories []string // If provided, it will filter çategory + NotCategories []string // Filter out specific categories + Accounts []string // If provided, it will filter account + NotAccounts []string // Filter out specific Accounts + Subjects []string // If provided, it will filter the rating subject + NotSubjects []string // Filter out specific subjects + DestPrefixes []string // If provided, it will filter on destination prefix + NotDestPrefixes []string // Filter out specific destination prefixes + Suppliers []string // If provided, it will filter the supplier + NotSuppliers []string // Filter out specific suppliers + DisconnectCauses []string // Filter for disconnect Cause + NotDisconnectCauses []string // Filter out specific disconnect causes + RatedAccounts []string // If provided, it will filter ratedaccount + NotRatedAccounts []string // Filter out specific RatedAccounts + RatedSubjects []string // If provided, it will filter the ratedsubject + NotRatedSubjects []string // Filter out specific RatedSubjects + Costs []float64 // Query based on costs specified + NotCosts []float64 // Filter out specific costs out from result + ExtraFields map[string]string // Query based on extra fields content + NotExtraFields map[string]string // Filter out based on extra fields content + OrderIdStart int64 // Export from this order identifier + OrderIdEnd int64 // Export smaller than this order identifier + SetupTimeStart string // Start of interval, bigger or equal than configured + SetupTimeEnd string // End interval, smaller than setupTime + AnswerTimeStart string // Start of interval, bigger or equal than configured + AnswerTimeEnd string // End interval, smaller than answerTime + CreatedAtStart string // Start of interval, bigger or equal than configured + CreatedAtEnd string // End interval, smaller than + UpdatedAtStart string // Start of interval, bigger or equal than configured + UpdatedAtEnd string // End interval, smaller than + UsageStart *float64 // Start of the usage interval (>=) + UsageEnd *float64 // End of the usage interval (<) + CostStart *float64 // Start of the cost interval (>=) + CostEnd *float64 // End of the usage interval (<) + FilterOnDerived bool // Do not consider derived CDRs but original one + Paginator // Add pagination } func (self *RpcCdrsFilter) AsCdrsFilter() (*CdrsFilter, error) { cdrFltr := &CdrsFilter{ - CgrIds: self.CgrIds, - NotCgrIds: self.NotCgrIds, - RunIds: self.RunIds, - NotRunIds: self.NotRunIds, - Tors: self.Tors, - NotTors: self.NotTors, - CdrHosts: self.CdrHosts, - NotCdrHosts: self.NotCdrHosts, - CdrSources: self.CdrSources, - NotCdrSources: self.NotCdrSources, - ReqTypes: self.ReqTypes, - NotReqTypes: self.NotReqTypes, - Directions: self.Directions, - NotDirections: self.NotDirections, - Tenants: self.Tenants, - NotTenants: self.NotTenants, - Categories: self.Categories, - NotCategories: self.NotCategories, - Accounts: self.Accounts, - NotAccounts: self.NotAccounts, - Subjects: self.Subjects, - NotSubjects: self.NotSubjects, - DestPrefixes: self.DestPrefixes, - NotDestPrefixes: self.NotDestPrefixes, - Suppliers: self.Suppliers, - NotSuppliers: self.NotSuppliers, - RatedAccounts: self.RatedAccounts, - NotRatedAccounts: self.NotRatedAccounts, - RatedSubjects: self.RatedSubjects, - NotRatedSubjects: self.NotRatedSubjects, - Costs: self.Costs, - NotCosts: self.NotCosts, - ExtraFields: self.ExtraFields, - NotExtraFields: self.NotExtraFields, - OrderIdStart: self.OrderIdStart, - OrderIdEnd: self.OrderIdEnd, - UsageStart: self.UsageStart, - UsageEnd: self.UsageEnd, - CostStart: self.CostStart, - CostEnd: self.CostEnd, - FilterOnDerived: self.FilterOnDerived, - Paginator: self.Paginator, + CgrIds: self.CgrIds, + NotCgrIds: self.NotCgrIds, + RunIds: self.RunIds, + NotRunIds: self.NotRunIds, + Tors: self.Tors, + NotTors: self.NotTors, + CdrHosts: self.CdrHosts, + NotCdrHosts: self.NotCdrHosts, + CdrSources: self.CdrSources, + NotCdrSources: self.NotCdrSources, + ReqTypes: self.ReqTypes, + NotReqTypes: self.NotReqTypes, + Directions: self.Directions, + NotDirections: self.NotDirections, + Tenants: self.Tenants, + NotTenants: self.NotTenants, + Categories: self.Categories, + NotCategories: self.NotCategories, + Accounts: self.Accounts, + NotAccounts: self.NotAccounts, + Subjects: self.Subjects, + NotSubjects: self.NotSubjects, + DestPrefixes: self.DestPrefixes, + NotDestPrefixes: self.NotDestPrefixes, + Suppliers: self.Suppliers, + NotSuppliers: self.NotSuppliers, + DisconnectCauses: self.DisconnectCauses, + NotDisconnectCauses: self.NotDisconnectCauses, + RatedAccounts: self.RatedAccounts, + NotRatedAccounts: self.NotRatedAccounts, + RatedSubjects: self.RatedSubjects, + NotRatedSubjects: self.NotRatedSubjects, + Costs: self.Costs, + NotCosts: self.NotCosts, + ExtraFields: self.ExtraFields, + NotExtraFields: self.NotExtraFields, + OrderIdStart: self.OrderIdStart, + OrderIdEnd: self.OrderIdEnd, + UsageStart: self.UsageStart, + UsageEnd: self.UsageEnd, + CostStart: self.CostStart, + CostEnd: self.CostEnd, + FilterOnDerived: self.FilterOnDerived, + Paginator: self.Paginator, } if len(self.SetupTimeStart) != 0 { if sTimeStart, err := ParseTimeDetectLayout(self.SetupTimeStart); err != nil { diff --git a/utils/apitpdata_test.go b/utils/apitpdata_test.go index 8d6b2a27a..3828a9ac9 100644 --- a/utils/apitpdata_test.go +++ b/utils/apitpdata_test.go @@ -343,42 +343,44 @@ func TestTPDerivedChargersAsExportSlice(t *testing.T) { Subject: "1001", DerivedChargers: []*TPDerivedCharger{ &TPDerivedCharger{ - RunId: "derived_run1", - RunFilters: "", - ReqTypeField: "^rated", - DirectionField: META_DEFAULT, - TenantField: META_DEFAULT, - CategoryField: META_DEFAULT, - AccountField: META_DEFAULT, - SubjectField: "^1002", - DestinationField: META_DEFAULT, - SetupTimeField: META_DEFAULT, - AnswerTimeField: META_DEFAULT, - UsageField: META_DEFAULT, - SupplierField: META_DEFAULT, + RunId: "derived_run1", + RunFilters: "", + ReqTypeField: "^rated", + DirectionField: META_DEFAULT, + TenantField: META_DEFAULT, + CategoryField: META_DEFAULT, + AccountField: META_DEFAULT, + SubjectField: "^1002", + DestinationField: META_DEFAULT, + SetupTimeField: META_DEFAULT, + AnswerTimeField: META_DEFAULT, + UsageField: META_DEFAULT, + SupplierField: META_DEFAULT, + DisconnectCauseField: META_DEFAULT, }, &TPDerivedCharger{ - RunId: "derived_run2", - RunFilters: "", - ReqTypeField: "^rated", - DirectionField: META_DEFAULT, - TenantField: META_DEFAULT, - CategoryField: META_DEFAULT, - AccountField: "^1002", - SubjectField: META_DEFAULT, - DestinationField: META_DEFAULT, - SetupTimeField: META_DEFAULT, - AnswerTimeField: META_DEFAULT, - UsageField: META_DEFAULT, - SupplierField: META_DEFAULT, + RunId: "derived_run2", + RunFilters: "", + ReqTypeField: "^rated", + DirectionField: META_DEFAULT, + TenantField: META_DEFAULT, + CategoryField: META_DEFAULT, + AccountField: "^1002", + SubjectField: META_DEFAULT, + DestinationField: META_DEFAULT, + SetupTimeField: META_DEFAULT, + AnswerTimeField: META_DEFAULT, + UsageField: META_DEFAULT, + SupplierField: META_DEFAULT, + DisconnectCauseField: META_DEFAULT, }, }, } expectedSlc := [][]string{ []string{"*out", "cgrates.org", "call", "1001", "1001", - "derived_run1", "", "^rated", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, "^1002", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT}, + "derived_run1", "", "^rated", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, "^1002", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT}, []string{"*out", "cgrates.org", "call", "1001", "1001", - "derived_run2", "", "^rated", META_DEFAULT, META_DEFAULT, META_DEFAULT, "^1002", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT}, + "derived_run2", "", "^rated", META_DEFAULT, META_DEFAULT, META_DEFAULT, "^1002", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT}, } if slc := dcs.AsExportSlice(); !reflect.DeepEqual(expectedSlc, slc) { t.Errorf("Expecting: %+v, received: %+v", expectedSlc, slc) diff --git a/utils/consts.go b/utils/consts.go index 902e802ad..28b05484a 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -72,7 +72,7 @@ const ( ACTION_PLANS_NRCOLS = 4 ACTION_TRIGGERS_NRCOLS = 19 ACCOUNT_ACTIONS_NRCOLS = 5 - DERIVED_CHARGERS_NRCOLS = 18 + DERIVED_CHARGERS_NRCOLS = 19 CDR_STATS_NRCOLS = 22 ROUNDING_UP = "*up" ROUNDING_MIDDLE = "*middle" @@ -184,6 +184,8 @@ const ( CGR_AUTHORIZE = "CGR_AUTHORIZE" CONFIG_DIR = "/etc/cgrates" CGR_SUPPLIER = "cgr_supplier" + DISCONNECT_CAUSE = "disconnect_cause" + CGR_DISCONNECT_CAUSE = "cgr_disconnect_cause" ) var ( diff --git a/utils/derivedchargers.go b/utils/derivedchargers.go index dbcfad7d7..275e0d92c 100644 --- a/utils/derivedchargers.go +++ b/utils/derivedchargers.go @@ -24,7 +24,7 @@ import ( ) // Wraps regexp compiling in case of rsr fields -func NewDerivedCharger(runId, runFilters, reqTypeFld, dirFld, tenantFld, catFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld, supplFld string) (dc *DerivedCharger, err error) { +func NewDerivedCharger(runId, runFilters, reqTypeFld, dirFld, tenantFld, catFld, acntFld, subjFld, dstFld, sTimeFld, aTimeFld, durFld, supplFld, dCauseFld string) (dc *DerivedCharger, err error) { if len(runId) == 0 { return nil, errors.New("Empty run id field") } @@ -101,35 +101,43 @@ func NewDerivedCharger(runId, runFilters, reqTypeFld, dirFld, tenantFld, catFld, return nil, err } } + dc.DisconnectCauseField = dCauseFld + if strings.HasPrefix(dc.DisconnectCauseField, REGEXP_PREFIX) || strings.HasPrefix(dc.DisconnectCauseField, STATIC_VALUE_PREFIX) { + if dc.rsrDisconnectCauseField, err = NewRSRField(dc.DisconnectCauseField); err != nil { + return nil, err + } + } return dc, nil } type DerivedCharger struct { - RunId string // Unique runId in the chain - RunFilters string // Only run the charger if all the filters match - ReqTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values - DirectionField string // Field containing direction info - TenantField string // Field containing tenant info - CategoryField string // Field containing tor info - AccountField string // Field containing account information - SubjectField string // Field containing subject information - DestinationField string // Field containing destination information - SetupTimeField string // Field containing setup time information - AnswerTimeField string // Field containing answer time information - UsageField string // Field containing usage information - SupplierField string // Field containing supplier information - rsrRunFilters []*RSRField // Storage for compiled Regexp in case of RSRFields - rsrReqTypeField *RSRField - rsrDirectionField *RSRField - rsrTenantField *RSRField - rsrCategoryField *RSRField - rsrAccountField *RSRField - rsrSubjectField *RSRField - rsrDestinationField *RSRField - rsrSetupTimeField *RSRField - rsrAnswerTimeField *RSRField - rsrUsageField *RSRField - rsrSupplierField *RSRField + RunId string // Unique runId in the chain + RunFilters string // Only run the charger if all the filters match + ReqTypeField string // Field containing request type info, number in case of csv source, '^' as prefix in case of static values + DirectionField string // Field containing direction info + TenantField string // Field containing tenant info + CategoryField string // Field containing tor info + AccountField string // Field containing account information + SubjectField string // Field containing subject information + DestinationField string // Field containing destination information + SetupTimeField string // Field containing setup time information + AnswerTimeField string // Field containing answer time information + UsageField string // Field containing usage information + SupplierField string // Field containing supplier information + DisconnectCauseField string // Field containing disconnect cause information + rsrRunFilters []*RSRField // Storage for compiled Regexp in case of RSRFields + rsrReqTypeField *RSRField + rsrDirectionField *RSRField + rsrTenantField *RSRField + rsrCategoryField *RSRField + rsrAccountField *RSRField + rsrSubjectField *RSRField + rsrDestinationField *RSRField + rsrSetupTimeField *RSRField + rsrAnswerTimeField *RSRField + rsrUsageField *RSRField + rsrSupplierField *RSRField + rsrDisconnectCauseField *RSRField } func DerivedChargersKey(direction, tenant, category, account, subject string) string { @@ -153,6 +161,6 @@ func (dcs DerivedChargers) Append(dc *DerivedCharger) (DerivedChargers, error) { func (dcs DerivedChargers) AppendDefaultRun() (DerivedChargers, error) { dcDf, _ := NewDerivedCharger(DEFAULT_RUNID, "", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, - META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT) + META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT) return append(dcs, dcDf), nil } diff --git a/utils/derivedchargers_test.go b/utils/derivedchargers_test.go index b07299cc7..5cbeb32c7 100644 --- a/utils/derivedchargers_test.go +++ b/utils/derivedchargers_test.go @@ -46,40 +46,42 @@ func TestAppendDerivedChargers(t *testing.T) { func TestNewDerivedCharger(t *testing.T) { edc1 := &DerivedCharger{ - RunId: "test1", - RunFilters: "", - ReqTypeField: "reqtype1", - DirectionField: "direction1", - TenantField: "tenant1", - CategoryField: "tor1", - AccountField: "account1", - SubjectField: "subject1", - DestinationField: "destination1", - SetupTimeField: "setuptime1", - AnswerTimeField: "answertime1", - UsageField: "duration1", - SupplierField: "supplier1", + RunId: "test1", + RunFilters: "", + ReqTypeField: "reqtype1", + DirectionField: "direction1", + TenantField: "tenant1", + CategoryField: "tor1", + AccountField: "account1", + SubjectField: "subject1", + DestinationField: "destination1", + SetupTimeField: "setuptime1", + AnswerTimeField: "answertime1", + UsageField: "duration1", + SupplierField: "supplier1", + DisconnectCauseField: "NORMAL_CLEARING", } if dc1, err := NewDerivedCharger("test1", "", "reqtype1", "direction1", "tenant1", "tor1", "account1", "subject1", "destination1", - "setuptime1", "answertime1", "duration1", "supplier1"); err != nil { + "setuptime1", "answertime1", "duration1", "supplier1", "NORMAL_CLEARING"); err != nil { t.Error("Unexpected error", err.Error) } else if !reflect.DeepEqual(edc1, dc1) { t.Errorf("Expecting: %v, received: %v", edc1, dc1) } edc2 := &DerivedCharger{ - RunId: "test2", - RunFilters: "^cdr_source/tdm_cdrs/", - ReqTypeField: "~reqtype2:s/sip:(.+)/$1/", - DirectionField: "~direction2:s/sip:(.+)/$1/", - TenantField: "~tenant2:s/sip:(.+)/$1/", - CategoryField: "~tor2:s/sip:(.+)/$1/", - AccountField: "~account2:s/sip:(.+)/$1/", - SubjectField: "~subject2:s/sip:(.+)/$1/", - DestinationField: "~destination2:s/sip:(.+)/$1/", - SetupTimeField: "~setuptime2:s/sip:(.+)/$1/", - AnswerTimeField: "~answertime2:s/sip:(.+)/$1/", - UsageField: "~duration2:s/sip:(.+)/$1/", - SupplierField: "~supplier2:s/(.+)/$1/", + RunId: "test2", + RunFilters: "^cdr_source/tdm_cdrs/", + ReqTypeField: "~reqtype2:s/sip:(.+)/$1/", + DirectionField: "~direction2:s/sip:(.+)/$1/", + TenantField: "~tenant2:s/sip:(.+)/$1/", + CategoryField: "~tor2:s/sip:(.+)/$1/", + AccountField: "~account2:s/sip:(.+)/$1/", + SubjectField: "~subject2:s/sip:(.+)/$1/", + DestinationField: "~destination2:s/sip:(.+)/$1/", + SetupTimeField: "~setuptime2:s/sip:(.+)/$1/", + AnswerTimeField: "~answertime2:s/sip:(.+)/$1/", + UsageField: "~duration2:s/sip:(.+)/$1/", + SupplierField: "~supplier2:s/(.+)/$1/", + DisconnectCauseField: "~cgr_disconnect:s/(.+)/$1/", } edc2.rsrRunFilters, _ = ParseRSRFields("^cdr_source/tdm_cdrs/", INFIELD_SEP) edc2.rsrReqTypeField, _ = NewRSRField("~reqtype2:s/sip:(.+)/$1/") @@ -93,6 +95,7 @@ func TestNewDerivedCharger(t *testing.T) { edc2.rsrAnswerTimeField, _ = NewRSRField("~answertime2:s/sip:(.+)/$1/") edc2.rsrUsageField, _ = NewRSRField("~duration2:s/sip:(.+)/$1/") edc2.rsrSupplierField, _ = NewRSRField("~supplier2:s/(.+)/$1/") + edc2.rsrDisconnectCauseField, _ = NewRSRField("~cgr_disconnect:s/(.+)/$1/") if dc2, err := NewDerivedCharger("test2", "^cdr_source/tdm_cdrs/", "~reqtype2:s/sip:(.+)/$1/", @@ -105,7 +108,8 @@ func TestNewDerivedCharger(t *testing.T) { "~setuptime2:s/sip:(.+)/$1/", "~answertime2:s/sip:(.+)/$1/", "~duration2:s/sip:(.+)/$1/", - "~supplier2:s/(.+)/$1/"); err != nil { + "~supplier2:s/(.+)/$1/", + "~cgr_disconnect:s/(.+)/$1/"); err != nil { t.Error("Unexpected error", err) } else if !reflect.DeepEqual(edc2, dc2) { t.Errorf("Expecting: %v, received: %v", edc2, dc2) @@ -122,17 +126,19 @@ func TestAppendDefaultRun(t *testing.T) { var dc1 DerivedChargers dcDf := &DerivedCharger{RunId: DEFAULT_RUNID, RunFilters: "", ReqTypeField: META_DEFAULT, DirectionField: META_DEFAULT, TenantField: META_DEFAULT, CategoryField: META_DEFAULT, AccountField: META_DEFAULT, SubjectField: META_DEFAULT, - DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, UsageField: META_DEFAULT, SupplierField: META_DEFAULT} + DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, UsageField: META_DEFAULT, SupplierField: META_DEFAULT, + DisconnectCauseField: META_DEFAULT} eDc1 := DerivedChargers{dcDf} if dc1, _ = dc1.AppendDefaultRun(); !reflect.DeepEqual(dc1, eDc1) { t.Error("Unexpected result.") } dc2 := DerivedChargers{ &DerivedCharger{RunId: "extra1", RunFilters: "", ReqTypeField: "reqtype2", DirectionField: META_DEFAULT, TenantField: META_DEFAULT, CategoryField: META_DEFAULT, - AccountField: "rif", SubjectField: "rif", DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, UsageField: META_DEFAULT}, + AccountField: "rif", SubjectField: "rif", DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, UsageField: META_DEFAULT, + DisconnectCauseField: META_DEFAULT}, &DerivedCharger{RunId: "extra2", ReqTypeField: META_DEFAULT, DirectionField: META_DEFAULT, TenantField: META_DEFAULT, CategoryField: META_DEFAULT, AccountField: "ivo", SubjectField: "ivo", DestinationField: META_DEFAULT, SetupTimeField: META_DEFAULT, AnswerTimeField: META_DEFAULT, - UsageField: META_DEFAULT, SupplierField: META_DEFAULT}, + UsageField: META_DEFAULT, SupplierField: META_DEFAULT, DisconnectCauseField: META_DEFAULT}, } eDc2 := append(dc2, dcDf) if dc2, _ = dc2.AppendDefaultRun(); !reflect.DeepEqual(dc2, eDc2) {