diff --git a/apier/v1/debit.go b/apier/v1/debit.go index 82db4e873..de9909e1e 100644 --- a/apier/v1/debit.go +++ b/apier/v1/debit.go @@ -26,34 +26,26 @@ import ( // account to go negative if the cost calculated is greater than the balance func (apier *ApierV1) DebitUsage(usageRecord engine.UsageRecord, reply *string) error { return apier.DebitUsageWithOptions(AttrDebitUsageWithOptions{ - Options: AttrDebitUsageOptions{AllowNegative: true}, - UsageRecord: usageRecord, + UsageRecord: &usageRecord, + AllowNegativeAccount: true, }, reply) } -// AttrDebitUsageOptions represents options that -// are applied to the DebitUsage request -type AttrDebitUsageOptions struct { - AllowNegative bool -} - // AttrDebitUsageWithOptions represents the DebitUsage request type AttrDebitUsageWithOptions struct { - Options AttrDebitUsageOptions - UsageRecord engine.UsageRecord + UsageRecord *engine.UsageRecord + AllowNegativeAccount bool // allow account to go negative during debit } // DebitUsageWithOptions will debit the account based on the usage cost with // additional options to control if the balance can go negative -func (apier *ApierV1) DebitUsageWithOptions(usageRecordWithOptions AttrDebitUsageWithOptions, reply *string) error { - var usageRecord = &usageRecordWithOptions.UsageRecord - var options = &usageRecordWithOptions.Options - +func (apier *ApierV1) DebitUsageWithOptions(args AttrDebitUsageWithOptions, reply *string) error { + usageRecord := args.UsageRecord if missing := utils.MissingStructFields(usageRecord, []string{"Account", "Destination", "Usage"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } - err := engine.LoadUserProfile(usageRecord, "") + err := engine.LoadUserProfile(args.UsageRecord, "") if err != nil { *reply = err.Error() return err @@ -83,14 +75,11 @@ func (apier *ApierV1) DebitUsageWithOptions(usageRecordWithOptions AttrDebitUsag } // Get the call descriptor from the usage record - cd, err := usageRecord.AsCallDescriptor(apier.Config.DefaultTimezone) + cd, err := usageRecord.AsCallDescriptor(apier.Config.DefaultTimezone, !args.AllowNegativeAccount) if err != nil { return utils.NewErrServerError(err) } - // Apply options - cd.AllowNegative = options.AllowNegative - // Calculate the cost for usage and debit the account var cc engine.CallCost if err := apier.Responder.Debit(cd, &cc); err != nil { diff --git a/apier/v1/debit_test.go b/apier/v1/debit_test.go index c34b57e37..e50da2784 100644 --- a/apier/v1/debit_test.go +++ b/apier/v1/debit_test.go @@ -18,8 +18,6 @@ along with this program. If not, see package v1 import ( - "flag" - "os" "testing" "time" @@ -34,7 +32,7 @@ var ( responder *engine.Responder ) -func TestMain(m *testing.M) { +func init() { apierDebitStorage, _ = engine.NewMapStorage() cfg, _ := config.NewDefaultCGRConfig() responder := new(engine.Responder) @@ -47,9 +45,6 @@ func TestMain(m *testing.M) { Config: cfg, Responder: responder, } - - flag.Parse() - os.Exit(m.Run()) } func TestDebitUsageWithOptions(t *testing.T) { @@ -123,11 +118,7 @@ func TestDebitUsageWithOptions(t *testing.T) { t.Error(err) } - allowNegativeOpt := AttrDebitUsageOptions{ - AllowNegative: false, - } - - usageRecord := engine.UsageRecord{ + usageRecord := &engine.UsageRecord{ Tenant: cgrTenant, Account: "account1", Destination: "*any", @@ -140,7 +131,8 @@ func TestDebitUsageWithOptions(t *testing.T) { } var reply string - if err := apierDebit.DebitUsageWithOptions(AttrDebitUsageWithOptions{Options: allowNegativeOpt, UsageRecord: usageRecord}, &reply); err != nil { + if err := apierDebit.DebitUsageWithOptions(AttrDebitUsageWithOptions{UsageRecord: usageRecord, + AllowNegativeAccount: false}, &reply); err != nil { t.Error(err) } @@ -151,6 +143,7 @@ func TestDebitUsageWithOptions(t *testing.T) { } eAcntVal := 9.0 if resolvedAccount.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { - t.Errorf("Expected: %f, received: %f", eAcntVal, resolvedAccount.BalanceMap[utils.MONETARY].GetTotalValue()) + t.Errorf("Expected: %f, received: %f", eAcntVal, + resolvedAccount.BalanceMap[utils.MONETARY].GetTotalValue()) } } diff --git a/engine/calldesc.go b/engine/calldesc.go index 5defe6099..9df42331d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -150,17 +150,17 @@ type CallDescriptor struct { TOR string // used unit balances selector ExtraFields map[string]string // Extra fields, mostly used for user profile matching // session limits - MaxRate float64 - MaxRateUnit time.Duration - MaxCostSoFar float64 - CgrID string - RunID string - ForceDuration bool // for Max debit if less than duration return err - PerformRounding bool // flag for rating info rounding - DryRun bool - AllowNegative bool - account *Account - testCallcost *CallCost // testing purpose only! + MaxRate float64 + MaxRateUnit time.Duration + MaxCostSoFar float64 + CgrID string + RunID string + ForceDuration bool // for Max debit if less than duration return err + PerformRounding bool // flag for rating info rounding + DryRun bool + DenyNegativeAccount bool // prevent account going on negative during debit + account *Account + testCallcost *CallCost // testing purpose only! } func (cd *CallDescriptor) ValidateCallData() error { @@ -700,7 +700,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { } else { if memberIds, sgerr := account.GetUniqueSharedGroupMembers(cd); sgerr == nil { _, err = Guardian.Guard(func() (interface{}, error) { - cc, err = cd.debit(account, cd.DryRun, cd.AllowNegative) + cc, err = cd.debit(account, cd.DryRun, !cd.DenyNegativeAccount) return 0, err }, 0, memberIds.Slice()...) } else { @@ -755,7 +755,7 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { cd.DurationIndex -= initialDuration - remainingDuration } //log.Print("Remaining duration: ", remainingDuration) - cc, err = cd.debit(account, cd.DryRun, true) + cc, err = cd.debit(account, cd.DryRun, !cd.DenyNegativeAccount) //log.Print(balanceMap[0].Value, balanceMap[1].Value) return 0, err }, 0, memberIDs.Slice()...) diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 812716b98..777134d9d 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -284,7 +284,7 @@ func TestGetCostRounding(t *testing.T) { func TestDebitRounding(t *testing.T) { t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2017, time.February, 2, 17, 33, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "round", Destination: "49", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, AllowNegative: true} + cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "round", Destination: "49", TimeStart: t1, TimeEnd: t2, LoopIndex: 0} result, _ := cd.Debit() if result.Cost != 0.30006 || result.GetConnectFee() != 0 { // should be 0.3 :( t.Error("bad cost", utils.ToIJSON(result)) @@ -294,7 +294,7 @@ func TestDebitRounding(t *testing.T) { func TestDebitPerformRounding(t *testing.T) { t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2017, time.February, 2, 17, 33, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "round", Destination: "49", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, PerformRounding: true, AllowNegative: true} + cd := &CallDescriptor{Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "round", Destination: "49", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, PerformRounding: true} result, _ := cd.Debit() if result.Cost != 0.3001 || result.GetConnectFee() != 0 { // should be 0.3 :( t.Error("bad cost", utils.ToIJSON(result)) @@ -793,16 +793,15 @@ func TestDebitRatingInfoOnZeroTime(t *testing.T) { 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, 27, 0, time.UTC), - MaxCostSoFar: 0, - AllowNegative: true, + 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, 27, 0, time.UTC), + MaxCostSoFar: 0, } cc, err := cd.Debit() if err != nil || @@ -919,7 +918,6 @@ func TestDebitRoundingRefund(t *testing.T) { TimeEnd: time.Date(2016, 3, 4, 13, 53, 00, 0, time.UTC), MaxCostSoFar: 0, PerformRounding: true, - AllowNegative: true, } acc, err := accountingStorage.GetAccount("cgrates.org:dy") if err != nil || acc.BalanceMap[utils.MONETARY][0].Value != 1 { @@ -1204,15 +1202,14 @@ func TestMaxDebitDurationNoGreatherThanInitialDuration(t *testing.T) { func TestDebitAndMaxDebit(t *testing.T) { cd1 := &CallDescriptor{ - TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 10, 21, 18, 34, 10, 0, time.UTC), - Direction: "*out", - Category: "0", - Tenant: "vdf", - Subject: "minu_from_tm", - Account: "minu", - Destination: "0723", - AllowNegative: true, + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 34, 10, 0, time.UTC), + Direction: "*out", + Category: "0", + Tenant: "vdf", + Subject: "minu_from_tm", + Account: "minu", + Destination: "0723", } cd2 := cd1.Clone() cc1, err1 := cd1.Debit() diff --git a/engine/cdr.go b/engine/cdr.go index cae29ba85..569bd252c 100644 --- a/engine/cdr.go +++ b/engine/cdr.go @@ -866,7 +866,8 @@ type UsageRecord struct { func (self *UsageRecord) AsStoredCdr(timezone string) (*CDR, error) { var err error - cdr := &CDR{CGRID: self.GetId(), ToR: self.ToR, RequestType: self.RequestType, Direction: self.Direction, Tenant: self.Tenant, Category: self.Category, Account: self.Account, Subject: self.Subject, Destination: self.Destination} + cdr := &CDR{CGRID: self.GetId(), ToR: self.ToR, RequestType: self.RequestType, Direction: self.Direction, + Tenant: self.Tenant, Category: self.Category, Account: self.Account, Subject: self.Subject, Destination: self.Destination} if cdr.SetupTime, err = utils.ParseTimeDetectLayout(self.SetupTime, timezone); err != nil { return nil, err } @@ -885,18 +886,18 @@ func (self *UsageRecord) AsStoredCdr(timezone string) (*CDR, error) { return cdr, nil } -func (self *UsageRecord) AsCallDescriptor(timezone string) (*CallDescriptor, error) { +func (self *UsageRecord) AsCallDescriptor(timezone string, denyNegative bool) (*CallDescriptor, error) { var err error cd := &CallDescriptor{ - CgrID: self.GetId(), - TOR: self.ToR, - Direction: self.Direction, - Tenant: self.Tenant, - Category: self.Category, - Subject: self.Subject, - Account: self.Account, - Destination: self.Destination, - AllowNegative: true, + CgrID: self.GetId(), + TOR: self.ToR, + Direction: self.Direction, + Tenant: self.Tenant, + Category: self.Category, + Subject: self.Subject, + Account: self.Account, + Destination: self.Destination, + DenyNegativeAccount: denyNegative, } timeStr := self.AnswerTime if len(timeStr) == 0 { // In case of auth, answer time will not be defined, so take it out of setup one diff --git a/engine/cdr_test.go b/engine/cdr_test.go index ed4a3d615..2d9ba1c19 100644 --- a/engine/cdr_test.go +++ b/engine/cdr_test.go @@ -473,8 +473,10 @@ func TestUsageReqAsCD(t *testing.T) { Account: "1001", Subject: "1001", Destination: "1002", SetupTime: "2013-11-07T08:42:20Z", AnswerTime: "2013-11-07T08:42:26Z", Usage: "0.00000001", } - eCD := &CallDescriptor{CgrID: "9473e7b2e075d168b9da10ae957ee68fe5a217e4", TOR: req.ToR, Direction: req.Direction, Tenant: req.Tenant, Category: req.Category, Account: req.Account, Subject: req.Subject, Destination: req.Destination, TimeStart: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), TimeEnd: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).Add(time.Duration(10)), AllowNegative: true} - if cd, err := req.AsCallDescriptor(""); err != nil { + eCD := &CallDescriptor{CgrID: "9473e7b2e075d168b9da10ae957ee68fe5a217e4", TOR: req.ToR, Direction: req.Direction, Tenant: req.Tenant, + Category: req.Category, Account: req.Account, Subject: req.Subject, Destination: req.Destination, + TimeStart: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), TimeEnd: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).Add(time.Duration(10)), DenyNegativeAccount: true} + if cd, err := req.AsCallDescriptor("", true); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCD, cd) { t.Errorf("Expected: %+v, received: %+v", eCD, cd) diff --git a/general_tests/ddazmbl1_test.go b/general_tests/ddazmbl1_test.go index b041b036c..abe9d2be9 100644 --- a/general_tests/ddazmbl1_test.go +++ b/general_tests/ddazmbl1_test.go @@ -144,15 +144,14 @@ func TestExecuteActions(t *testing.T) { func TestDebit(t *testing.T) { cd := &engine.CallDescriptor{ - Direction: "*out", - Category: "call", - Tenant: "cgrates.org", - Subject: "12344", - Account: "12344", - Destination: "447956933443", - TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), - TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), - AllowNegative: true, + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "12344", + Account: "12344", + Destination: "447956933443", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), } if cc, err := cd.Debit(); err != nil { t.Error(err) diff --git a/general_tests/ddazmbl2_test.go b/general_tests/ddazmbl2_test.go index 77c08edae..7ea5fe9b6 100644 --- a/general_tests/ddazmbl2_test.go +++ b/general_tests/ddazmbl2_test.go @@ -144,15 +144,14 @@ func TestExecuteActions2(t *testing.T) { func TestDebit2(t *testing.T) { cd := &engine.CallDescriptor{ - Direction: "*out", - Category: "call", - Tenant: "cgrates.org", - Subject: "12345", - Account: "12345", - Destination: "447956933443", - TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), - TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), - AllowNegative: true, + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "12345", + Account: "12345", + Destination: "447956933443", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), } if cc, err := cd.Debit(); err != nil { t.Error(err) diff --git a/general_tests/ddazmbl3_test.go b/general_tests/ddazmbl3_test.go index 0b1d4fe4a..c5928bb6a 100644 --- a/general_tests/ddazmbl3_test.go +++ b/general_tests/ddazmbl3_test.go @@ -140,15 +140,14 @@ func TestExecuteActions3(t *testing.T) { func TestDebit3(t *testing.T) { cd := &engine.CallDescriptor{ - Direction: "*out", - Category: "call", - Tenant: "cgrates.org", - Subject: "12346", - Account: "12346", - Destination: "447956933443", - TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), - TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), - AllowNegative: true, + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "12346", + Account: "12346", + Destination: "447956933443", + TimeStart: time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC), + TimeEnd: time.Date(2014, 3, 4, 6, 0, 10, 0, time.UTC), } if cc, err := cd.Debit(); err != nil { t.Error(err)