From 4e784b6ef59d205cda6682068f498f80833f481d Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 19 Jan 2016 21:24:13 +0200 Subject: [PATCH 1/6] conditional topup tests and improvements fixes #275 --- engine/account.go | 4 +- engine/actions_test.go | 191 ++++++++++++++++++++++++++++++++++++++ utils/cond_loader.go | 26 +++++- utils/cond_loader_test.go | 25 +++++ 4 files changed, 240 insertions(+), 6 deletions(-) diff --git a/engine/account.go b/engine/account.go index d733af1c8..16493a6cb 100644 --- a/engine/account.go +++ b/engine/account.go @@ -699,7 +699,9 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balance func (acc *Account) matchConditions(condition string) (bool, error) { cl := &utils.CondLoader{} - cl.Parse(condition) + if err := cl.Parse(condition); err != nil { + return false, err + } for balanceType, balanceChain := range acc.BalanceMap { for _, b := range balanceChain { check, err := cl.Check(&struct { diff --git a/engine/actions_test.go b/engine/actions_test.go index 12adecf9d..4eaaec183 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -1568,6 +1568,197 @@ func TestActionTransferMonetaryDefaultFilter(t *testing.T) { } } +func TestActionConditionalTopup(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Id: utils.META_DEFAULT, + Value: 10, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 3, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*monetary","Value":1,"Weight":10}`, + Balance: &Balance{ + Value: 11, + Weight: 30, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 5 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 31 || + afterUb.BalanceMap[utils.MONETARY][4].Value != 11 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +func TestActionConditionalTopupNoMatch(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Id: utils.META_DEFAULT, + Value: 10, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 3, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*monetary","Value":2,"Weight":10}`, + Balance: &Balance{ + Value: 11, + Weight: 30, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 4 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 20 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +func TestActionConditionalTopupExistingBalance(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + utils.VOICE: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Value: 10, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 100, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*voice","Value":{"*gte":100}}`, + Balance: &Balance{ + Value: 11, + Weight: 10, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 2 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 18 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + /**************** Benchmarks ********************************/ func BenchmarkUUID(b *testing.B) { diff --git a/utils/cond_loader.go b/utils/cond_loader.go index 8fdfca867..6af762673 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -148,6 +148,13 @@ func (kv *keyValue) checkStruct(o interface{}) (bool, error) { return value.Interface() == kv.value, nil } +type trueElement struct{} + +func (te *trueElement) addChild(condElement) error { return ErrNotImplemented } +func (te *trueElement) checkStruct(o interface{}) (bool, error) { + return true, nil +} + func isOperator(s string) bool { return strings.HasPrefix(s, "*") } @@ -186,18 +193,27 @@ func (cp *CondLoader) load(a map[string]interface{}, parentElement condElement) if parentElement != nil { parentElement.addChild(currentElement) } else { - return currentElement, nil + if len(a) > 1 { + parentElement = &operatorSlice{operator: CondAND} + parentElement.addChild(currentElement) + } else { + return currentElement, nil + } } } - return nil, nil + return parentElement, nil } func (cp *CondLoader) Parse(s string) (err error) { a := make(map[string]interface{}) - if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil { - return err + if len(s) != 0 { + if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil { + return err + } + cp.rootElement, err = cp.load(a, nil) + } else { + cp.rootElement = &trueElement{} } - cp.rootElement, err = cp.load(a, nil) return } diff --git a/utils/cond_loader_test.go b/utils/cond_loader_test.go index c0e9930ff..908c29b19 100644 --- a/utils/cond_loader_test.go +++ b/utils/cond_loader_test.go @@ -20,6 +20,10 @@ func TestCondLoader(t *testing.T) { if err != nil { t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) } + err = cl.Parse(``) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } } func TestCondKeyValue(t *testing.T) { @@ -68,6 +72,27 @@ func TestCondKeyValue(t *testing.T) { if check, err := cl.Check(o); check || err != nil { t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) } + err = cl.Parse(`{"Field":6, "Other":false}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } + err = cl.Parse(`{"Other":true, "Field":{"*gt":5}}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); !check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } + err = cl.Parse(``) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); !check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } } func TestCondKeyValuePointer(t *testing.T) { From 54f6d5b02f6739ceab6091e350d6fca8aada5f17 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 19 Jan 2016 22:01:03 +0200 Subject: [PATCH 2/6] added mms tor type --- apier/v1/cdre.go | 7 +++++-- apier/v2/cdre.go | 7 +++++-- cdre/cdrexporter.go | 34 ++++++++++++++++++++++------------ cdre/cdrexporter_test.go | 2 +- cdre/csv_test.go | 4 ++-- cdre/fixedwidth_test.go | 4 ++-- config/cdreconfig.go | 5 +++++ config/cfg_data.json | 2 +- config/config_defaults.go | 1 + config/config_json_test.go | 5 +++-- config/libconfig_json.go | 1 + engine/cdr.go | 2 +- utils/apitpdata.go | 2 ++ utils/consts.go | 1 + 14 files changed, 52 insertions(+), 25 deletions(-) diff --git a/apier/v1/cdre.go b/apier/v1/cdre.go index fe77a03d1..224058cfc 100644 --- a/apier/v1/cdre.go +++ b/apier/v1/cdre.go @@ -144,6 +144,10 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E if attr.SmsUsageMultiplyFactor != nil && *attr.SmsUsageMultiplyFactor != 0.0 { smsUsageMultiplyFactor = *attr.SmsUsageMultiplyFactor } + mmsUsageMultiplyFactor := exportTemplate.MMSUsageMultiplyFactor + if attr.MmsUsageMultiplyFactor != nil && *attr.MmsUsageMultiplyFactor != 0.0 { + mmsUsageMultiplyFactor = *attr.MmsUsageMultiplyFactor + } genericUsageMultiplyFactor := exportTemplate.GenericUsageMultiplyFactor if attr.GenericUsageMultiplyFactor != nil && *attr.GenericUsageMultiplyFactor != 0.0 { genericUsageMultiplyFactor = *attr.GenericUsageMultiplyFactor @@ -179,8 +183,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E *reply = utils.ExportedFileCdrs{ExportedFilePath: ""} return nil } - cdrexp, err := cdre.NewCdrExporter(cdrs, self.CdrDb, exportTemplate, cdrFormat, fieldSep, exportId, dataUsageMultiplyFactor, smsUsageMultiplyFactor, genericUsageMultiplyFactor, - costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify, self.Config.DefaultTimezone) + cdrexp, err := cdre.NewCdrExporter(cdrs, self.CdrDb, exportTemplate, cdrFormat, fieldSep, exportId, dataUsageMultiplyFactor, smsUsageMultiplyFactor, mmsUsageMultiplyFactor, genericUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify, self.Config.DefaultTimezone) if err != nil { return utils.NewErrServerError(err) } diff --git a/apier/v2/cdre.go b/apier/v2/cdre.go index d4e0d81bb..31ea8f43e 100644 --- a/apier/v2/cdre.go +++ b/apier/v2/cdre.go @@ -80,6 +80,10 @@ func (self *ApierV2) ExportCdrsToFile(attr utils.AttrExportCdrsToFile, reply *ut if attr.SMSUsageMultiplyFactor != nil && *attr.SMSUsageMultiplyFactor != 0.0 { SMSUsageMultiplyFactor = *attr.SMSUsageMultiplyFactor } + MMSUsageMultiplyFactor := exportTemplate.MMSUsageMultiplyFactor + if attr.MMSUsageMultiplyFactor != nil && *attr.MMSUsageMultiplyFactor != 0.0 { + MMSUsageMultiplyFactor = *attr.MMSUsageMultiplyFactor + } genericUsageMultiplyFactor := exportTemplate.GenericUsageMultiplyFactor if attr.GenericUsageMultiplyFactor != nil && *attr.GenericUsageMultiplyFactor != 0.0 { genericUsageMultiplyFactor = *attr.GenericUsageMultiplyFactor @@ -115,8 +119,7 @@ func (self *ApierV2) ExportCdrsToFile(attr utils.AttrExportCdrsToFile, reply *ut *reply = utils.ExportedFileCdrs{ExportedFilePath: ""} return nil } - cdrexp, err := cdre.NewCdrExporter(cdrs, self.CdrDb, exportTemplate, cdrFormat, fieldSep, ExportID, dataUsageMultiplyFactor, SMSUsageMultiplyFactor, genericUsageMultiplyFactor, - costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify, self.Config.DefaultTimezone) + cdrexp, err := cdre.NewCdrExporter(cdrs, self.CdrDb, exportTemplate, cdrFormat, fieldSep, ExportID, dataUsageMultiplyFactor, SMSUsageMultiplyFactor, MMSUsageMultiplyFactor, genericUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify, self.Config.DefaultTimezone) if err != nil { return utils.NewErrServerError(err) } diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index 00fbe67d9..a98a26c95 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -43,6 +43,7 @@ const ( META_NRCDRS = "*cdrs_number" META_DURCDRS = "*cdrs_duration" META_SMSUSAGE = "*sms_usage" + META_MMSUSAGE = "*mms_usage" META_GENERICUSAGE = "*generic_usage" META_DATAUSAGE = "*data_usage" META_COSTCDRS = "*cdrs_cost" @@ -53,7 +54,7 @@ const ( var err error func NewCdrExporter(cdrs []*engine.CDR, cdrDb engine.CdrStorage, exportTpl *config.CdreConfig, cdrFormat string, fieldSeparator rune, exportId string, - dataUsageMultiplyFactor, smsUsageMultiplyFactor, genericUsageMultiplyFactor, costMultiplyFactor float64, + dataUsageMultiplyFactor, smsUsageMultiplyFactor, mmsUsageMultiplyFactor, genericUsageMultiplyFactor, costMultiplyFactor float64, costShiftDigits, roundDecimals, cgrPrecision int, maskDestId string, maskLen int, httpSkipTlsCheck bool, timezone string) (*CdrExporter, error) { if len(cdrs) == 0 { // Nothing to export return nil, nil @@ -66,7 +67,7 @@ func NewCdrExporter(cdrs []*engine.CDR, cdrDb engine.CdrStorage, exportTpl *conf fieldSeparator: fieldSeparator, exportId: exportId, dataUsageMultiplyFactor: dataUsageMultiplyFactor, - smsUsageMultiplyFactor: smsUsageMultiplyFactor, + mmsUsageMultiplyFactor: mmsUsageMultiplyFactor, costMultiplyFactor: costMultiplyFactor, costShiftDigits: costShiftDigits, roundDecimals: roundDecimals, @@ -92,18 +93,19 @@ type CdrExporter struct { exportId string // Unique identifier or this export dataUsageMultiplyFactor, smsUsageMultiplyFactor, // Multiply the SMS usage (eg: some billing systems billing them as minutes) + mmsUsageMultiplyFactor, genericUsageMultiplyFactor, costMultiplyFactor float64 - costShiftDigits, roundDecimals, cgrPrecision int - maskDestId string - maskLen int - httpSkipTlsCheck bool - timezone string - header, trailer []string // Header and Trailer fields - content [][]string // Rows of cdr fields - firstCdrATime, lastCdrATime time.Time - numberOfRecords int - totalDuration, totalDataUsage, totalSmsUsage, totalGenericUsage time.Duration + costShiftDigits, roundDecimals, cgrPrecision int + maskDestId string + maskLen int + httpSkipTlsCheck bool + timezone string + header, trailer []string // Header and Trailer fields + content [][]string // Rows of cdr fields + firstCdrATime, lastCdrATime time.Time + numberOfRecords int + totalDuration, totalDataUsage, totalSmsUsage, totalMmsUsage, totalGenericUsage time.Duration totalCost float64 firstExpOrderId, lastExpOrderId int64 @@ -234,6 +236,9 @@ func (cdre *CdrExporter) metaHandler(tag, arg string) (string, error) { case META_SMSUSAGE: emulatedCdr := &engine.CDR{ToR: utils.SMS, Usage: cdre.totalSmsUsage} return emulatedCdr.FormatUsage(arg), nil + case META_MMSUSAGE: + emulatedCdr := &engine.CDR{ToR: utils.MMS, Usage: cdre.totalMmsUsage} + return emulatedCdr.FormatUsage(arg), nil case META_GENERICUSAGE: emulatedCdr := &engine.CDR{ToR: utils.GENERIC, Usage: cdre.totalGenericUsage} return emulatedCdr.FormatUsage(arg), nil @@ -322,6 +327,8 @@ func (cdre *CdrExporter) processCdr(cdr *engine.CDR) error { cdr.UsageMultiply(cdre.dataUsageMultiplyFactor, cdre.cgrPrecision) } else if cdre.smsUsageMultiplyFactor != 0 && cdr.ToR == utils.SMS { cdr.UsageMultiply(cdre.smsUsageMultiplyFactor, cdre.cgrPrecision) + } else if cdre.mmsUsageMultiplyFactor != 0 && cdr.ToR == utils.MMS { + cdr.UsageMultiply(cdre.mmsUsageMultiplyFactor, cdre.cgrPrecision) } else if cdre.genericUsageMultiplyFactor != 0 && cdr.ToR == utils.GENERIC { cdr.UsageMultiply(cdre.genericUsageMultiplyFactor, cdre.cgrPrecision) } @@ -392,6 +399,9 @@ func (cdre *CdrExporter) processCdr(cdr *engine.CDR) error { if cdr.ToR == utils.SMS { // Count usage for SMS cdre.totalSmsUsage += cdr.Usage } + if cdr.ToR == utils.MMS { // Count usage for MMS + cdre.totalMmsUsage += cdr.Usage + } if cdr.ToR == utils.GENERIC { // Count usage for GENERIC cdre.totalGenericUsage += cdr.Usage } diff --git a/cdre/cdrexporter_test.go b/cdre/cdrexporter_test.go index 0960c8b8f..045fc0e4b 100644 --- a/cdre/cdrexporter_test.go +++ b/cdre/cdrexporter_test.go @@ -52,7 +52,7 @@ func TestCdreGetCombimedCdrFieldVal(t *testing.T) { Usage: time.Duration(10) * time.Second, RunID: "RETAIL1", Cost: 5.01}, } cdre, err := NewCdrExporter(cdrs, nil, cfg.CdreProfiles["*default"], cfg.CdreProfiles["*default"].CdrFormat, cfg.CdreProfiles["*default"].FieldSeparator, - "firstexport", 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify, "") + "firstexport", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify, "") if err != nil { t.Error("Unexpected error received: ", err) } diff --git a/cdre/csv_test.go b/cdre/csv_test.go index 0990c7a80..c69c140b9 100644 --- a/cdre/csv_test.go +++ b/cdre/csv_test.go @@ -40,7 +40,7 @@ func TestCsvCdrWriter(t *testing.T) { Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01, } - cdre, err := NewCdrExporter([]*engine.CDR{storedCdr1}, nil, cfg.CdreProfiles["*default"], utils.CSV, ',', "firstexport", 0.0, 0.0, 0.0, 0.0, 0, 4, + cdre, err := NewCdrExporter([]*engine.CDR{storedCdr1}, nil, cfg.CdreProfiles["*default"], utils.CSV, ',', "firstexport", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify, "") if err != nil { t.Error("Unexpected error received: ", err) @@ -69,7 +69,7 @@ func TestAlternativeFieldSeparator(t *testing.T) { ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01, } cdre, err := NewCdrExporter([]*engine.CDR{storedCdr1}, nil, cfg.CdreProfiles["*default"], utils.CSV, '|', - "firstexport", 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify, "") + "firstexport", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify, "") if err != nil { t.Error("Unexpected error received: ", err) } diff --git a/cdre/fixedwidth_test.go b/cdre/fixedwidth_test.go index ffdd1f747..ef5f5137e 100644 --- a/cdre/fixedwidth_test.go +++ b/cdre/fixedwidth_test.go @@ -127,7 +127,7 @@ func TestWriteCdr(t *testing.T) { Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 2.34567, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, } - cdre, err := NewCdrExporter([]*engine.CDR{cdr}, nil, cdreCfg, utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0.0, 0.0, 0, 4, + cdre, err := NewCdrExporter([]*engine.CDR{cdr}, nil, cdreCfg, utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify, "") if err != nil { t.Error(err) @@ -203,7 +203,7 @@ func TestWriteCdrs(t *testing.T) { } cfg, _ := config.NewDefaultCGRConfig() cdre, err := NewCdrExporter([]*engine.CDR{cdr1, cdr2, cdr3, cdr4}, nil, cdreCfg, utils.CDRE_FIXED_WIDTH, ',', - "fwv_1", 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify, "") + "fwv_1", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify, "") if err != nil { t.Error(err) } diff --git a/config/cdreconfig.go b/config/cdreconfig.go index 55a98c3b5..f55bad28e 100644 --- a/config/cdreconfig.go +++ b/config/cdreconfig.go @@ -24,6 +24,7 @@ type CdreConfig struct { FieldSeparator rune DataUsageMultiplyFactor float64 SMSUsageMultiplyFactor float64 + MMSUsageMultiplyFactor float64 GenericUsageMultiplyFactor float64 CostMultiplyFactor float64 CostRoundingDecimals int @@ -54,6 +55,9 @@ func (self *CdreConfig) loadFromJsonCfg(jsnCfg *CdreJsonCfg) error { if jsnCfg.Sms_usage_multiply_factor != nil { self.SMSUsageMultiplyFactor = *jsnCfg.Sms_usage_multiply_factor } + if jsnCfg.Mms_usage_multiply_factor != nil { + self.MMSUsageMultiplyFactor = *jsnCfg.Mms_usage_multiply_factor + } if jsnCfg.Generic_usage_multiply_factor != nil { self.GenericUsageMultiplyFactor = *jsnCfg.Generic_usage_multiply_factor } @@ -100,6 +104,7 @@ func (self *CdreConfig) Clone() *CdreConfig { clnCdre.FieldSeparator = self.FieldSeparator clnCdre.DataUsageMultiplyFactor = self.DataUsageMultiplyFactor clnCdre.SMSUsageMultiplyFactor = self.SMSUsageMultiplyFactor + clnCdre.MMSUsageMultiplyFactor = self.MMSUsageMultiplyFactor clnCdre.GenericUsageMultiplyFactor = self.GenericUsageMultiplyFactor clnCdre.CostMultiplyFactor = self.CostMultiplyFactor clnCdre.CostRoundingDecimals = self.CostRoundingDecimals diff --git a/config/cfg_data.json b/config/cfg_data.json index 77f20a7da..f5eb4a811 100644 --- a/config/cfg_data.json +++ b/config/cfg_data.json @@ -33,7 +33,7 @@ "run_delay": 1, "cdr_source_id": "csv2", // free form field, tag identifying the source of the CDRs within CDRS database "content_fields":[ // import template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value - {"field_id": "ToR", "value": "~7:s/^(voice|data|sms|generic)$/*$1/"}, + {"field_id": "ToR", "value": "~7:s/^(voice|data|sms|mms|generic)$/*$1/"}, {"field_id": "AnswerTime", "value": "1"}, {"field_id": "Usage", "value": "~9:s/^(\\d+)$/${1}s/"}, ], diff --git a/config/config_defaults.go b/config/config_defaults.go index 837c8ecd0..0cedd84f0 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -132,6 +132,7 @@ const CGRATES_CFG_JSON = ` "field_separator": ",", "data_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from KBytes to Bytes) "sms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from SMS unit to call duration in some billing systems) + "mms_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from MMS unit to call duration in some billing systems) "generic_usage_multiply_factor": 1, // multiply data usage before export (eg: convert from GENERIC unit to call duration in some billing systems) "cost_multiply_factor": 1, // multiply cost before export, eg: add VAT "cost_rounding_decimals": -1, // rounding decimals for Cost values. -1 to disable rounding diff --git a/config/config_json_test.go b/config/config_json_test.go index c7317f93d..2548ec5c4 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -249,6 +249,7 @@ func TestDfCdreJsonCfgs(t *testing.T) { Field_separator: utils.StringPointer(","), Data_usage_multiply_factor: utils.Float64Pointer(1.0), Sms_usage_multiply_factor: utils.Float64Pointer(1.0), + Mms_usage_multiply_factor: utils.Float64Pointer(1.0), Generic_usage_multiply_factor: utils.Float64Pointer(1.0), Cost_multiply_factor: utils.Float64Pointer(1.0), Cost_rounding_decimals: utils.IntPointer(-1), @@ -591,7 +592,7 @@ func TestNewCgrJsonCfgFromFile(t *testing.T) { t.Error("Received: ", gCfg) } cdrFields := []*CdrFieldJsonCfg{ - &CdrFieldJsonCfg{Field_id: utils.StringPointer(utils.TOR), Value: utils.StringPointer("~7:s/^(voice|data|sms|generic)$/*$1/")}, + &CdrFieldJsonCfg{Field_id: utils.StringPointer(utils.TOR), Value: utils.StringPointer("~7:s/^(voice|data|sms|mms|generic)$/*$1/")}, &CdrFieldJsonCfg{Field_id: utils.StringPointer(utils.ANSWER_TIME), Value: utils.StringPointer("1")}, &CdrFieldJsonCfg{Field_id: utils.StringPointer(utils.USAGE), Value: utils.StringPointer(`~9:s/^(\d+)$/${1}s/`)}, } @@ -615,7 +616,7 @@ func TestNewCgrJsonCfgFromFile(t *testing.T) { if cfg, err := cgrJsonCfg.CdrcJsonCfg(); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCfgCdrc, cfg) { - t.Error("Received: ", cfg["CDRC-CSV2"]) + t.Error("Received: ", utils.ToIJSON(cfg["CDRC-CSV2"])) } eCfgSmFs := &SmFsJsonCfg{ Enabled: utils.BoolPointer(true), diff --git a/config/libconfig_json.go b/config/libconfig_json.go index fdf261f00..fba619846 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -127,6 +127,7 @@ type CdreJsonCfg struct { Field_separator *string Data_usage_multiply_factor *float64 Sms_usage_multiply_factor *float64 + Mms_usage_multiply_factor *float64 Generic_usage_multiply_factor *float64 Cost_multiply_factor *float64 Cost_rounding_decimals *int diff --git a/engine/cdr.go b/engine/cdr.go index ca0893832..de741ed3e 100644 --- a/engine/cdr.go +++ b/engine/cdr.go @@ -126,7 +126,7 @@ func (cdr *CDR) FormatCost(shiftDecimals, roundDecimals int) string { // Formats usage on export func (cdr *CDR) FormatUsage(layout string) string { - if utils.IsSliceMember([]string{utils.DATA, utils.SMS, utils.GENERIC}, cdr.ToR) { + if utils.IsSliceMember([]string{utils.DATA, utils.SMS, utils.MMS, utils.GENERIC}, cdr.ToR) { return strconv.FormatFloat(utils.Round(cdr.Usage.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64) } switch layout { diff --git a/utils/apitpdata.go b/utils/apitpdata.go index dd3c1c246..9a2c63d31 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -597,6 +597,7 @@ type AttrExpFileCdrs struct { ExportTemplate *string // Exported fields template <""|fld1,fld2|*xml:instance_name> DataUsageMultiplyFactor *float64 // Multiply data usage before export (eg: convert from KBytes to Bytes) SmsUsageMultiplyFactor *float64 // Multiply sms usage before export (eg: convert from SMS unit to call duration for some billing systems) + MmsUsageMultiplyFactor *float64 // Multiply mms usage before export (eg: convert from MMS unit to call duration for some billing systems) GenericUsageMultiplyFactor *float64 // Multiply generic usage before export (eg: convert from GENERIC unit to call duration for some billing systems) CostMultiplyFactor *float64 // Multiply the cost before export, eg: apply VAT CostShiftDigits *int // If defined it will shift cost digits before applying rouding (eg: convert from Eur->cents), -1 to use general config ones @@ -1089,6 +1090,7 @@ type AttrExportCdrsToFile struct { ExportTemplate *string // Exported fields template <""|fld1,fld2|*xml:instance_name> DataUsageMultiplyFactor *float64 // Multiply data usage before export (eg: convert from KBytes to Bytes) SMSUsageMultiplyFactor *float64 // Multiply sms usage before export (eg: convert from SMS unit to call duration for some billing systems) + MMSUsageMultiplyFactor *float64 // Multiply mms usage before export (eg: convert from MMS unit to call duration for some billing systems) GenericUsageMultiplyFactor *float64 // Multiply generic usage before export (eg: convert from GENERIC unit to call duration for some billing systems) CostMultiplyFactor *float64 // Multiply the cost before export, eg: apply VAT CostShiftDigits *int // If defined it will shift cost digits before applying rouding (eg: convert from Eur->cents), -1 to use general config ones diff --git a/utils/consts.go b/utils/consts.go index 345408b29..6f413f1f3 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -148,6 +148,7 @@ const ( HDR_VAL_SEP = "/" MONETARY = "*monetary" SMS = "*sms" + MMS = "*mms" GENERIC = "*generic" DATA = "*data" VOICE = "*voice" From 0f2c803f028b03b0944ae82ed5d0e099e0854f51 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 19 Jan 2016 22:35:19 +0200 Subject: [PATCH 3/6] conditional language documentation --- utils/cond_loader.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/utils/cond_loader.go b/utils/cond_loader.go index 6af762673..bebc35aef 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -1,5 +1,29 @@ package utils +/* +When an action is using *conditional_ form before the execution the engine is checking the ExtraParameters field for condition filter, loads it and checks all the balances in the account for one that is satisfying the condition. If one is fond the action is executed, otherwise it will do nothing for this account. + +The condition syntax is a json encoded document similar to mongodb query language. + +Examples: +- {"Weight":{"*gt":50}} checks for a balance with weight greater than 50 +- {"*or":[{"Value":{"*eq":0}},{"Value":{"*gte":100}}] checks for a balance with value equal to 0 or equal or highr than 100 + +Available operators: +- *eq: equal +- *gt: greater than +- *gte: greater or equal than +- *lt: less then +- *lte: less or equal than +- *or: logical or +- *and: logical and +- *has: logical has + +Equal (*eq) and local and (*and) operators are implicit for shortcuts. In this way: + +{"*and":[{"Value":{"*eq":3}},{"Weight":{"*eq":10}}]} is equivalent to: {"Value":3, "Weight":10}. +*/ + import ( "encoding/json" "fmt" @@ -13,7 +37,7 @@ const ( CondGTE = "*gte" CondLT = "*lt" CondLTE = "*lte" - CondEXP = "*exp" + //CondEXP = "*exp" CondOR = "*or" CondAND = "*and" CondHAS = "*has" From 327d1b6de7d6b00e83ba187aef05b84d13d46e77 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 19 Jan 2016 22:50:35 +0200 Subject: [PATCH 4/6] added *exp condition operator (expired) --- utils/cond_loader.go | 21 ++++++++++++++++++++- utils/cond_loader_test.go | 29 +++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/utils/cond_loader.go b/utils/cond_loader.go index bebc35aef..18e3be116 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -29,6 +29,7 @@ import ( "fmt" "reflect" "strings" + "time" ) const ( @@ -37,7 +38,7 @@ const ( CondGTE = "*gte" CondLT = "*lt" CondLTE = "*lte" - //CondEXP = "*exp" + CondEXP = "*exp" CondOR = "*or" CondAND = "*and" CondHAS = "*has" @@ -134,6 +135,24 @@ func (ov *operatorValue) checkStruct(o interface{}) (bool, error) { } return true, nil } + // date conversion + if ov.operator == CondEXP { + var expDate time.Time + var ok bool + if expDate, ok = o.(time.Time); !ok { + return false, NewErrInvalidArgument(o) + } + var expired bool + if expired, ok = ov.value.(bool); !ok { + return false, NewErrInvalidArgument(ov.value) + } + if expired { // check for expiration + return !expDate.IsZero() && expDate.Before(time.Now()), nil + } else { // check not expired + return expDate.IsZero() || expDate.After(time.Now()), nil + } + } + // float conversion var of, vf float64 var ok bool diff --git a/utils/cond_loader_test.go b/utils/cond_loader_test.go index 908c29b19..996d12d27 100644 --- a/utils/cond_loader_test.go +++ b/utils/cond_loader_test.go @@ -3,6 +3,7 @@ package utils import ( "strings" "testing" + "time" ) func TestCondLoader(t *testing.T) { @@ -28,13 +29,15 @@ func TestCondLoader(t *testing.T) { func TestCondKeyValue(t *testing.T) { o := struct { - Test string - Field float64 - Other bool + Test string + Field float64 + Other bool + ExpDate time.Time }{ - Test: "test", - Field: 6.0, - Other: true, + Test: "test", + Field: 6.0, + Other: true, + ExpDate: time.Date(2016, 1, 19, 20, 47, 0, 0, time.UTC), } cl := &CondLoader{} err := cl.Parse(`{"Test":"test"}`) @@ -93,6 +96,20 @@ func TestCondKeyValue(t *testing.T) { if check, err := cl.Check(o); !check || err != nil { t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) } + err = cl.Parse(`{"ExpDate":{"*exp":true}}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); !check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } + err = cl.Parse(`{"ExpDate":{"*exp":false}}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } } func TestCondKeyValuePointer(t *testing.T) { From 38e79d5ec9b707d9089dff5dbeb6303d4d4b883b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 19 Jan 2016 22:51:40 +0200 Subject: [PATCH 5/6] added *exp to the documentation --- utils/cond_loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/cond_loader.go b/utils/cond_loader.go index 18e3be116..e03cba8d3 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -15,6 +15,7 @@ Available operators: - *gte: greater or equal than - *lt: less then - *lte: less or equal than +- *exp: expired - *or: logical or - *and: logical and - *has: logical has From 31302da05546b1b7e9b4a38ba9abc08d5951d9ae Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 20 Jan 2016 12:36:19 +0200 Subject: [PATCH 6/6] small comment change --- utils/cond_loader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/cond_loader.go b/utils/cond_loader.go index e03cba8d3..5ff35350c 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -18,7 +18,7 @@ Available operators: - *exp: expired - *or: logical or - *and: logical and -- *has: logical has +- *has: receives a list of elements and checks that the elements are present in the specified field (also a list) Equal (*eq) and local and (*and) operators are implicit for shortcuts. In this way: