mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Merge branch 'master' into actmods
This commit is contained in:
@@ -642,6 +642,7 @@ func (bc BalanceChain) SaveDirtyBalances(acc *Account) {
|
||||
allowNegative := ""
|
||||
disabled := ""
|
||||
if b.account != nil { // only publish modifications for balances with account set
|
||||
//utils.LogStack()
|
||||
accountId = b.account.Id
|
||||
allowNegative = strconv.FormatBool(b.account.AllowNegative)
|
||||
disabled = strconv.FormatBool(b.account.Disabled)
|
||||
|
||||
@@ -152,3 +152,15 @@ func (cc *CallCost) AsJSON() string {
|
||||
ccJson, _ := json.Marshal(cc)
|
||||
return string(ccJson)
|
||||
}
|
||||
|
||||
func (cc *CallCost) UpdateCost() {
|
||||
cost := 0.0
|
||||
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
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ var (
|
||||
|
||||
// Exported method to set the storage getter.
|
||||
func SetRatingStorage(sg RatingStorage) {
|
||||
ratingStorage = sg
|
||||
ratingStorage = sg
|
||||
}
|
||||
|
||||
func SetAccountingStorage(ag AccountingStorage) {
|
||||
@@ -636,16 +636,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool)
|
||||
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.UpdateCost()
|
||||
cc.Timespans.Compress()
|
||||
//log.Printf("OUT CC: ", cc)
|
||||
return
|
||||
|
||||
@@ -557,6 +557,28 @@ func TestGetCostWithMaxCost(t *testing.T) {
|
||||
t.Errorf("Expected %v was %v", expected, cc.Cost)
|
||||
}
|
||||
}
|
||||
func TestGetCostRoundingIssue(t *testing.T) {
|
||||
ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT")
|
||||
for _, at := range ap {
|
||||
at.Execute()
|
||||
}
|
||||
cd := &CallDescriptor{
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "dy",
|
||||
Account: "dy",
|
||||
Destination: "0723123113",
|
||||
TimeStart: time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
|
||||
TimeEnd: time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC),
|
||||
MaxCostSoFar: 0,
|
||||
}
|
||||
cc, err := cd.GetCost()
|
||||
expected := 0.17
|
||||
if cc.Cost != expected || err != nil {
|
||||
t.Errorf("Expected %v was %+v", expected, cc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxSessionTimeWithMaxCostFree(t *testing.T) {
|
||||
ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT")
|
||||
|
||||
@@ -70,6 +70,7 @@ RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s
|
||||
RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s
|
||||
R_URG,0,0,1,1,0
|
||||
MX,0,1,1s,1s,0
|
||||
DY,0.15,0.05,60s,1s,0s
|
||||
`
|
||||
destinationRates = `
|
||||
RT_STANDARD,GERMANY,R1,*middle,4,0,
|
||||
@@ -91,6 +92,7 @@ DATA_RATE,*any,LANDLINE_OFFPEAK,*middle,4,0,
|
||||
RT_URG,URG,R_URG,*middle,4,0,
|
||||
MX_FREE,RET,MX,*middle,4,10,*free
|
||||
MX_DISC,RET,MX,*middle,4,10,*disconnect
|
||||
RT_DY,RET,DY,*up,2,0,
|
||||
`
|
||||
ratingPlans = `
|
||||
STANDARD,RT_STANDARD,WORKDAYS_00,10
|
||||
@@ -115,6 +117,7 @@ RP_MX,MX_DISC,WORKDAYS_00,10
|
||||
RP_MX,MX_FREE,WORKDAYS_18,10
|
||||
GER_ONLY,GER,*any,10
|
||||
ANY_PLAN,DATA_RATE,*any,10
|
||||
DY_PLAN,RT_DY,*any,10
|
||||
`
|
||||
ratingProfiles = `
|
||||
*out,CUSTOMER_1,0,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb,
|
||||
@@ -141,6 +144,7 @@ ANY_PLAN,DATA_RATE,*any,10
|
||||
*out,cgrates.org,call,nt,2012-02-28T00:00:00Z,GER_ONLY,,
|
||||
*in,cgrates.org,LCR_STANDARD,max,2013-03-23T00:00:00Z,RP_MX,,
|
||||
*out,cgrates.org,call,money,2015-02-28T00:00:00Z,EVENING,,
|
||||
*out,cgrates.org,call,dy,2015-02-28T00:00:00Z,DY_PLAN,,
|
||||
`
|
||||
sharedGroups = `
|
||||
SG1,*any,*lowest,
|
||||
@@ -391,7 +395,7 @@ func TestLoadTimimgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRates(t *testing.T) {
|
||||
if len(csvr.rates) != 13 {
|
||||
if len(csvr.rates) != 14 {
|
||||
t.Error("Failed to load rates: ", len(csvr.rates))
|
||||
}
|
||||
rate := csvr.rates["R1"].RateSlots[0]
|
||||
@@ -461,7 +465,7 @@ func TestLoadRates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadDestinationRates(t *testing.T) {
|
||||
if len(csvr.destinationRates) != 14 {
|
||||
if len(csvr.destinationRates) != 15 {
|
||||
t.Error("Failed to load destinationrates: ", len(csvr.destinationRates))
|
||||
}
|
||||
drs := csvr.destinationRates["RT_STANDARD"]
|
||||
@@ -609,7 +613,7 @@ func TestLoadDestinationRates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRatingPlans(t *testing.T) {
|
||||
if len(csvr.ratingPlans) != 13 {
|
||||
if len(csvr.ratingPlans) != 14 {
|
||||
t.Error("Failed to load rating plans: ", len(csvr.ratingPlans))
|
||||
}
|
||||
rplan := csvr.ratingPlans["STANDARD"]
|
||||
@@ -781,7 +785,7 @@ func TestLoadRatingPlans(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadRatingProfiles(t *testing.T) {
|
||||
if len(csvr.ratingProfiles) != 21 {
|
||||
if len(csvr.ratingProfiles) != 22 {
|
||||
t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles)
|
||||
}
|
||||
rp := csvr.ratingProfiles["*out:test:0:trp"]
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -52,7 +51,7 @@ func NewSureTaxRequest(cdr *StoredCdr, stCfg *config.SureTaxCfg) (*SureTaxReques
|
||||
if len(definedTaxExtempt) != 0 {
|
||||
taxExempt = strings.Split(cdr.FieldsAsString(stCfg.TaxExemptionCodeList), ",")
|
||||
}
|
||||
stReq := new(SureTaxRequest)
|
||||
stReq := new(STRequest)
|
||||
stReq.ClientNumber = stCfg.ClientNumber
|
||||
stReq.BusinessUnit = "" // Export it to config
|
||||
stReq.ValidationKey = stCfg.ValidationKey
|
||||
@@ -86,11 +85,15 @@ func NewSureTaxRequest(cdr *StoredCdr, stCfg *config.SureTaxCfg) (*SureTaxReques
|
||||
TaxExemptionCodeList: taxExempt,
|
||||
},
|
||||
}
|
||||
return stReq, nil
|
||||
return &SureTaxRequest{Request: stReq}, nil
|
||||
}
|
||||
|
||||
type SureTaxRequest struct {
|
||||
Request *STRequest // SureTax Requires us to encapsulate the content into a request element
|
||||
}
|
||||
|
||||
// SureTax Request type
|
||||
type SureTaxRequest struct {
|
||||
type STRequest struct {
|
||||
ClientNumber string // Client ID Number – provided by SureTax. Required. Max Len: 10
|
||||
BusinessUnit string // Client’s 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
|
||||
@@ -130,19 +133,12 @@ type STRequestItem struct {
|
||||
TaxExemptionCodeList []string // Required. Tax Exemption to be applied to this item only.
|
||||
}
|
||||
|
||||
// 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 {
|
||||
D *STResponse // SureTax requires encapsulating reply into a D object
|
||||
}
|
||||
|
||||
type STResponse struct {
|
||||
Successful string // Response will be either ‘Y' or ‘N' : Y = Success / Success with Item error N = Failure
|
||||
ResponseCode int64 // 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.
|
||||
@@ -190,38 +186,40 @@ func SureTaxProcessCdr(cdr *StoredCdr) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
jsnContent, 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))
|
||||
utils.Logger.Debug(fmt.Sprintf("NewSureTaxRequest: %s\n", string(jsnContent)))
|
||||
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 {
|
||||
utils.Logger.Debug(fmt.Sprintf("Unexpected response body received, error: %s\n", err.Error()))
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
utils.Logger.Debug(fmt.Sprintf("Unexpected code received: %d\n", resp.StatusCode))
|
||||
return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode)
|
||||
}
|
||||
utils.Logger.Debug(fmt.Sprintf("Received raw answer from SureTax: %s\n", string(respBody)))
|
||||
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
|
||||
utils.Logger.Debug(fmt.Sprintf("Received answer from SureTax: %+v\n", stResp))
|
||||
if stResp.D.ResponseCode != 9999 {
|
||||
cdr.ExtraInfo = stResp.D.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)
|
||||
cdr.Cost = utils.Round(stResp.D.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
} else {
|
||||
cdr.Cost = utils.Round(cdr.Cost+stResp.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)
|
||||
cdr.Cost = utils.Round(cdr.Cost+stResp.D.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)
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestNewSureTaxRequest(t *testing.T) {
|
||||
stCfg.ClientNumber = "000000000"
|
||||
stCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
|
||||
stCfg.Timezone = time.UTC
|
||||
eSureTaxRequest := &SureTaxRequest{
|
||||
eSureTaxRequest := &SureTaxRequest{Request: &STRequest{
|
||||
ClientNumber: "000000000",
|
||||
ValidationKey: "19491161-F004-4F44-BDB3-E976D6739A64",
|
||||
DataYear: "2013",
|
||||
@@ -69,10 +69,10 @@ func TestNewSureTaxRequest(t *testing.T) {
|
||||
TaxExemptionCodeList: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
if stReq, err := NewSureTaxRequest(cdr, stCfg); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eSureTaxRequest, stReq) {
|
||||
t.Errorf("Expecting: %+v, received: %+v", eSureTaxRequest.ItemList[0], stReq.ItemList[0])
|
||||
t.Errorf("Expecting: %+v, received: %+v", eSureTaxRequest.Request.ItemList[0], stReq.Request.ItemList[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,11 +263,7 @@ func (ts *TimeSpan) getCost() float64 {
|
||||
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
|
||||
}
|
||||
cost := ts.Increments.GetTotalCost()
|
||||
if ts.RateInterval != nil && ts.RateInterval.Rating != nil {
|
||||
return utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod)
|
||||
} else {
|
||||
|
||||
@@ -137,7 +137,7 @@ func TestSTIProcessExternalCdr(t *testing.T) {
|
||||
AccId: "teststicdr1", CdrHost: "192.168.1.1", CdrSource: "STI_TEST", ReqType: utils.META_RATED, Direction: utils.OUT,
|
||||
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "+14082342500", Destination: "+16268412300", Supplier: "SUPPL1",
|
||||
SetupTime: "2015-10-18T13:00:00Z", AnswerTime: "2015-10-18T13:00:00Z",
|
||||
Usage: "15s", Pdd: "7.0", ExtraFields: map[string]string{"ClientNumber": "000000534", "": "valextr2"},
|
||||
Usage: "15s", Pdd: "7.0", ExtraFields: map[string]string{"CustomerNumber": "000000534", "ZipCode": ""},
|
||||
}
|
||||
var reply string
|
||||
if err := stiRpc.Call("CdrsV2.ProcessExternalCdr", cdr, &reply); err != nil {
|
||||
@@ -145,7 +145,7 @@ func TestSTIProcessExternalCdr(t *testing.T) {
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply received: ", reply)
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
|
||||
time.Sleep(time.Duration(2) * time.Second)
|
||||
}
|
||||
|
||||
func TestSTIGetCdrs(t *testing.T) {
|
||||
@@ -163,6 +163,16 @@ func TestSTIGetCdrs(t *testing.T) {
|
||||
t.Errorf("Unexpected Cost for CDR: %+v", cdrs[0])
|
||||
}
|
||||
}
|
||||
req = utils.RpcCdrsFilter{RunIds: []string{utils.META_SURETAX}, Accounts: []string{"1001"}}
|
||||
if err := stiRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if len(cdrs) != 1 {
|
||||
t.Error("Unexpected number of CDRs returned: ", len(cdrs))
|
||||
} else {
|
||||
if cdrs[0].Cost != 0.012 {
|
||||
t.Errorf("Unexpected Cost for CDR: %+v", cdrs[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSTIStopCgrEngine(t *testing.T) {
|
||||
|
||||
@@ -663,7 +663,7 @@ func TestTutLocalCostErrors(t *testing.T) {
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply received: ", reply)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for CDR to be processed
|
||||
req = utils.RpcCdrsFilter{RunIds: []string{utils.META_DEFAULT}, Accounts: []string{cdr2.Account}, DestPrefixes: []string{cdr2.Destination}}
|
||||
if err := tutLocalRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
|
||||
@@ -233,6 +233,8 @@ func (s *Session) SaveOperations() {
|
||||
firstCC.Merge(cc)
|
||||
//utils.Logger.Debug(fmt.Sprintf("AFTER MERGE: %s", utils.ToJSON(firstCC)))
|
||||
}
|
||||
// make sure we have rounded timespans final cost
|
||||
firstCC.UpdateCost()
|
||||
|
||||
var reply string
|
||||
err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"log/syslog"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var Logger LoggerInterface
|
||||
@@ -85,3 +86,9 @@ func (sl *StdLogger) Warning(m string) (err error) {
|
||||
log.Print("[WARNING]" + m)
|
||||
return
|
||||
}
|
||||
|
||||
func LogStack() {
|
||||
buf := make([]byte, 300)
|
||||
runtime.Stack(buf, false)
|
||||
Logger.Debug(string(buf))
|
||||
}
|
||||
|
||||
@@ -221,3 +221,14 @@ func TestRSRFieldsId(t *testing.T) {
|
||||
t.Errorf("Received id: %s", idRcv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSRCostDetails(t *testing.T) {
|
||||
fieldsStr1 := `{"Direction":"*out","Category":"default_route","Tenant":"demo.cgrates.org","Subject":"voxbeam_premium","Account":"6335820713","Destination":"15143606781","TOR":"*voice","Cost":0.0007,"Timespans":[{"TimeStart":"2015-08-30T21:46:54Z","TimeEnd":"2015-08-30T21:47:06Z","Cost":0.00072,"RateInterval":{"Timing":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""},"Rating":{"ConnectFee":0,"RoundingMethod":"*middle","RoundingDecimals":5,"MaxCost":0,"MaxCostStrategy":"0","Rates":[{"GroupIntervalStart":0,"Value":0.0036,"RateIncrement":6000000000,"RateUnit":60000000000}]},"Weight":10},"DurationIndex":12000000000,"Increments":[{"Duration":6000000000,"Cost":0.00036,"BalanceInfo":{"UnitBalanceUuid":"","MoneyBalanceUuid":"40adda88-25d3-4009-b928-f39d61590439","AccountId":"*out:demo.cgrates.org:6335820713"},"BalanceRateInterval":null,"UnitInfo":null,"CompressFactor":2}],"MatchedSubject":"*out:demo.cgrates.org:default_route:voxbeam_premium","MatchedPrefix":"1514","MatchedDestId":"Canada","RatingPlanId":"RP_VOXBEAM_PREMIUM"}]}`
|
||||
rsrField, err := NewRSRField(`~cost_details:s/"MatchedDestId":"(\w+)"/${1}/`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if parsedVal := rsrField.ParseValue(fieldsStr1); parsedVal != "Canada" {
|
||||
t.Errorf("Expecting: Canada, received: %s", parsedVal)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user