AccountSummary inside CDR object, decoupling it from CallCost for error cases

This commit is contained in:
DanB
2016-08-29 14:28:14 +02:00
parent 90069ab05d
commit fece61093e
10 changed files with 51 additions and 15 deletions

View File

@@ -28,6 +28,7 @@ CREATE TABLE cdrs (
cost_source varchar(64) NOT NULL,
cost DECIMAL(20,4) NOT NULL,
cost_details text,
account_summary text,
extra_info text,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,

View File

@@ -28,6 +28,7 @@ CREATE TABLE cdrs (
cost_source VARCHAR(64) NOT NULL,
cost NUMERIC(20,4) DEFAULT NULL,
cost_details jsonb,
account_summary jsonb,
extra_info text,
created_at TIMESTAMP,
updated_at TIMESTAMP NULL,

View File

@@ -1036,6 +1036,13 @@ func (acc *Account) AsAccountSummary() *AccountSummary {
return ad
}
func NewAccountSummaryFromJSON(jsn string) (acntSummary *AccountSummary, err error) {
if !utils.IsSliceMember([]string{"", "null"}, jsn) { // Unmarshal only when content
json.Unmarshal([]byte(jsn), &acntSummary)
}
return
}
// AccountDigest contains compressed information about an Account
type AccountSummary struct {
Tenant string

View File

@@ -1873,6 +1873,14 @@ func TestAccountGetBalancesForPrefixMixedBad(t *testing.T) {
}
}
func TestAccountNewAccountSummaryFromJSON(t *testing.T) {
if acnt, err := NewAccountSummaryFromJSON("null"); err != nil {
t.Error(err)
} else if acnt != nil {
t.Errorf("Expecting nil, received: %+v", acnt)
}
}
func TestAccountAsAccountDigest(t *testing.T) {
acnt1 := &Account{
ID: "cgrates.org:account1",

View File

@@ -30,7 +30,6 @@ type CallCost struct {
Cost float64
Timespans TimeSpans
RatedUsage float64
AccountSummary *AccountSummary
deductConnectFee bool
negativeConnectFee bool // the connect fee went negative on default balance
maxCostDisconect bool

View File

@@ -103,10 +103,11 @@ type CDR struct {
ExtraFields map[string]string // Extra fields to be stored in CDR
CostSource string // The source of this cost
Cost float64
CostDetails *CallCost // Attach the cost details to CDR when possible
ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation
Rated bool // Mark the CDR as rated so we do not process it during rating
Partial bool // Used for partial record processing by CDRC
CostDetails *CallCost // Attach the cost details to CDR when possible
AccountSummary *AccountSummary // Store AccountSummary information
ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation
Rated bool // Mark the CDR as rated so we do not process it during rating
Partial bool // Used for partial record processing by CDRC
}
func (cdr *CDR) CostDetailsJson() string {

View File

@@ -239,22 +239,27 @@ func (self *CdrServer) deriveRateStoreStatsReplicate(cdr *CDR, store, stats, rep
}
}
}
// Store AccountSummary if requested
if self.cgrCfg.CDRScdrAccountSummary {
for _, ratedCDR := range ratedCDRs {
if utils.IsSliceMember([]string{utils.META_PREPAID, utils.PREPAID, utils.META_PSEUDOPREPAID, utils.PSEUDOPREPAID,
utils.META_POSTPAID, utils.POSTPAID}, ratedCDR.RequestType) {
acntID := utils.ConcatenatedKey(ratedCDR.Tenant, ratedCDR.Account)
acnt, err := self.dataDB.GetAccount(acntID)
if err != nil {
utils.Logger.Err(fmt.Sprintf("<CDRS> Querying AccountDigest for account: %s got error: %s", acntID, err.Error()))
} else if acnt.ID != "" {
ratedCDR.AccountSummary = acnt.AsAccountSummary()
}
}
}
}
// Store rated CDRs
if store {
for _, ratedCDR := range ratedCDRs {
if ratedCDR.CostDetails != nil {
ratedCDR.CostDetails.UpdateCost()
ratedCDR.CostDetails.UpdateRatedUsage()
if utils.IsSliceMember([]string{utils.META_PREPAID, utils.PREPAID, utils.META_PSEUDOPREPAID, utils.PSEUDOPREPAID,
utils.META_POSTPAID, utils.POSTPAID}, ratedCDR.RequestType) && self.cgrCfg.CDRScdrAccountSummary {
acntID := utils.ConcatenatedKey(ratedCDR.Tenant, ratedCDR.Account)
acnt, err := self.dataDB.GetAccount(acntID)
if err != nil {
utils.Logger.Err(fmt.Sprintf("<CDRS> Querying AccountDigest for account: %s got error: %s", acntID, err.Error()))
} else if acnt.ID != "" {
ratedCDR.CostDetails.AccountSummary = acnt.AsAccountSummary()
}
}
}
if err := self.cdrDb.SetCDR(ratedCDR, true); err != nil {
utils.Logger.Err(fmt.Sprintf("<CDRS> Storing rated CDR %+v, got error: %s", ratedCDR, err.Error()))

View File

@@ -423,6 +423,7 @@ type TBLCDRs struct {
Cost float64
CostDetails string
CostSource string
AccountSummary string
ExtraInfo string
CreatedAt time.Time
UpdatedAt time.Time

View File

@@ -675,6 +675,7 @@ func (self *SQLStorage) SetCDR(cdr *CDR, allowUpdate bool) error {
CostSource: cdr.CostSource,
Cost: cdr.Cost,
CostDetails: cdr.CostDetailsJson(),
AccountSummary: utils.ToJSON(cdr.AccountSummary),
ExtraInfo: cdr.ExtraInfo,
CreatedAt: time.Now(),
})
@@ -707,6 +708,7 @@ func (self *SQLStorage) SetCDR(cdr *CDR, allowUpdate bool) error {
CostSource: cdr.CostSource,
Cost: cdr.Cost,
CostDetails: cdr.CostDetailsJson(),
AccountSummary: utils.ToJSON(cdr.AccountSummary),
ExtraInfo: cdr.ExtraInfo,
UpdatedAt: time.Now(),
},
@@ -975,6 +977,10 @@ func (self *SQLStorage) GetCDRs(qryFltr *utils.CDRsFilter, remove bool) ([]*CDR,
return nil, 0, fmt.Errorf("JSON unmarshal callcost error for cgrid: %s, runid: %v, error: %s", result.Cgrid, result.RunID, err.Error())
}
}
acntSummary, err := NewAccountSummaryFromJSON(result.AccountSummary)
if err != nil {
return nil, 0, fmt.Errorf("JSON unmarshal account summary error for cgrid: %s, runid: %v, error: %s", result.Cgrid, result.RunID, err.Error())
}
usageDur := time.Duration(result.Usage * utils.NANO_MULTIPLIER)
pddDur := time.Duration(result.Pdd * utils.NANO_MULTIPLIER)
storCdr := &CDR{
@@ -1002,6 +1008,7 @@ func (self *SQLStorage) GetCDRs(qryFltr *utils.CDRsFilter, remove bool) ([]*CDR,
CostSource: result.CostSource,
Cost: result.Cost,
CostDetails: &callCost,
AccountSummary: acntSummary,
ExtraInfo: result.ExtraInfo,
}
cdrs = append(cdrs, storCdr)

View File

@@ -703,3 +703,9 @@ func TestMaskSuffix(t *testing.T) {
}
}
func TestToJSON(t *testing.T) {
if outNilObj := ToJSON(nil); outNilObj != "null" {
t.Errorf("Expecting null, received: <%q>", outNilObj)
}
}