cdr AsStoredCdr() -> ForkCdr(), fixup ForkCdr in StoredCdr type

This commit is contained in:
DanB
2014-04-27 14:17:36 +02:00
parent 35cb7483de
commit e01ad95465
13 changed files with 150 additions and 43 deletions

View File

@@ -118,7 +118,7 @@ func (self *Cdrc) parseFieldsConfig() error {
}
// Takes the record out of csv and turns it into http form which can be posted
func (self *Cdrc) recordAsStoredCdr(record []string) (*utils.StoredCdr, error) {
func (self *Cdrc) recordForkCdr(record []string) (*utils.StoredCdr, error) {
ratedCdr := &utils.StoredCdr{CdrSource: self.cgrCfg.CdrcSourceId, ExtraFields: map[string]string{}, Cost: -1}
var err error
for cfgFieldName, cfgFieldVal := range self.cfgCdrFields {
@@ -233,7 +233,7 @@ func (self *Cdrc) processFile(filePath string) error {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
continue // Other csv related errors, ignore
}
rawCdr, err := self.recordAsStoredCdr(record)
rawCdr, err := self.recordForkCdr(record)
if err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
continue

View File

@@ -56,7 +56,7 @@ func TestParseFieldsConfig(t *testing.T) {
}
}
func TestRecordAsStoredCdr(t *testing.T) {
func TestRecordForkCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
cgrConfig.CdrcExtraFields = []string{"supplier:11"}
cdrc := &Cdrc{cgrCfg: cgrConfig}
@@ -64,13 +64,13 @@ func TestRecordAsStoredCdr(t *testing.T) {
t.Error("Failed parsing default fieldIndexesFromConfig", err)
}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordAsStoredCdr(cdrRow)
_, err := cdrc.recordForkCdr(cdrRow)
if err == nil {
t.Error("Failed to corectly detect missing fields from record")
}
cdrRow = []string{"acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963", "2013-02-03 19:50:00", "2013-02-03 19:54:00", "62",
"supplier1", "172.16.1.1"}
rtCdr, err := cdrc.recordAsStoredCdr(cdrRow)
rtCdr, err := cdrc.recordForkCdr(cdrRow)
if err != nil {
t.Error("Failed to parse CDR in rated cdr", err)
}

View File

@@ -201,7 +201,7 @@ func (fsCdr FSCdr) Restore(input string) error {
}
// Used in extra mediation
func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) {
func (fsCdr FSCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) {
if utils.IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
return nil, errors.New(fmt.Sprintf("%s:FieldName", utils.ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
}

View File

@@ -102,13 +102,13 @@ func TestCDRFields(t *testing.T) {
}
func TestFsCdrAsStoredCdr(t *testing.T) {
func TestFsCdrForkCdr(t *testing.T) {
cfg, _ = config.NewDefaultCGRConfig()
fsCdr, err := new(FSCdr).New(body)
if err != nil {
t.Errorf("Error loading cdr: %v", err)
}
rtCdrOut, err := fsCdr.AsStoredCdr("wholesale_run", "^"+utils.RATED, "^*out", "cgr_tenant", "cgr_tor", "cgr_account", "cgr_subject", "cgr_destination", "start_epoch",
rtCdrOut, err := fsCdr.ForkCdr("wholesale_run", "^"+utils.RATED, "^*out", "cgr_tenant", "cgr_tor", "cgr_account", "cgr_subject", "cgr_destination", "start_epoch",
"answer_epoch", "billsec", []string{"effective_caller_id_number"}, true)
if err != nil {
t.Error("Unexpected error received", err)
@@ -122,7 +122,7 @@ func TestFsCdrAsStoredCdr(t *testing.T) {
if !reflect.DeepEqual(rtCdrOut, expctRatedCdr) {
t.Errorf("Received: %v, expected: %v", rtCdrOut, expctRatedCdr)
}
rtCdrOut2, err := fsCdr.AsStoredCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "cgr_destination",
rtCdrOut2, err := fsCdr.ForkCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "cgr_destination",
"^2013-12-07T08:42:24Z", "^2013-12-07T08:42:26Z", "^12s", []string{"effective_caller_id_number"}, true)
if err != nil {
t.Error("Unexpected error received", err)
@@ -136,7 +136,7 @@ func TestFsCdrAsStoredCdr(t *testing.T) {
if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) {
t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2)
}
_, err = fsCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true)
_, err = fsCdr.ForkCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration", []string{"field_extr1", "fieldextr2"}, true)
if err == nil {
t.Error("Failed to detect missing header")
}

View File

@@ -0,0 +1,32 @@
# CGRateS Configuration file
#
# Used in mediator_local_test
# Starts rater, cdrs and mediator connecting over internal channel
[rater]
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
[cdre]
export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
[mediator]
enabled = true # Starts Mediator service: <true|false>.
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
[derived_charging]
run_ids = run2 # Identifiers of additional sessions control.
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
tor_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.

View File

@@ -17,4 +17,18 @@ export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will
enabled = true # Starts Mediator service: <true|false>.
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
[derived_charging]
run_ids = run2 # Identifiers of additional sessions control.
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
tor_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
setup_time_fields = *default # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
answer_time_fields = *default # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
duration_fields = *default # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.

View File

@@ -124,8 +124,9 @@ func (self *Mediator) RateCdr(dbcdr utils.RawCDR) error {
return errors.New(errText)
}
for _, dc := range dcs {
forkedCdr, err := dbcdr.AsStoredCdr(dc.RunId, dc.ReqTypeField, dc.DirectionField,
forkedCdr, err := dbcdr.ForkCdr(dc.RunId, dc.ReqTypeField, dc.DirectionField,
dc.TenantField, dc.TorField, dc.AccountField, dc.SubjectField, dc.DestinationField, dc.SetupTimeField, dc.AnswerTimeField, dc.DurationField, []string{}, true)
engine.Logger.Debug(fmt.Sprintf("Forked CDR for dc: %v, is: %v", dc, forkedCdr))
if err != nil { // Errors on fork, cannot calculate further, write that into db for later analysis
self.cdrDb.SetRatedCdr(&utils.StoredCdr{CgrId: dbcdr.GetCgrId(), MediationRunId: dc.RunId, Cost: -1.0}, err.Error()) // Cannot fork CDR, important just runid and error
continue

View File

@@ -237,6 +237,38 @@ func TestRateCdrs(t *testing.T) {
}
}
func TestDerivedCharging(t *testing.T) {
if !*testLocal {
return
}
var reply string
if err := cgrRpc.Call("MediatorV1.RateCdrs", utils.AttrRateCdrs{}, &reply); err != nil {
t.Error(err.Error())
} else if reply != utils.OK {
t.Errorf("Unexpected reply: %s", reply)
}
if nonRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, true, true); err != nil {
t.Error(err)
} else if len(nonRatedCdrs) != 0 { // Just two of them should be non-rated
t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs)))
}
if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true); err != nil {
t.Error(err)
} else if len(errRatedCdrs) != 2 { // The first 2 with errors should be still there before rerating
t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs)))
}
if err := cgrRpc.Call("MediatorV1.RateCdrs", utils.AttrRateCdrs{RerateErrors: true}, &reply); err != nil {
t.Error(err.Error())
} else if reply != utils.OK {
t.Errorf("Unexpected reply: %s", reply)
}
if errRatedCdrs, err := cdrStor.GetStoredCdrs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0, 0, time.Time{}, time.Time{}, false, true); err != nil {
t.Error(err)
} else if len(errRatedCdrs) != 1 { // One CDR with errors should be fixed now by rerating
t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs)))
}
}
// Simply kill the engine after we are done with tests within this file
func TestStopEngine(t *testing.T) {
if !*testLocal {

View File

@@ -110,7 +110,7 @@ func (cgrCdr CgrCdr) GetDuration() (time.Duration, error) {
// Used in mediation, fieldsMandatory marks whether missing field out of request represents error or can be ignored
// If the fields in parameters start with ^ their value is considered instead of dynamically retrieving it from CDR
func (cgrCdr CgrCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
func (cgrCdr CgrCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
if IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
return nil, errors.New(fmt.Sprintf("%s:FieldName", ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
}

View File

@@ -85,11 +85,27 @@ func TestCgrCdrFields(t *testing.T) {
}
}
func TestCgrCdrAsStoredCdr(t *testing.T) {
func TestCgrCdrForkCdr(t *testing.T) {
sampleCdr1 := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
rtSampleCdrOut, err := sampleCdr1.ForkCdr("sample_run1", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
[]string{}, true)
if err != nil {
t.Error("Unexpected error received", err)
}
setupTime1 := time.Date(2013, 11, 7, 8, 42, 24, 0, time.UTC)
expctSplRatedCdr := &StoredCdr{CgrId: Sha1("dsafdsaf", setupTime1.String()), AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "source_test", ReqType: "rated",
Direction: "*out", Tenant: "cgrates.org", TOR: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: setupTime1, AnswerTime: time.Unix(1383813746, 0).UTC(),
Duration: 10000000000, ExtraFields: map[string]string{}, MediationRunId: "sample_run1", Cost: -1}
if !reflect.DeepEqual(expctSplRatedCdr, rtSampleCdrOut) {
t.Errorf("Expected: %v, received: %v", expctSplRatedCdr, rtSampleCdrOut)
}
cgrCdr := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
rtCdrOut, err := cgrCdr.AsStoredCdr("wholesale_run", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
rtCdrOut, err := cgrCdr.ForkCdr("wholesale_run", "reqtype", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
[]string{"field_extr1", "fieldextr2"}, true)
if err != nil {
t.Error("Unexpected error received", err)
@@ -102,7 +118,7 @@ func TestCgrCdrAsStoredCdr(t *testing.T) {
if !reflect.DeepEqual(rtCdrOut, expctRatedCdr) {
t.Errorf("Received: %v, expected: %v", rtCdrOut, expctRatedCdr)
}
rtCdrOut2, err := cgrCdr.AsStoredCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "destination",
rtCdrOut2, err := cgrCdr.ForkCdr("wholesale_run", "^postpaid", "^*in", "^cgrates.com", "^premium_call", "^first_account", "^first_subject", "destination",
"^2013-12-07T08:42:24Z", "^2013-12-07T08:42:26Z", "^12s", []string{"field_extr1", "fieldextr2"}, true)
if err != nil {
t.Error("Unexpected error received", err)
@@ -115,14 +131,14 @@ func TestCgrCdrAsStoredCdr(t *testing.T) {
if !reflect.DeepEqual(rtCdrOut2, expctRatedCdr2) {
t.Errorf("Received: %v, expected: %v", rtCdrOut2, expctRatedCdr2)
}
_, err = cgrCdr.AsStoredCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
_, err = cgrCdr.ForkCdr("wholesale_run", "dummy_header", "direction", "tenant", "tor", "account", "subject", "destination", "setup_time", "answer_time", "duration",
[]string{"field_extr1", "fieldextr2"}, true)
if err == nil {
t.Error("Failed to detect missing header")
}
}
func TestCgrCdrAsStoredCdrFromMetaDefaults(t *testing.T) {
func TestCgrCdrForkCdrFromMetaDefaults(t *testing.T) {
cgrCdr := &CgrCdr{"accid": "dsafdsaf", "cdrhost": "192.168.1.1", "cdrsource": "source_test", "reqtype": "rated", "direction": "*out", "tenant": "cgrates.org", "tor": "call",
"account": "1001", "subject": "1001", "destination": "1002", "setup_time": "2013-11-07T08:42:24Z", "answer_time": "2013-11-07T08:42:26Z", "duration": "10",
"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
@@ -132,7 +148,7 @@ func TestCgrCdrAsStoredCdrFromMetaDefaults(t *testing.T) {
SetupTime: setupTime, AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Duration: time.Duration(10) * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, MediationRunId: "wholesale_run", Cost: -1}
cdrOut, err := cgrCdr.AsStoredCdr("wholesale_run", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT,
cdrOut, err := cgrCdr.ForkCdr("wholesale_run", META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT, META_DEFAULT,
META_DEFAULT, META_DEFAULT, META_DEFAULT, []string{"field_extr1", "fieldextr2"}, true)
if err != nil {
t.Fatal("Unexpected error received", err)

View File

@@ -100,6 +100,7 @@ const (
MASK_CHAR = "*"
CONCATENATED_KEY_SEP = ":"
META_DEFAULT = "*default"
FORKED_CDR = "forked_cdr"
)
var (

View File

@@ -40,6 +40,6 @@ type RawCDR interface {
GetSetupTime() (time.Time, error) // Time when the call was set-up
GetAnswerTime() (time.Time, error) // Time when the call was answered
GetDuration() (time.Duration, error)
GetExtraFields() map[string]string //Stores extra CDR Fields
AsStoredCdr(string, string, string, string, string, string, string, string, string, string, string, []string, bool) (*StoredCdr, error) // Based on fields queried will return a particular instance of RatedCDR
GetExtraFields() map[string]string //Stores extra CDR Fields
ForkCdr(string, string, string, string, string, string, string, string, string, string, string, []string, bool) (*StoredCdr, error) // Based on fields queried will return a particular instance of RatedCDR
}

View File

@@ -27,29 +27,29 @@ import (
func NewStoredCdrFromRawCDR(rawcdr RawCDR) (*StoredCdr, error) {
var err error
rtCdr := new(StoredCdr)
rtCdr.CgrId = rawcdr.GetCgrId()
rtCdr.AccId = rawcdr.GetAccId()
rtCdr.CdrHost = rawcdr.GetCdrHost()
rtCdr.CdrSource = rawcdr.GetCdrSource()
rtCdr.ReqType = rawcdr.GetReqType()
rtCdr.Direction = rawcdr.GetDirection()
rtCdr.Tenant = rawcdr.GetTenant()
rtCdr.TOR = rawcdr.GetTOR()
rtCdr.Account = rawcdr.GetAccount()
rtCdr.Subject = rawcdr.GetSubject()
rtCdr.Destination = rawcdr.GetDestination()
if rtCdr.SetupTime, err = rawcdr.GetSetupTime(); err != nil {
strCdr := new(StoredCdr)
strCdr.CgrId = rawcdr.GetCgrId()
strCdr.AccId = rawcdr.GetAccId()
strCdr.CdrHost = rawcdr.GetCdrHost()
strCdr.CdrSource = rawcdr.GetCdrSource()
strCdr.ReqType = rawcdr.GetReqType()
strCdr.Direction = rawcdr.GetDirection()
strCdr.Tenant = rawcdr.GetTenant()
strCdr.TOR = rawcdr.GetTOR()
strCdr.Account = rawcdr.GetAccount()
strCdr.Subject = rawcdr.GetSubject()
strCdr.Destination = rawcdr.GetDestination()
if strCdr.SetupTime, err = rawcdr.GetSetupTime(); err != nil {
return nil, err
}
if rtCdr.AnswerTime, err = rawcdr.GetAnswerTime(); err != nil {
if strCdr.AnswerTime, err = rawcdr.GetAnswerTime(); err != nil {
return nil, err
}
rtCdr.Duration, _ = rawcdr.GetDuration()
rtCdr.ExtraFields = rawcdr.GetExtraFields()
rtCdr.MediationRunId = DEFAULT_RUNID
rtCdr.Cost = -1
return rtCdr, nil
strCdr.Duration, _ = rawcdr.GetDuration()
strCdr.ExtraFields = rawcdr.GetExtraFields()
strCdr.MediationRunId = DEFAULT_RUNID
strCdr.Cost = -1
return strCdr, nil
}
// Rated CDR as extracted from StorDb. Kinda standard of internal CDR, complies to CDR interface also
@@ -145,10 +145,6 @@ func (storedCdr *StoredCdr) FormatCost(shiftDecimals, roundDecimals int) string
return strconv.FormatFloat(cost, 'f', roundDecimals, 64)
}
func (storedCdr *StoredCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
return storedCdr, nil
}
// Converts part of the rated Cdr as httpForm used to post remotely to CDRS
func (storedCdr *StoredCdr) AsRawCdrHttpForm() url.Values {
v := url.Values{}
@@ -212,3 +208,18 @@ func (storedCdr *StoredCdr) ExportFieldValue(fldName string) string {
return storedCdr.ExtraFields[fldName]
}
}
// Converts to CgrCdr, so we can fork with less code
func (storedCdr *StoredCdr) AsCgrCdr() CgrCdr {
cgrCdr := make(CgrCdr)
for _, fldName := range PrimaryCdrFields {
cgrCdr[fldName] = storedCdr.ExportFieldValue(fldName)
}
return cgrCdr
}
func (storedCdr *StoredCdr) ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld,
setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
return storedCdr.AsCgrCdr().ForkCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld,
setupTimeFld, answerTimeFld, durationFld, extraFlds, fieldsMandatory)
}