mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Adding costDigitsShift formatting
This commit is contained in:
@@ -53,7 +53,11 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
if len(exportId) == 0 {
|
||||
exportId = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
}
|
||||
roundDecimals := attr.RoundingDecimals
|
||||
costShiftDigits := attr.CostShiftDigits
|
||||
if costShiftDigits != 0 {
|
||||
costShiftDigits = self.Config.CdreCostShiftDigits
|
||||
}
|
||||
roundDecimals := attr.RoundDecimals
|
||||
if roundDecimals == 0 {
|
||||
roundDecimals = self.Config.RoundingDecimals
|
||||
}
|
||||
@@ -99,7 +103,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
csvWriter := cdre.NewCsvCdrWriter(fileOut, roundDecimals, maskDestId, maskLen, exportedFields)
|
||||
csvWriter := cdre.NewCsvCdrWriter(fileOut, costShiftDigits, roundDecimals, maskDestId, maskLen, exportedFields)
|
||||
exportedIds := make([]string, 0)
|
||||
unexportedIds := make(map[string]string)
|
||||
for _, cdr := range cdrs {
|
||||
@@ -132,7 +136,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals, maskDestId, maskLen)
|
||||
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, costShiftDigits, roundDecimals, maskDestId, maskLen)
|
||||
exportedIds := make([]string, 0)
|
||||
unexportedIds := make(map[string]string)
|
||||
for _, cdr := range cdrs {
|
||||
|
||||
16
cdre/csv.go
16
cdre/csv.go
@@ -26,15 +26,15 @@ import (
|
||||
)
|
||||
|
||||
type CsvCdrWriter struct {
|
||||
writer *csv.Writer
|
||||
roundDecimals int // Round floats like Cost using this number of decimals
|
||||
maskDestId string
|
||||
maskLen int
|
||||
exportedFields []*utils.RSRField // The fields exported, order important
|
||||
writer *csv.Writer
|
||||
costShiftDigits, roundDecimals int // Round floats like Cost using this number of decimals
|
||||
maskDestId string
|
||||
maskLen int
|
||||
exportedFields []*utils.RSRField // The fields exported, order important
|
||||
}
|
||||
|
||||
func NewCsvCdrWriter(writer io.Writer, roundDecimals int, maskDestId string, maskLen int, exportedFields []*utils.RSRField) *CsvCdrWriter {
|
||||
return &CsvCdrWriter{csv.NewWriter(writer), roundDecimals, maskDestId, maskLen, exportedFields}
|
||||
func NewCsvCdrWriter(writer io.Writer, costShiftDigits, roundDecimals int, maskDestId string, maskLen int, exportedFields []*utils.RSRField) *CsvCdrWriter {
|
||||
return &CsvCdrWriter{csv.NewWriter(writer), costShiftDigits, roundDecimals, maskDestId, maskLen, exportedFields}
|
||||
}
|
||||
|
||||
func (csvwr *CsvCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
@@ -42,7 +42,7 @@ func (csvwr *CsvCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
for idx, fld := range csvwr.exportedFields {
|
||||
var fldVal string
|
||||
if fld.Id == utils.COST {
|
||||
fldVal = cdr.FormatCost(csvwr.roundDecimals)
|
||||
fldVal = cdr.FormatCost(csvwr.costShiftDigits, csvwr.roundDecimals)
|
||||
} else if fld.Id == utils.DESTINATION {
|
||||
fldVal = cdr.ExportFieldValue(utils.DESTINATION)
|
||||
if len(csvwr.maskDestId) != 0 && csvwr.maskLen > 0 && engine.CachedDestHasPrefix(csvwr.maskDestId, fldVal) {
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestCsvCdrWriter(t *testing.T) {
|
||||
writer := &bytes.Buffer{}
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
exportedFields := append(cfg.CdreExportedFields, &utils.RSRField{Id: "extra3"}, &utils.RSRField{Id: "dummy_extra"}, &utils.RSRField{Id: "extra1"})
|
||||
csvCdrWriter := NewCsvCdrWriter(writer, 4, "", -1, exportedFields)
|
||||
csvCdrWriter := NewCsvCdrWriter(writer, 0, 4, "", -1, exportedFields)
|
||||
ratedCdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
|
||||
|
||||
@@ -47,38 +47,40 @@ const (
|
||||
META_DURCDRS = "cdrs_duration"
|
||||
META_COSTCDRS = "cdrs_cost"
|
||||
META_MASKDESTINATION = "mask_destination"
|
||||
META_FORMATCOST = "format_cost"
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string,
|
||||
roundDecimals int, maskDestId string, maskLen int) (*FixedWidthCdrWriter, error) {
|
||||
costShiftDigits, roundDecimals int, maskDestId string, maskLen int) (*FixedWidthCdrWriter, error) {
|
||||
return &FixedWidthCdrWriter{
|
||||
logDb: logDb,
|
||||
writer: outFile,
|
||||
exportTemplate: exportTpl,
|
||||
exportId: exportId,
|
||||
roundDecimals: roundDecimals,
|
||||
maskDestId: maskDestId,
|
||||
maskLen: maskLen,
|
||||
header: &bytes.Buffer{},
|
||||
content: &bytes.Buffer{},
|
||||
trailer: &bytes.Buffer{}}, nil
|
||||
logDb: logDb,
|
||||
writer: outFile,
|
||||
exportTemplate: exportTpl,
|
||||
exportId: exportId,
|
||||
costShiftDigits: costShiftDigits,
|
||||
roundDecimals: roundDecimals,
|
||||
maskDestId: maskDestId,
|
||||
maskLen: maskLen,
|
||||
header: &bytes.Buffer{},
|
||||
content: &bytes.Buffer{},
|
||||
trailer: &bytes.Buffer{}}, nil
|
||||
}
|
||||
|
||||
type FixedWidthCdrWriter struct {
|
||||
logDb engine.LogStorage // Used to extract cost_details if these are requested
|
||||
writer io.Writer
|
||||
exportTemplate *config.CgrXmlCdreFwCfg
|
||||
exportId string // Unique identifier or this export
|
||||
roundDecimals int
|
||||
maskDestId string
|
||||
maskLen int
|
||||
header, content, trailer *bytes.Buffer
|
||||
firstCdrATime, lastCdrATime time.Time
|
||||
numberOfRecords int
|
||||
totalDuration time.Duration
|
||||
totalCost float64
|
||||
logDb engine.LogStorage // Used to extract cost_details if these are requested
|
||||
writer io.Writer
|
||||
exportTemplate *config.CgrXmlCdreFwCfg
|
||||
exportId string // Unique identifier or this export
|
||||
costShiftDigits, roundDecimals int
|
||||
maskDestId string
|
||||
maskLen int
|
||||
header, content, trailer *bytes.Buffer
|
||||
firstCdrATime, lastCdrATime time.Time
|
||||
numberOfRecords int
|
||||
totalDuration time.Duration
|
||||
totalCost float64
|
||||
}
|
||||
|
||||
// Return Json marshaled callCost attached to
|
||||
@@ -116,7 +118,7 @@ func (fwv *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layo
|
||||
return "", err
|
||||
}
|
||||
case utils.COST:
|
||||
cdrVal = cdr.FormatCost(fwv.roundDecimals)
|
||||
cdrVal = cdr.FormatCost(fwv.costShiftDigits, fwv.roundDecimals)
|
||||
case utils.SETUP_TIME:
|
||||
cdrVal = cdr.SetupTime.Format(layout)
|
||||
case utils.ANSWER_TIME: // Format time based on layout
|
||||
|
||||
@@ -93,6 +93,7 @@ type CGRConfig struct {
|
||||
CdreCdrFormat string // Format of the exported CDRs. <csv>
|
||||
CdreMaskDestId string // Id of the destination list to be masked in CDRs
|
||||
CdreMaskLength int // Number of digits to mask in the destination suffix if destination is in the MaskDestinationdsId
|
||||
CdreCostShiftDigits int // Shift digits in the cost on export (eg: convert from EUR to cents)
|
||||
CdreDir string // Path towards exported cdrs directory
|
||||
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
|
||||
CdreFWXmlTemplate *CgrXmlCdreFwCfg // Use this configuration as export template in case of fixed fields length
|
||||
@@ -201,6 +202,7 @@ func (self *CGRConfig) setDefaults() error {
|
||||
self.CdreCdrFormat = "csv"
|
||||
self.CdreMaskDestId = ""
|
||||
self.CdreMaskLength = 0
|
||||
self.CdreCostShiftDigits = 0
|
||||
self.CdreDir = "/var/log/cgrates/cdr/cdre"
|
||||
self.CdrcEnabled = false
|
||||
self.CdrcCdrs = utils.INTERNAL
|
||||
@@ -498,6 +500,9 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("cdre", "mask_length"); hasOpt {
|
||||
cfg.CdreMaskLength, _ = c.GetInt("cdre", "mask_length")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "cost_shift_digits"); hasOpt {
|
||||
cfg.CdreCostShiftDigits, _ = c.GetInt("cdre", "cost_shift_digits")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "export_template"); hasOpt { // Load configs for csv normally from template, fixed_width from xml file
|
||||
exportTemplate, _ := c.GetString("cdre", "export_template")
|
||||
if cfg.CdreCdrFormat != utils.CDRE_FIXED_WIDTH { // Csv most likely
|
||||
|
||||
@@ -84,6 +84,7 @@ func TestDefaults(t *testing.T) {
|
||||
eCfg.CdreCdrFormat = "csv"
|
||||
eCfg.CdreMaskDestId = ""
|
||||
eCfg.CdreMaskLength = 0
|
||||
eCfg.CdreCostShiftDigits = 0
|
||||
eCfg.CdreDir = "/var/log/cgrates/cdr/cdre"
|
||||
eCfg.CdrcEnabled = false
|
||||
eCfg.CdrcCdrs = utils.INTERNAL
|
||||
@@ -240,6 +241,7 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.CdreCdrFormat = "test"
|
||||
eCfg.CdreMaskDestId = "test"
|
||||
eCfg.CdreMaskLength = 99
|
||||
eCfg.CdreCostShiftDigits = 99
|
||||
eCfg.CdreExportedFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.CdreDir = "test"
|
||||
eCfg.CdrcEnabled = true
|
||||
|
||||
@@ -51,6 +51,7 @@ mediator = test # Address where to reach the Mediator. Empty for disabling me
|
||||
cdr_format = test # Exported CDRs format <csv>
|
||||
mask_destination_id = test # Destination id containing called addresses to be masked on export
|
||||
mask_length = 99 # Length of the destination suffix to be masked
|
||||
cost_shift_digits = 99 # Shift the number of cost
|
||||
export_dir = test # Path where the exported CDRs will be placed
|
||||
export_template = test # List of fields in the exported CDRs
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
# cdr_format = csv # Exported CDRs format <csv>
|
||||
# mask_destination_id = # Destination id containing called addresses to be masked on export
|
||||
# mask_length = 0 # Length of the destination suffix to be masked
|
||||
# cost_shift_digits = 0 # Shift cost on export with the number of digits digits defined here (eg: convert from Eur to cent).
|
||||
# export_dir = /var/log/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
|
||||
# export_template = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,tor,account,subject,destination,setup_time,answer_time,duration,cost
|
||||
# Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
|
||||
@@ -784,7 +784,7 @@ func (self *SQLStorage) GetStoredCdrs(cgrIds, runIds, cdrHosts, cdrSources, reqT
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(extraFields, &extraFieldsMp); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("JSON unmarshal error for cgrid: %s, runid: %s, error: %s", cgrid, runid, err.Error())
|
||||
}
|
||||
storCdr := &utils.StoredCdr{
|
||||
CgrId: cgrid, AccId: accid, CdrHost: cdrhost, CdrSource: cdrsrc, ReqType: reqtype, Direction: direction, Tenant: tenant,
|
||||
|
||||
@@ -320,7 +320,8 @@ type AttrExpFileCdrs struct {
|
||||
ExportId string // Optional exportid
|
||||
ExportFileName string // If provided the output filename will be set to this
|
||||
ExportTemplate string // Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
RoundingDecimals int // Overwrite configured roundDecimals with this dynamically
|
||||
CostShiftDigits int // If defined it will shift cost digits before applying rouding (eg: convert from Eur->cents)
|
||||
RoundDecimals int // Overwrite configured roundDecimals with this dynamically
|
||||
MaskDestinationId string // Overwrite configured MaskDestId
|
||||
MaskLength int // Overwrite configured MaskLength
|
||||
CgrIds []string // If provided, it will filter based on the cgrids present in list
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -135,8 +136,12 @@ func (storedCdr *StoredCdr) GetExtraFields() map[string]string {
|
||||
}
|
||||
|
||||
// Return cost as string, formated with number of decimals configured
|
||||
func (storedCdr *StoredCdr) FormatCost(roundDecimals int) string {
|
||||
return strconv.FormatFloat(storedCdr.Cost, 'f', roundDecimals, 64)
|
||||
func (storedCdr *StoredCdr) FormatCost(shiftDecimals, roundDecimals int) string {
|
||||
cost := storedCdr.Cost
|
||||
if shiftDecimals != 0 {
|
||||
cost = cost * math.Pow10(shiftDecimals)
|
||||
}
|
||||
return strconv.FormatFloat(cost, 'f', roundDecimals, 64)
|
||||
}
|
||||
|
||||
func (storedCdr *StoredCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*StoredCdr, error) {
|
||||
|
||||
@@ -179,11 +179,15 @@ func TestExportFieldValue(t *testing.T) {
|
||||
|
||||
func TestFormatCost(t *testing.T) {
|
||||
cdr := StoredCdr{Cost: 1.01}
|
||||
if cdr.FormatCost(4) != "1.0100" {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(4))
|
||||
if cdr.FormatCost(0, 4) != "1.0100" {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(0, 4))
|
||||
}
|
||||
cdr = StoredCdr{Cost: 1.01001}
|
||||
if cdr.FormatCost(4) != "1.0100" {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(4))
|
||||
if cdr.FormatCost(0, 4) != "1.0100" {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(0, 4))
|
||||
}
|
||||
cdr = StoredCdr{Cost: 1.01001}
|
||||
if cdr.FormatCost(2, 0) != "101" {
|
||||
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 0))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user