// Write one CDR and test it's results only for content buffer func TestWriteCdr(t *testing.T) { wrBuf := &bytes.Buffer{} logDb, _ := engine.NewMapStorage() cfg, _ := config.NewDefaultCGRConfig() fixedWidth := utils.CDRE_FIXED_WIDTH exportTpl := &config.CgrXmlCdreCfg{ CdrFormat: &fixedWidth, Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds}, Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds}, Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds}, } cdr := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), TOR: utils.VOICE, OrderId: 1, AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, } cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify) if err != nil { t.Error(err) } eHeader := "10 VOI0000007111308420024031415390001 \n" eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.34570\n" eTrailer := "90 VOI0000000000100000010071113084260071113084200 \n" if err := cdre.writeOut(wrBuf); err != nil { t.Error(err) } allOut := wrBuf.String() eAllOut := eHeader + eContentOut + eTrailer if math.Mod(float64(len(allOut)), 145) != 0 { t.Error("Unexpected export content length", len(allOut)) } else if len(allOut) != len(eAllOut) { t.Errorf("Output does not match expected length. Have output %q, expecting: %q", allOut, eAllOut) } // Test stats if !cdre.firstCdrATime.Equal(cdr.AnswerTime) { t.Error("Unexpected firstCdrATime in stats: ", cdre.firstCdrATime) } else if !cdre.lastCdrATime.Equal(cdr.AnswerTime) { t.Error("Unexpected lastCdrATime in stats: ", cdre.lastCdrATime) } else if cdre.numberOfRecords != 1 { t.Error("Unexpected number of records in the stats: ", cdre.numberOfRecords) } else if cdre.totalDuration != cdr.Usage { t.Error("Unexpected total duration in the stats: ", cdre.totalDuration) } else if cdre.totalCost != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) { t.Error("Unexpected total cost in the stats: ", cdre.totalCost) } if cdre.FirstOrderId() != 1 { t.Error("Unexpected FirstOrderId", cdre.FirstOrderId()) } if cdre.LastOrderId() != 1 { t.Error("Unexpected LastOrderId", cdre.LastOrderId()) } if cdre.TotalCost() != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) { t.Error("Unexpected TotalCost: ", cdre.TotalCost()) } }
func SureTaxProcessCdr(cdr *CDR) error { stCfg := config.CgrConfig().SureTaxCfg() if stCfg == nil { return errors.New("Invalid SureTax configuration") } if sureTaxClient == nil { // First time used, init the client here tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: config.CgrConfig().HttpSkipTlsVerify}, } sureTaxClient = &http.Client{Transport: tr} } req, err := NewSureTaxRequest(cdr, stCfg) if err != nil { return err } jsnContent, err := json.Marshal(req) if err != nil { return err } resp, err := sureTaxClient.Post(stCfg.Url, "application/json", bytes.NewBuffer(jsnContent)) if err != nil { return err } defer resp.Body.Close() respBody, err := ioutil.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode > 299 { return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode) } var respFull SureTaxResponse if err := json.Unmarshal(respBody, &respFull); err != nil { return err } var stResp STResponse if err := json.Unmarshal([]byte(respFull.D), &stResp); err != nil { return err } if stResp.ResponseCode != "9999" { cdr.ExtraInfo = stResp.HeaderMessage return nil // No error because the request was processed by SureTax, error will be in the ExtraInfo } // Write cost to CDR totalTax, err := strconv.ParseFloat(stResp.TotalTax, 64) if err != nil { cdr.ExtraInfo = err.Error() } if !stCfg.IncludeLocalCost { cdr.Cost = utils.Round(totalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE) } else { cdr.Cost = utils.Round(cdr.Cost+totalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE) } // Add response into extra fields to be available for later review cdr.ExtraFields[utils.META_SURETAX] = respFull.D return nil }
func SureTaxProcessCdr(cdr *StoredCdr) error { stCfg := config.CgrConfig().SureTaxCfg() if stCfg == nil { return errors.New("Invalid SureTax configuration") } if sureTaxClient == nil { // First time used, init the client here tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: config.CgrConfig().HttpSkipTlsVerify}, } sureTaxClient = &http.Client{Transport: tr} } req, err := NewSureTaxRequest(cdr, stCfg) if err != nil { return err } body, err := json.Marshal(req) if err != nil { return err } utils.Logger.Debug(fmt.Sprintf("###SureTax NewSureTaxRequest: %+v, ItemList: %+v\n", req, req.ItemList[0])) resp, err := sureTaxClient.Post(stCfg.Url, "application/json", bytes.NewBuffer(body)) if err != nil { return err } defer resp.Body.Close() respBody, err := ioutil.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode > 299 { return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode) } var stResp SureTaxResponse if err := json.Unmarshal(respBody, &stResp); err != nil { return err } utils.Logger.Debug(fmt.Sprintf("###SureTax received response: %+v\n", stResp)) if stResp.ResponseCode != 9999 { cdr.ExtraInfo = stResp.HeaderMessage return nil // No error because the request was processed by SureTax, error will be in the ExtraInfo } // Write cost to CDR if !stCfg.IncludeLocalCost { cdr.Cost = utils.Round(stResp.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE) } else { cdr.Cost = utils.Round(cdr.Cost+stResp.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE) } // Add response into extra fields to be available for later review cdr.ExtraFields[utils.META_SURETAX] = string(respBody) return nil }
// Useful for CDR generation func (kev KamEvent) ParseEventValue(rsrFld *utils.RSRField, timezone string) string { sTime, _ := kev.GetSetupTime(utils.META_DEFAULT, config.CgrConfig().DefaultTimezone) aTime, _ := kev.GetAnswerTime(utils.META_DEFAULT, config.CgrConfig().DefaultTimezone) duration, _ := kev.GetDuration(utils.META_DEFAULT) switch rsrFld.Id { case utils.CGRID: return rsrFld.ParseValue(kev.GetCgrId(timezone)) case utils.TOR: return rsrFld.ParseValue(utils.VOICE) case utils.ACCID: return rsrFld.ParseValue(kev.GetUUID()) case utils.CDRHOST: return rsrFld.ParseValue(kev.GetOriginatorIP(utils.META_DEFAULT)) case utils.CDRSOURCE: return rsrFld.ParseValue(kev.GetCdrSource()) case utils.REQTYPE: return rsrFld.ParseValue(kev.GetReqType(utils.META_DEFAULT)) case utils.DIRECTION: return rsrFld.ParseValue(kev.GetDirection(utils.META_DEFAULT)) case utils.TENANT: return rsrFld.ParseValue(kev.GetTenant(utils.META_DEFAULT)) case utils.CATEGORY: return rsrFld.ParseValue(kev.GetCategory(utils.META_DEFAULT)) case utils.ACCOUNT: return rsrFld.ParseValue(kev.GetAccount(utils.META_DEFAULT)) case utils.SUBJECT: return rsrFld.ParseValue(kev.GetSubject(utils.META_DEFAULT)) case utils.DESTINATION: return rsrFld.ParseValue(kev.GetDestination(utils.META_DEFAULT)) case utils.SETUP_TIME: return rsrFld.ParseValue(sTime.String()) case utils.ANSWER_TIME: return rsrFld.ParseValue(aTime.String()) case utils.USAGE: return rsrFld.ParseValue(strconv.FormatFloat(utils.Round(duration.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64)) case utils.PDD: 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: return rsrFld.ParseValue("-1.0") default: return rsrFld.ParseValue(kev.GetExtraFields()[rsrFld.Id]) } }
func (incs Increments) GetTotalCost() float64 { cost := 0.0 for _, increment := range incs { cost += increment.GetCost() } return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) }
func (cc *CallCost) Round() { if len(cc.Timespans) == 0 || cc.Timespans[0] == nil { return } var totalCorrectionCost float64 for _, ts := range cc.Timespans { if len(ts.Increments) == 0 { continue // safe check } inc := ts.Increments[0] if inc.BalanceInfo.Monetary == nil || inc.Cost == 0 { // this is a unit payied timespan, nothing to round continue } cost := ts.CalculateCost() roundedCost := utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) correctionCost := roundedCost - cost //log.Print(cost, roundedCost, correctionCost) if correctionCost != 0 { ts.RoundIncrement = &Increment{ Cost: correctionCost, BalanceInfo: inc.BalanceInfo, } totalCorrectionCost += correctionCost ts.Cost += correctionCost } } cc.Cost += totalCorrectionCost }
// Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method) // from user's money balance. func (cd *CallDescriptor) debit(account *Account) (cc *CallCost, err error) { cc, err = cd.GetCost() cc.Timespans.Decompress() if err != nil { Logger.Err(fmt.Sprintf("<Rater> Error getting cost for account key %v: %v", cd.GetAccountKey(), err)) return } //Logger.Debug(fmt.Sprintf("<Rater> Attempting to debit from %v, value: %v", cd.GetAccountKey(), cc.Cost+cc.ConnectFee)) defer accountingStorage.SetAccount(account) //ub, _ := json.Marshal(account) //Logger.Debug(fmt.Sprintf("Account: %s", ub)) //cCost, _ := json.Marshal(cc) //Logger.Debug(fmt.Sprintf("CallCost: %s", cCost)) if cc.Cost != 0 || (cc.deductConnectFee && cc.GetConnectFee() != 0) { account.debitCreditBalance(cc, true) } cost := 0.0 // re-calculate call cost after balances if cc.deductConnectFee { // add back the connectFee cost += cc.GetConnectFee() } for _, ts := range cc.Timespans { cost += ts.getCost() cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals } cc.Cost = cost cc.Timespans.Compress() return }
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resErr error) (*KamAuthReply, error) { var err error kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers} if resErr != nil { kar.Error = resErr.Error() } if _, hasIt := kev[KAM_TR_INDEX]; !hasIt { return nil, utils.NewErrMandatoryIeMissing(KAM_TR_INDEX, "") } if kar.TransactionIndex, err = strconv.Atoi(kev[KAM_TR_INDEX]); err != nil { return nil, err } if _, hasIt := kev[KAM_TR_LABEL]; !hasIt { return nil, utils.NewErrMandatoryIeMissing(KAM_TR_LABEL, "") } if kar.TransactionLabel, err = strconv.Atoi(kev[KAM_TR_LABEL]); err != nil { return nil, err } if maxSessionTime != -1 { // Convert maxSessionTime from nanoseconds into seconds maxSessionDur := time.Duration(maxSessionTime) maxSessionTime = maxSessionDur.Seconds() } kar.MaxSessionTime = int(utils.Round(maxSessionTime, 0, utils.ROUNDING_MIDDLE)) return kar, nil }
func (PDD *PDDMetric) GetValue() float64 { if PDD.count == 0 { return STATS_NA } val := PDD.sum.Seconds() / PDD.count return utils.Round(val, globalRoundingDecimals, utils.ROUNDING_MIDDLE) }
// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:57:26Z"' func TestDmtAgentSendCCRUpdate2(t *testing.T) { cdr := &engine.CDR{CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, OriginID: "testccr1", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", Supplier: "SUPPL1", SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), RunID: utils.DEFAULT_RUNID, Usage: time.Duration(600) * time.Second, PDD: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "*****@*****.**"}, } ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) m, err := ccr.AsDiameterMessage() if err != nil { t.Error(err) } if err := dmtClient.SendMessage(m); err != nil { t.Error(err) } time.Sleep(time.Duration(*waitRater) * time.Millisecond) msg := dmtClient.ReceivedMessage(rplyTimeout) if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { t.Error(err) } else if len(avps) == 0 { t.Error("Granted-Service-Unit not found") } else if strCCTime := avpValAsString(avps[0]); strCCTime != "300" { t.Errorf("Expecting 300, received: %s", strCCTime) } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} eAcntVal := 9.002800 if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error(err) } else if utils.Round(acnt.BalanceMap[utils.MONETARY].GetTotalValue(), 5, utils.ROUNDING_MIDDLE) != eAcntVal { t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) } }
// Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method) // from user's money balance. func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) (cc *CallCost, err error) { if cd.TimeEnd.Sub(cd.TimeStart) == 0 { return cd.CreateCallCost(), nil } if !dryRun { defer accountingStorage.SetAccount(account) } if cd.TOR == "" { cd.TOR = utils.VOICE } //log.Printf("Debit CD: %+v", cd) cc, err = account.debitCreditBalance(cd, !dryRun, dryRun, goNegative) //log.Printf("HERE: %+v %v", cc, err) if err != nil { utils.Logger.Err(fmt.Sprintf("<Rater> Error getting cost for account key <%s>: %s", cd.GetAccountKey(), err.Error())) return nil, err } cost := 0.0 // calculate call cost after balances if cc.deductConnectFee { // add back the connectFee cost += cc.GetConnectFee() } for _, ts := range cc.Timespans { cost += ts.getCost() cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals } cc.Cost = cost cc.Timespans.Compress() //log.Printf("OUT CC: ", cc) return }
func (acd *ACDMetric) GetValue() float64 { if acd.count == 0 { return STATS_NA } val := acd.sum.Seconds() / acd.count return utils.Round(val, globalRoundingDecimals, utils.ROUNDING_MIDDLE) }
func (b *Balance) SetValue(amount float64) { b.Value = amount b.Value = utils.Round(b.GetValue(), globalRoundingDecimals, utils.ROUNDING_MIDDLE) b.dirty = true // publish event accountId := "" allowNegative := "" disabled := "" if b.account != nil { accountId = b.account.Id allowNegative = strconv.FormatBool(b.account.AllowNegative) disabled = strconv.FormatBool(b.account.Disabled) } Publish(CgrEvent{ "EventName": utils.EVT_ACCOUNT_BALANCE_MODIFIED, "Uuid": b.Uuid, "Id": b.Id, "Value": strconv.FormatFloat(b.Value, 'f', -1, 64), "ExpirationDate": b.ExpirationDate.String(), "Weight": strconv.FormatFloat(b.Weight, 'f', -1, 64), "DestinationIds": b.DestinationIds, "RatingSubject": b.RatingSubject, "Category": b.Category, "SharedGroup": b.SharedGroup, "TimingIDs": b.TimingIDs, "Account": accountId, "AccountAllowNegative": allowNegative, "AccountDisabled": disabled, }) }
func (acc *ACCMetric) GetValue() float64 { if acc.count == 0 { return STATS_NA } val := acc.sum / acc.count return utils.Round(val, globalRoundingDecimals, utils.ROUNDING_MIDDLE) }
// Handle various meta functions used in header/trailer func (cdre *CdrExporter) metaHandler(tag, arg string) (string, error) { switch tag { case META_EXPORTID: return cdre.exportId, nil case META_TIMENOW: return time.Now().Format(arg), nil case META_FIRSTCDRATIME: return cdre.firstCdrATime.Format(arg), nil case META_LASTCDRATIME: return cdre.lastCdrATime.Format(arg), nil case META_NRCDRS: return strconv.Itoa(cdre.numberOfRecords), nil case META_DURCDRS: emulatedCdr := &engine.CDR{ToR: utils.VOICE, Usage: cdre.totalDuration} return emulatedCdr.FormatUsage(arg), nil 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 case META_DATAUSAGE: emulatedCdr := &engine.CDR{ToR: utils.DATA, Usage: cdre.totalDataUsage} return emulatedCdr.FormatUsage(arg), nil case META_COSTCDRS: return strconv.FormatFloat(utils.Round(cdre.totalCost, cdre.cgrPrecision, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil default: return "", fmt.Errorf("Unsupported METATAG: %s", tag) } }
func (asr *ASRMetric) GetValue() float64 { if asr.count == 0 { return STATS_NA } val := asr.answered / asr.count * 100 return utils.Round(val, globalRoundingDecimals, utils.ROUNDING_MIDDLE) }
// Formats usage on export func (cdr *CDR) FormatUsage(layout string) string { 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 { default: return strconv.FormatFloat(float64(cdr.Usage.Nanoseconds())/1000000000, 'f', -1, 64) } }
func (bc Balances) GetTotalValue() (total float64) { for _, b := range bc { if !b.IsExpired() && b.IsActive() { total += b.GetValue() } } total = utils.Round(total, globalRoundingDecimals, utils.ROUNDING_MIDDLE) return }
func (cd *CallDescriptor) getCost() (*CallCost, error) { // check for 0 duration if cd.GetDuration() == 0 { cc := cd.CreateCallCost() // add RatingInfo err := cd.LoadRatingPlans() if err == nil && len(cd.RatingInfos) > 0 { ts := &TimeSpan{ TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd, } ts.setRatingInfo(cd.RatingInfos[0]) cc.Timespans = append(cc.Timespans, ts) } return cc, nil } if cd.DurationIndex < cd.TimeEnd.Sub(cd.TimeStart) { cd.DurationIndex = cd.TimeEnd.Sub(cd.TimeStart) } if cd.TOR == "" { cd.TOR = utils.VOICE } err := cd.LoadRatingPlans() //log.Print("ERR: ", err) //log.Print("RI: ", utils.ToJSON(cd.RatingInfos)) if err != nil { //utils.Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans() cost := 0.0 for i, ts := range timespans { ts.createIncrementsSlice() // only add connect fee if this is the first/only call cost request //log.Printf("Interval: %+v", ts.RateInterval.Timing) if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } cost += ts.CalculateCost() } //startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.Category)) cc := cd.CreateCallCost() cc.Cost = cost cc.Timespans = timespans // global rounding roundingDecimals, roundingMethod := cc.GetLongestRounding() cc.Cost = utils.Round(cc.Cost, roundingDecimals, roundingMethod) //utils.Logger.Info(fmt.Sprintf("<Rater> Get Cost: %s => %v", cd.GetKey(), cc)) cc.Timespans.Compress() cc.UpdateRatedUsage() return cc, err }
// Init a new request to be sent out to SureTax func NewSureTaxRequest(cdr *StoredCdr, stCfg *config.SureTaxCfg) (*SureTaxRequest, error) { if stCfg == nil { return nil, errors.New("Invalid SureTax config.") } aTimeLoc := cdr.AnswerTime.In(stCfg.Timezone) revenue := utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE) unts, err := strconv.ParseInt(cdr.FieldsAsString(stCfg.Units), 10, 64) if err != nil { return nil, err } taxExempt := []string{} definedTaxExtempt := cdr.FieldsAsString(stCfg.TaxExemptionCodeList) if len(definedTaxExtempt) != 0 { taxExempt = strings.Split(cdr.FieldsAsString(stCfg.TaxExemptionCodeList), ",") } stReq := new(STRequest) stReq.ClientNumber = stCfg.ClientNumber stReq.BusinessUnit = stCfg.BusinessUnit stReq.ValidationKey = stCfg.ValidationKey stReq.DataYear = strconv.Itoa(aTimeLoc.Year()) stReq.DataMonth = strconv.Itoa(int(aTimeLoc.Month())) stReq.TotalRevenue = revenue stReq.ReturnFileCode = stCfg.ReturnFileCode stReq.ClientTracking = cdr.FieldsAsString(stCfg.ClientTracking) stReq.ResponseGroup = stCfg.ResponseGroup stReq.ResponseType = stCfg.ResponseType stReq.ItemList = []*STRequestItem{ &STRequestItem{ CustomerNumber: cdr.FieldsAsString(stCfg.CustomerNumber), OrigNumber: cdr.FieldsAsString(stCfg.OrigNumber), TermNumber: cdr.FieldsAsString(stCfg.TermNumber), BillToNumber: cdr.FieldsAsString(stCfg.BillToNumber), Zipcode: cdr.FieldsAsString(stCfg.Zipcode), Plus4: cdr.FieldsAsString(stCfg.Plus4), P2PZipcode: cdr.FieldsAsString(stCfg.P2PZipcode), P2PPlus4: cdr.FieldsAsString(stCfg.P2PPlus4), TransDate: aTimeLoc.Format("2006-01-02T15:04:05"), Revenue: revenue, Units: unts, UnitType: cdr.FieldsAsString(stCfg.UnitType), Seconds: int64(cdr.Usage.Seconds()), TaxIncludedCode: cdr.FieldsAsString(stCfg.TaxIncluded), TaxSitusRule: cdr.FieldsAsString(stCfg.TaxSitusRule), TransTypeCode: cdr.FieldsAsString(stCfg.TransTypeCode), SalesTypeCode: cdr.FieldsAsString(stCfg.SalesTypeCode), RegulatoryCode: stCfg.RegulatoryCode, TaxExemptionCodeList: taxExempt, }, } jsnContent, err := json.Marshal(stReq) if err != nil { return nil, err } return &SureTaxRequest{Request: string(jsnContent)}, nil }
// Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refund on session // manager debit loop where the cost cannot be recalculated) func (ts *TimeSpan) calculateCost() float64 { if ts.Increments.Length() == 0 { if ts.RateInterval == nil { return 0 } return ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) } else { cost := ts.Increments.GetTotalCost() return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) } }
func (cc *CallCost) updateCost() { cost := 0.0 if cc.deductConnectFee { // add back the connectFee cost += cc.GetConnectFee() } for _, ts := range cc.Timespans { ts.Cost = ts.CalculateCost() cost += ts.Cost cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals } cc.Cost = cost }
// Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refund on session // manager debit loop where the cost cannot be recalculated) func (ts *TimeSpan) getCost() float64 { if ts.Increments.Length() == 0 { if ts.RateInterval == nil { return 0 } cost := ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) return ts.Cost } else { return ts.Increments[0].Cost * float64(ts.Increments.Length()) } }
// Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refund on session // manager debit loop where the cost cannot be recalculated) func (ts *TimeSpan) getCost() float64 { if ts.Increments.Length() == 0 { if ts.RateInterval == nil { return 0 } cost := ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) return ts.Cost } else { cost := 0.0 // some increments may have 0 cost because of the max cost strategy for _, inc := range ts.Increments { cost += inc.Cost } if ts.RateInterval != nil && ts.RateInterval.Rating != nil { return utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) } else { return utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) } } }
func metaSum(m *diam.Message, argsTpl utils.RSRFields, passAtIndex, roundingDecimals int) (string, error) { valStr := composedFieldvalue(m, argsTpl, passAtIndex, nil) handlerArgs := strings.Split(valStr, utils.HandlerArgSep) var summed float64 for _, arg := range handlerArgs { val, err := strconv.ParseFloat(arg, 64) if err != nil { return "", err } summed += val } return strconv.FormatFloat(utils.Round(summed, roundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil }
// Init a new request to be sent out to SureTax func NewSureTaxRequest(clientNumber, validationKey string, timezone *time.Location, originationNrTpl, terminationNrTpl utils.RSRFields, cdr *StoredCdr) (*SureTaxRequest, error) { if clientNumber == "" { return nil, utils.NewErrMandatoryIeMissing("ClientNumber") } if validationKey == "" { return nil, utils.NewErrMandatoryIeMissing("ValidationKey") } aTime := cdr.AnswerTime.In(timezone) stReq := &SureTaxRequest{ClientNumber: clientNumber, ValidationKey: validationKey, DataYear: strconv.Itoa(aTime.Year()), DataMonth: strconv.Itoa(int(aTime.Month())), TotalRevenue: utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE), ReturnFileCode: "0", ClientTracking: cdr.CgrId, ResponseGroup: "03", ResponseType: "", ItemList: []*STRequestItem{ &STRequestItem{ OrigNumber: cdr.FieldsAsString(originationNrTpl), TermNumber: cdr.FieldsAsString(terminationNrTpl), BillToNumber: cdr.FieldsAsString(originationNrTpl), TransDate: aTime.Format("2006-01-02T15:04:05"), Revenue: utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE), Units: 1, UnitType: "00", Seconds: int64(utils.Round(cdr.Usage.Seconds(), 0, utils.ROUNDING_MIDDLE)), TaxIncludedCode: "0", TaxSitusRule: "1", TransTypeCode: "010101", SalesTypeCode: "R", RegulatoryCode: "01", TaxExemptionCodeList: []string{"00"}, }, }, } return stReq, nil }
// metaValueExponent will multiply the float value with the exponent provided. // Expects 2 arguments in template separated by | func metaValueExponent(m *diam.Message, argsTpl utils.RSRFields, roundingDecimals int) (string, error) { valStr := composedFieldvalue(m, argsTpl, 0, nil) handlerArgs := strings.Split(valStr, utils.HandlerArgSep) if len(handlerArgs) != 2 { return "", errors.New("Unexpected number of arguments") } val, err := strconv.ParseFloat(handlerArgs[0], 64) if err != nil { return "", err } exp, err := strconv.Atoi(handlerArgs[1]) if err != nil { return "", err } res := val * math.Pow10(exp) return strconv.FormatFloat(utils.Round(res, roundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil }
/* Creates a CallCost structure with the cost information calculated for the received CallDescriptor. */ func (cd *CallDescriptor) GetCost() (*CallCost, error) { if cd.DurationIndex < cd.TimeEnd.Sub(cd.TimeStart) { cd.DurationIndex = cd.TimeEnd.Sub(cd.TimeStart) } if cd.TOR == "" { cd.TOR = MINUTES } err := cd.LoadRatingPlans() if err != nil { Logger.Err(fmt.Sprintf("error getting cost for key %s: %v", cd.GetKey(cd.Subject), err)) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans() cost := 0.0 for i, ts := range timespans { // only add connect fee if this is the first/only call cost request if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } cost += ts.getCost() } //startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.Category)) cc := &CallCost{ Direction: cd.Direction, Category: cd.Category, Tenant: cd.Tenant, Account: cd.Account, Destination: cd.Destination, Subject: cd.Subject, Cost: cost, Timespans: timespans, deductConnectFee: cd.LoopIndex == 0, TOR: cd.TOR, } // global rounding roundingDecimals, roundingMethod := cc.GetLongestRounding() cc.Cost = utils.Round(cc.Cost, roundingDecimals, roundingMethod) //Logger.Info(fmt.Sprintf("<Rater> Get Cost: %s => %v", cd.GetKey(), cc)) cc.Timespans.Compress() return cc, err }
func (cd *CallDescriptor) getCost() (*CallCost, error) { // check for 0 duration if cd.TimeEnd.Sub(cd.TimeStart) == 0 { return cd.CreateCallCost(), nil } if cd.DurationIndex < cd.TimeEnd.Sub(cd.TimeStart) { cd.DurationIndex = cd.TimeEnd.Sub(cd.TimeStart) } if cd.TOR == "" { cd.TOR = utils.VOICE } err := cd.LoadRatingPlans() if err != nil { utils.Logger.Err(fmt.Sprintf("error getting cost for key <%s>: %s", cd.GetKey(cd.Subject), err.Error())) return &CallCost{Cost: -1}, err } timespans := cd.splitInTimeSpans() cost := 0.0 for i, ts := range timespans { // only add connect fee if this is the first/only call cost request //log.Printf("Interval: %+v", ts.RateInterval.Timing) if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } cost += ts.getCost() } //startIndex := len(fmt.Sprintf("%s:%s:%s:", cd.Direction, cd.Tenant, cd.Category)) cc := cd.CreateCallCost() cc.Cost = cost cc.Timespans = timespans // global rounding roundingDecimals, roundingMethod := cc.GetLongestRounding() cc.Cost = utils.Round(cc.Cost, roundingDecimals, roundingMethod) //utils.Logger.Info(fmt.Sprintf("<Rater> Get Cost: %s => %v", cd.GetKey(), cc)) cc.Timespans.Compress() return cc, err }
/* Creates a CallCost structure with the cost information calculated for the received CallDescriptor. */ func (cd *CallDescriptor) GetCost() (*CallCost, error) { cd.account = nil // make sure it's not cached cc, err := cd.getCost() if err != nil || cd.GetDuration() == 0 { return cc, err } cost := 0.0 for i, ts := range cc.Timespans { // only add connect fee if this is the first/only call cost request //log.Printf("Interval: %+v", ts.RateInterval.Timing) if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.Rating.ConnectFee } //log.Printf("TS: %+v", ts) // handle max cost maxCost, strategy := ts.RateInterval.GetMaxCost() ts.Cost = ts.calculateCost() cost += ts.Cost cd.MaxCostSoFar += cost //log.Print("Before: ", cost) if strategy != "" && maxCost > 0 { //log.Print("HERE: ", strategy, maxCost) if strategy == utils.MAX_COST_FREE && cd.MaxCostSoFar >= maxCost { cost = maxCost cd.MaxCostSoFar = maxCost } } //log.Print("Cost: ", cost) } cc.Cost = cost // global rounding roundingDecimals, roundingMethod := cc.GetLongestRounding() cc.Cost = utils.Round(cc.Cost, roundingDecimals, roundingMethod) return cc, nil }