SureTax data structures with tests for NewSureTaxRequest

This commit is contained in:
DanB
2015-10-04 18:23:32 +02:00
parent 9674f0ad31
commit 5a614711f2
9 changed files with 306 additions and 14 deletions

View File

@@ -238,7 +238,7 @@ type CGRConfig struct {
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
SureTax *SureTaxCfg // Load here SureTax configuration, as pointer so we can have runtime reloads in the future
SureTaxCfg *SureTaxCfg // Load here SureTax configuration, as pointer so we can have runtime reloads in the future
DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .json options
ConfigReloads map[string]chan struct{} // Signals to specific entities that a config reload should occur
// Cache defaults loaded from json and needing clones
@@ -804,15 +804,33 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
}
if jsnSureTaxCfg != nil {
self.SureTax = new(SureTaxCfg) // Reset previous values
self.SureTaxCfg = new(SureTaxCfg) // Reset previous values
if jsnSureTaxCfg.Url != nil {
self.SureTax.Url = *jsnSureTaxCfg.Url
self.SureTaxCfg.Url = *jsnSureTaxCfg.Url
}
if jsnSureTaxCfg.Client_number != nil {
self.SureTax.ClientNumber = *jsnSureTaxCfg.Client_number
self.SureTaxCfg.ClientNumber = *jsnSureTaxCfg.Client_number
}
if jsnSureTaxCfg.Validation_key != nil {
self.SureTax.ValidationKey = *jsnSureTaxCfg.Validation_key
self.SureTaxCfg.ValidationKey = *jsnSureTaxCfg.Validation_key
}
if jsnSureTaxCfg.Timezone != nil {
if self.SureTaxCfg.Timezone, err = time.LoadLocation(*jsnSureTaxCfg.Timezone); err != nil {
return err
}
}
if jsnSureTaxCfg.Include_local_cost != nil {
self.SureTaxCfg.IncludeLocalCost = *jsnSureTaxCfg.Include_local_cost
}
if jsnSureTaxCfg.Origination_number != nil {
if self.SureTaxCfg.OriginationNumber, err = utils.ParseRSRFields(*jsnSureTaxCfg.Origination_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnSureTaxCfg.Termination_number != nil {
if self.SureTaxCfg.TerminationNumber, err = utils.ParseRSRFields(*jsnSureTaxCfg.Termination_number, utils.INFIELD_SEP); err != nil {
return err
}
}
}
return nil

View File

@@ -283,6 +283,10 @@ const CGRATES_CFG_JSON = `
"url": "", // API url
"client_number": "", // client number, provided by SureTax
"validation_key": "", // validation key provided by SureTax
"timezone": "Local", // convert the time of the events to this timezone before sending request out <UTC|Local|$IANA_TZ_DB>
"include_local_cost": false, // sum local calculated cost with tax one in final cost
"origination_number": "Subject", // template extracting origination number out of StoredCdr
"termination_number": "Destination", // template extracting termination number out of StoredCdr
},
}`

View File

@@ -459,9 +459,13 @@ func TestDfMailerJsonCfg(t *testing.T) {
func TestDfSureTaxJsonCfg(t *testing.T) {
eCfg := &SureTaxJsonCfg{
Url: utils.StringPointer(""),
Client_number: utils.StringPointer(""),
Validation_key: utils.StringPointer(""),
Url: utils.StringPointer(""),
Client_number: utils.StringPointer(""),
Validation_key: utils.StringPointer(""),
Timezone: utils.StringPointer("Local"),
Include_local_cost: utils.BoolPointer(false),
Origination_number: utils.StringPointer("Subject"),
Termination_number: utils.StringPointer("Destination"),
}
if cfg, err := dfCgrJsonCfg.SureTaxJsonCfg(); err != nil {
t.Error(err)

View File

@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"time"
"github.com/cgrates/cgrates/utils"
)
@@ -31,7 +33,11 @@ type CdrReplicationCfg struct {
}
type SureTaxCfg struct {
Url string
ClientNumber string
ValidationKey string
Url string
ClientNumber string
ValidationKey string
Timezone *time.Location // Convert the time of the events to this timezone before sending request out
IncludeLocalCost bool
OriginationNumber utils.RSRFields // Concatenate all of them to get value
TerminationNumber utils.RSRFields // Concatenate all of them to get value
}

View File

@@ -256,7 +256,11 @@ type MailerJsonCfg struct {
// SureTax config section
type SureTaxJsonCfg struct {
Url *string // API url
Client_number *string // client number provided by SureTax
Validation_key *string // validation key provided by SureTax
Url *string // API url
Client_number *string // client number provided by SureTax
Validation_key *string // validation key provided by SureTax
Timezone *string // convert the time of the events to this timezone before sending request out
Include_local_cost *bool // sum local calculated cost with tax one
Origination_number *string // template extracting origination number out of StoredCdr
Termination_number *string // template extracting origination number out of StoredCdr
}

View File

@@ -135,6 +135,9 @@ func (storedCdr *StoredCdr) FormatUsage(layout string) string {
// Used to retrieve fields as string, primary fields are const labeled
func (storedCdr *StoredCdr) FieldAsString(rsrFld *utils.RSRField) string {
if rsrFld.IsStatic() { // Static values do not care about headers
return rsrFld.ParseValue("")
}
switch rsrFld.Id {
case utils.CGRID:
return rsrFld.ParseValue(storedCdr.CgrId)
@@ -189,6 +192,15 @@ func (storedCdr *StoredCdr) FieldAsString(rsrFld *utils.RSRField) string {
}
}
// concatenates values of multiple fields defined in template, used eg in CDR templates
func (storedCdr *StoredCdr) FieldsAsString(rsrFlds utils.RSRFields) string {
var fldVal string
for _, rsrFld := range rsrFlds {
fldVal += storedCdr.FieldAsString(rsrFld)
}
return fldVal
}
func (storedCdr *StoredCdr) PassesFieldFilter(fieldFilter *utils.RSRField) (bool, string) {
if fieldFilter == nil {
return true, ""

View File

@@ -124,6 +124,20 @@ func TestFieldAsString(t *testing.T) {
}
}
func TestFieldsAsString(t *testing.T) {
cdr := StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf",
CdrHost: "192.168.1.1", CdrSource: "test", ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(10) * time.Second, Pdd: time.Duration(5) * time.Second, Supplier: "SUPPL1", ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
}
eVal := "call_from_1001"
if val := cdr.FieldsAsString(utils.ParseRSRFieldsMustCompile("Category;^_from_;Account", utils.INFIELD_SEP)); val != eVal {
t.Errorf("Expecting : %s, received: %s", eVal, val)
}
}
func TestPassesFieldFilter(t *testing.T) {
cdr := &StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf",
CdrHost: "192.168.1.1", CdrSource: "test", ReqType: utils.META_RATED, Direction: "*out", Tenant: "cgrates.org",

154
engine/suretax.go Normal file
View File

@@ -0,0 +1,154 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2012-2015 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package engine
import (
"encoding/json"
"net/url"
"strconv"
"time"
"github.com/cgrates/cgrates/utils"
)
// Part of SureTax Request
type STRequestItem struct {
LineNumber string // Used to identify an item within the request. If no value is provided, requests are numbered sequentially. Max Len: 40
InvoiceNumber string // Used for tax aggregation by Invoice. Must be alphanumeric. Max Len: 40
CustomerNumber string // Used for tax aggregation by Customer. Must be alphanumeric. Max Len: 40
OrigNumber string // Required when using Tax Situs Rule 01 or 03. Format: NPANXXNNNN
TermNumber string // Required when using Tax Situs Rule 01. Format: NPANXXNNNN
BillToNumber string // Required when using Tax Situs Rule 01 or 02. Format: NPANXXNNNN
Zipcode string // Required when using Tax Situs Rule 04, 05, or 14.
Plus4 string // Zip code extension in format: 9999 (not applicable for Tax Situs Rule 14)
P2PZipcode string // Secondary zip code in format: 99999 (US or US territory) or X9X9X9 (Canadian)
P2PPlus4 string // Secondary zip code extension in format: 99999 (US or US territory) or X9X9X9 (Canadian)
TransDate string // Required. Date of transaction. Valid date formats include: MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DDTHH:MM:SS
Revenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus -indicator.
Units int64 // Required. Units representing number of “lines” or unique charges contained within the revenue. This value is essentially a multiplier on unit-based fees (e.g. E911 fees). Format: 99999. Default should be 1 (one unit).
UnitType string // Required. 00 Default / Number of unique access lines.
Seconds int64 // Required. Duration of call in seconds. Format 99999. Default should be 1.
TaxIncludedCode string // Required. Values: 0 Default (No Tax Included) 1 Tax Included in Revenue
TaxSitusRule string // Required.
TransTypeCode string // Required. Transaction Type Indicator.
SalesTypeCode string // Required. Values: R Residential customer (default) B Business customer I Industrial customer L Lifeline customer
RegulatoryCode string // Required. Provider Type.
TaxExemptionCodeList []string // Required. Tax Exemption to be applied to this item only.
}
// 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
}
// SureTax Request type
type SureTaxRequest struct {
ClientNumber string // Client ID Number provided by SureTax. Required. Max Len: 10
BusinessUnit string // Clients Business Unit. Value for this field is not required. Max Len: 20
ValidationKey string // Validation Key provided by SureTax. Required for client access to API function. Max Len: 36
DataYear string // Required. YYYY Year to use for tax calculation purposes
DataMonth string // Required. MM Month to use for tax calculation purposes. Leading zero is preferred.
TotalRevenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus - indicator.
ReturnFileCode string // Required. 0 Default.Q Quote purposes taxes are computed and returned in the response message for generating quotes.
ClientTracking string // Field for client transaction tracking. This value will be provided in the response data. Value for this field is not required, but preferred. Max Len: 100
IndustryExemption string // Reserved for future use.
ResponseGroup string // Required. Determines how taxes are grouped for the response.
ResponseType string // Required. Determines the granularity of taxes and (optionally) the decimal precision for the tax calculations and amounts in the response.
ItemList []*STRequestItem // List of Item records
}
// Converts the request into the format SureTax expects
func (self *SureTaxRequest) AsHttpForm() (url.Values, error) {
jsnContent, err := json.Marshal(self)
if err != nil {
return nil, err
}
v := url.Values{}
v.Set("request", string(jsnContent))
return v, nil
}
// SureTax Response type
type SureTaxResponse struct {
Successful string // Response will be either Y' or N' : Y = Success / Success with Item error N = Failure
ResponseCode string // ResponseCode: 9999 Request was successful. 1101-1400 Range of values for a failed request (no processing occurred) 9001 Request was successful, but items within the request have errors. The specific items with errors are provided in the ItemMessages field.
HeaderMessage string // Response message: For ResponseCode 9999 “Success”For ResponseCode 9001 “Success with Item errors”. For ResponseCode 1100-1400 Unsuccessful / declined web request.
ItemMessages []*STItemMessage // This field contains a list of items that were not able to be processed due to bad or invalid data (see Response Code of “9001”).
ClientTracking string // Client transaction tracking provided in web request.
TotalTax float64 // Total Tax a total of all taxes included in the TaxList
TransId int // Transaction ID provided by SureTax
GroupList []*STGroup // contains one-to-many Groups
}
// Part of the SureTax Response
type STItemMessage struct {
LineNumber string // value corresponding to the line number in the web request
ResponseCode string // a value in the range 9100-9400
Message string // the error message corresponding to the ResponseCode
}
// Part of the SureTax Response
type STGroup struct {
StateCode string // Tax State
InvoiceNumber string // Invoice Number
CustomerNumber string // Customer number
TaxList []*STTaxItem // contains one-to-many Tax Items
}
// Part of the SureTax Response
type STTaxItem struct {
TaxTypeCode string // Tax Type Code
TaxTypeDesc string // Tax Type Description
TaxAmount float64 // Tax Amount
}

76
engine/suretax_test.go Normal file
View File

@@ -0,0 +1,76 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2015 ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package engine
import (
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func TestNewSureTaxRequest(t *testing.T) {
storedCdr := &StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE,
AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out",
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(12) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", Rated: true,
}
cfg, _ := config.NewDefaultCGRConfig()
cfg.SureTaxCfg.ClientNumber = "000000000"
cfg.SureTaxCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
cfg.SureTaxCfg.Timezone = time.UTC
eSureTaxRequest := &SureTaxRequest{
ClientNumber: cfg.SureTaxCfg.ClientNumber,
ValidationKey: cfg.SureTaxCfg.ValidationKey,
DataYear: "2013",
DataMonth: "11",
TotalRevenue: 1.01,
ReturnFileCode: "0",
ClientTracking: storedCdr.CgrId,
ResponseGroup: "03",
ResponseType: "",
ItemList: []*STRequestItem{
&STRequestItem{
OrigNumber: "1001",
TermNumber: "1002",
BillToNumber: "1001",
TransDate: "2013-11-07T08:42:26",
Revenue: 1.01,
Units: 1,
UnitType: "00",
Seconds: 12,
TaxIncludedCode: "0",
TaxSitusRule: "1",
TransTypeCode: "010101",
SalesTypeCode: "R",
RegulatoryCode: "01",
TaxExemptionCodeList: []string{"00"},
},
},
}
if stReq, err := NewSureTaxRequest(cfg.SureTaxCfg.ClientNumber, cfg.SureTaxCfg.ValidationKey, cfg.SureTaxCfg.Timezone, cfg.SureTaxCfg.OriginationNumber,
cfg.SureTaxCfg.TerminationNumber, storedCdr); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eSureTaxRequest, stReq) {
t.Errorf("Expecting: %+v, received: %+v", eSureTaxRequest.ItemList[0], stReq.ItemList[0])
}
}