From 373df2329c6ae06d7805f93a13ca022eda4d0920 Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Wed, 19 Apr 2023 02:54:59 -0400 Subject: [PATCH] Revise CDR rerating If the reRate parameter is set to true, also set the refund to true. The rerate parameter is now no longer hardcoded to true for the RateCDRs API.If required, the "*rerate" flag must be provided by the caller. In case CostDetails is not populated, retrieve it from StorDB if possible and add it to the CGREvent before converting to CDRs. Now that the refund happens before the debit, revise the expected values for the testV1CDRsProcessEventWithRefund subtest within the apier/v1/cdrs_it_test.go file. --- apier/v2/cdrs_it_test.go | 4 +-- engine/cdrs.go | 44 +++++++++++++++++++--------- general_tests/rerate_cdrs_it_test.go | 2 +- general_tests/rerate_exp_it_test.go | 2 +- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apier/v2/cdrs_it_test.go b/apier/v2/cdrs_it_test.go index ad06f03f3..71ef1e135 100644 --- a/apier/v2/cdrs_it_test.go +++ b/apier/v2/cdrs_it_test.go @@ -288,7 +288,7 @@ func testV2CDRsRateCDRs(t *testing.T) { if err := cdrsRpc.Call(utils.CDRsV1RateCDRs, &engine.ArgRateCDRs{ RPCCDRsFilter: utils.RPCCDRsFilter{NotRunIDs: []string{"raw"}}, - Flags: []string{"*chargers:false"}, + Flags: []string{"*chargers:false", utils.MetaRerate}, }, &reply); err != nil { t.Error("Unexpected error: ", err.Error()) } else if reply != utils.OK { @@ -664,7 +664,7 @@ func testV2CDRsRateCDRsWithRatingPlan(t *testing.T) { if err := cdrsRpc.Call(utils.CDRsV1RateCDRs, &engine.ArgRateCDRs{ RPCCDRsFilter: utils.RPCCDRsFilter{NotRunIDs: []string{"raw"}, Accounts: []string{"testV2CDRsProcessCDR4"}}, - Flags: []string{"*chargers:true"}, + Flags: []string{"*chargers:true", utils.MetaRerate}, }, &reply); err != nil { t.Error("Unexpected error: ", err.Error()) } else if reply != utils.OK { diff --git a/engine/cdrs.go b/engine/cdrs.go index 64a6a0583..fafeae9ab 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -500,6 +500,30 @@ func (cdrS *CDRServer) processEvents(evs []*utils.CGREvent, cdrs := make([]*CDR, len(cgrEvs)) if refund || ralS || store || reRate || export { for i, cgrEv := range cgrEvs { + if refund { + if _, has := cgrEv.Event[utils.CostDetails]; !has { + // if CostDetails is not populated or is nil, look for it inside the previously stored cdr + var cgrID string // prepare CGRID to filter for previous CDR + if val, has := cgrEv.Event[utils.CGRID]; !has { + cgrID = utils.Sha1(utils.IfaceAsString(cgrEv.Event[utils.OriginID]), + utils.IfaceAsString(cgrEv.Event[utils.OriginHost])) + } else { + cgrID = utils.IfaceAsString(val) + } + var prevCDRs []*CDR // only one should be returned + if prevCDRs, _, err = cdrS.cdrDb.GetCDRs( + &utils.CDRsFilter{CGRIDs: []string{cgrID}, + RunIDs: []string{utils.IfaceAsString(cgrEv.Event[utils.RunID])}}, false); err != nil { + utils.Logger.Err( + fmt.Sprintf("<%s> could not retrieve previously stored CDR, error: <%s>", + utils.CDRs, err.Error())) + err = utils.ErrPartiallyExecuted + return + } else { + cgrEv.Event[utils.CostDetails] = prevCDRs[0].CostDetails + } + } + } if cdrs[i], err = NewMapEvent(cgrEv.Event).AsCDR(cdrS.cgrCfg, cgrEv.Tenant, cdrS.cgrCfg.GeneralCfg().DefaultTimezone); err != nil { utils.Logger.Warning( @@ -516,25 +540,13 @@ func (cdrS *CDRServer) processEvents(evs []*utils.CGREvent, } if refund { for i, cdr := range cdrs { - if cdr.CostDetails == nil { // if CostDetails is not populated, look for it inside the previously stored cdr - var prevCDRs []*CDR // only one should be returned - if prevCDRs, _, err = cdrS.cdrDb.GetCDRs( - &utils.CDRsFilter{CGRIDs: []string{cdr.CGRID}, - RunIDs: []string{cdr.RunID}}, false); err != nil { - utils.Logger.Info( - fmt.Sprintf("<%s> could not retrieve previously stored CDR, error: <%s>", - utils.CDRs, err.Error())) - continue - } - cdr.CostDetails = prevCDRs[0].CostDetails - } if rfnd, errRfd := cdrS.refundEventCost(cdr.CostDetails, cdr.RequestType, cdr.ToR); errRfd != nil { utils.Logger.Warning( fmt.Sprintf("<%s> error: <%s> refunding CDR %+v", utils.CDRs, errRfd.Error(), utils.ToJSON(cdr))) } else if rfnd { - cdr.CostDetails = nil + cdr.CostDetails = nil // this makes sure that the rater will recalculate (and debit) the cost procFlgs[i].Add(utils.MetaRefund) } } @@ -1093,6 +1105,10 @@ func (cdrS *CDRServer) V1RateCDRs(arg *ArgRateCDRs, reply *string) (err error) { if flgs.Has(utils.MetaAttributes) { attrS = flgs.GetBool(utils.MetaAttributes) } + var reRate bool + if flgs.Has(utils.MetaRerate) { + reRate = flgs.GetBool(utils.MetaRerate) + } if chrgS && len(cdrS.cgrCfg.CdrsCfg().ChargerSConns) == 0 { return utils.NewErrNotConnected(utils.ChargerS) @@ -1104,7 +1120,7 @@ func (cdrS *CDRServer) V1RateCDRs(arg *ArgRateCDRs, reply *string) (err error) { cgrEvs[i].APIOpts = arg.APIOpts } if _, err = cdrS.processEvents(cgrEvs, chrgS, attrS, false, - true, store, true, export, thdS, statS); err != nil { + true, store, reRate, export, thdS, statS); err != nil { return utils.NewErrServerError(err) } diff --git a/general_tests/rerate_cdrs_it_test.go b/general_tests/rerate_cdrs_it_test.go index 9b0a5fd51..3710d602d 100644 --- a/general_tests/rerate_cdrs_it_test.go +++ b/general_tests/rerate_cdrs_it_test.go @@ -339,7 +339,7 @@ func testRerateCDRsGetAccountAfterProcessEvent2(t *testing.T) { func testRerateCDRsRerateCDRs(t *testing.T) { var reply string if err := rrCdrsRPC.Call(utils.CDRsV1RateCDRs, &engine.ArgRateCDRs{ - Flags: []string{utils.MetaRALs}, + Flags: []string{utils.MetaRerate}, RPCCDRsFilter: utils.RPCCDRsFilter{ OrderBy: utils.AnswerTime, CGRIDs: []string{rrCdrsUUID}, diff --git a/general_tests/rerate_exp_it_test.go b/general_tests/rerate_exp_it_test.go index 1e2f2a764..fcec75043 100644 --- a/general_tests/rerate_exp_it_test.go +++ b/general_tests/rerate_exp_it_test.go @@ -439,7 +439,7 @@ func testRerateExpGetAccountAfterProcessEvent3(t *testing.T) { func testRerateExpRerateCDRs(t *testing.T) { var reply string if err := ng2RPC.Call(utils.CDRsV1RateCDRs, &engine.ArgRateCDRs{ - Flags: []string{utils.MetaRALs}, + Flags: []string{utils.MetaRerate}, RPCCDRsFilter: utils.RPCCDRsFilter{ OrderBy: utils.AnswerTime, CGRIDs: []string{ng1UUID, ng2UUID},