diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index b8d9920d1..058bd9b04 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -134,17 +134,14 @@ func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, r if err != nil { return 0, err } - for idx, actr := range ub.ActionTriggers { + nactrs := make(engine.ActionTriggerPriotityList, 0) + for _, actr := range ub.ActionTriggers { match, _ := regexp.MatchString(attrs.ActionTriggersId, actr.Id) if len(attrs.ActionTriggersId) != 0 && !match { - continue - } - if len(ub.ActionTriggers) != 1 { // Remove by index - ub.ActionTriggers[idx], ub.ActionTriggers = ub.ActionTriggers[len(ub.ActionTriggers)-1], ub.ActionTriggers[:len(ub.ActionTriggers)-1] - } else { // For last item, simply reinit the slice - ub.ActionTriggers = make(engine.ActionTriggerPriotityList, 0) + nactrs = append(nactrs, actr) } } + ub.ActionTriggers = nactrs if err := self.AccountDb.SetAccount(ub); err != nil { return 0, err } diff --git a/engine/account.go b/engine/account.go index 46241aa1f..d3f88b126 100644 --- a/engine/account.go +++ b/engine/account.go @@ -150,7 +150,7 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { func (ub *Account) getBalancesForPrefix(prefix, category string, balances BalanceChain, sharedGroup string) BalanceChain { var usefulBalances BalanceChain for _, b := range balances { - if b.IsExpired() || (ub.AllowNegative == false && b.SharedGroup == "" && b.Value <= 0) { + if b.IsExpired() || (b.SharedGroup == "" && b.Value <= 0) { continue } if sharedGroup != "" && b.SharedGroup != sharedGroup { @@ -320,18 +320,23 @@ func (ub *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun boo if err != nil { Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) } - initialLength = len(cc.Timespans) - cc.Timespans = append(cc.Timespans, leftCC.Timespans...) - if initialLength == 0 { - // this is the first add, debit the connect fee - ub.DebitConnectionFee(cc, usefulMoneyBalances, count) + if leftCC.Cost == 0 && len(leftCC.Timespans) > 0 { + cc.Timespans = append(cc.Timespans, leftCC.Timespans...) } - if leftCC.Cost == 0 || goNegative { - //log.Printf("Left CC: %+v", leftCC) + + //log.Printf("HERE: %+v %d", leftCC) + if leftCC.Cost > 0 && goNegative { + initialLength = len(cc.Timespans) + cc.Timespans = append(cc.Timespans, leftCC.Timespans...) + if initialLength == 0 { + // this is the first add, debit the connect fee + ub.DebitConnectionFee(cc, usefulMoneyBalances, count) + } + //log.Printf("Left CC: %+v ", leftCC) // get the default money balanance // and go negative on it with the amount still unpaid - if len(leftCC.Timespans) > 0 && leftCC.Cost > 0 && !ub.AllowNegative { - err = errors.New("not enough credit") + if len(leftCC.Timespans) > 0 && leftCC.Cost > 0 && !ub.AllowNegative && !dryRun { + Logger.Err(fmt.Sprintf(" Going negative on account %s with AllowNegative: false", cd.GetAccountKey())) } for _, ts := range leftCC.Timespans { if ts.Increments == nil { diff --git a/engine/account_test.go b/engine/account_test.go index b00acdbdc..22d0e585b 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -803,134 +803,6 @@ func TestDebitCreditSubjectMoney(t *testing.T) { } }*/ -func TestDebitCreditSubjectMixedMoreTS(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationIds: "NAT", RatingSubject: "minu"} - cc := &CallCost{ - Tenant: "vdf", - Category: "0", - Direction: OUTBOUND, - Destination: "0723045326", - Timespans: []*TimeSpan{ - &TimeSpan{ - TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), - DurationIndex: 0, - RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, - }, - &TimeSpan{ - TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), - DurationIndex: 10 * time.Second, - RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, - }, - }, - TOR: utils.VOICE, - deductConnectFee: true, - } - cd := &CallDescriptor{ - Tenant: cc.Tenant, - Category: cc.Category, - TimeStart: cc.Timespans[0].TimeStart, - TimeEnd: cc.Timespans[1].TimeEnd, - Direction: cc.Direction, - Destination: cc.Destination, - TOR: cc.TOR, - DurationIndex: cc.GetDuration(), - testCallcost: cc, - } - rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ - utils.VOICE + OUTBOUND: BalanceChain{b1}, - utils.MONETARY + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50, RatingSubject: "minu"}}, - }} - var err error - cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) - if err == nil { - t.Error("Error showing debiting balance error: ", err) - } - //t.Logf("%+v %+v", cc.Timespans[0], cc.Timespans[1]) - /*if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testb" || - cc.Timespans[0].Increments[0].Duration != time.Second { - t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) - } - if rifsBalance.BalanceMap[utils.VOICE+OUTBOUND][0].Value != 20 || - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][0].Value != 0 || - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][1].Value != -31 { - t.Errorf("Error extracting minutes from balance: %+v, %+v, %+v", - rifsBalance.BalanceMap[utils.VOICE+OUTBOUND][0].Value, rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][0].Value, - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][1].Value) - } - if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != 50*time.Second || cc.Timespans[1].GetDuration() != 30*time.Second { - for _, ts := range cc.Timespans { - t.Log(ts.GetDuration()) - } - t.Error("Error truncating extra timespans: ", len(cc.Timespans)) - }*/ -} - -func TestDebitCreditSubjectMixedPartPay(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationIds: "NAT", RatingSubject: "minu"} - cc := &CallCost{ - Tenant: "vdf", - Category: "0", - Direction: OUTBOUND, - Destination: "0723045326", - Timespans: []*TimeSpan{ - &TimeSpan{ - TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), - DurationIndex: 0, - RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, - }, - &TimeSpan{ - TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), - DurationIndex: 10 * time.Second, - RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, - }, - }, - TOR: utils.VOICE, - deductConnectFee: true, - } - cd := &CallDescriptor{ - Tenant: cc.Tenant, - Category: cc.Category, - TimeStart: cc.Timespans[0].TimeStart, - TimeEnd: cc.Timespans[1].TimeEnd, - Direction: cc.Direction, - Destination: cc.Destination, - TOR: cc.TOR, - DurationIndex: cc.GetDuration(), - testCallcost: cc, - } - rifsBalance := &Account{Id: "other", BalanceMap: map[string]BalanceChain{ - utils.VOICE + OUTBOUND: BalanceChain{b1}, - utils.MONETARY + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 75, RatingSubject: "minu"}}, - }} - var err error - cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) - if err == nil { - t.Error("Error showing debiting balance error: ", err) - } - //t.Logf("%+v %+v", cc.Timespans[0], cc.Timespans[1]) - /*if cc.Timespans[0].Increments[0].BalanceInfo.UnitBalanceUuid != "testb" || - cc.Timespans[0].Increments[0].Duration != time.Second { - t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) - } - if rifsBalance.BalanceMap[utils.VOICE+OUTBOUND][0].Value != 0 || - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][0].Value != 0 || - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][1].Value != -11 { - t.Errorf("Error extracting minutes from balance: %+v, %+v %+v", - rifsBalance.BalanceMap[utils.VOICE+OUTBOUND][0].Value, rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][0].Value, - rifsBalance.BalanceMap[utils.MONETARY+OUTBOUND][1].Value) - } - if len(cc.Timespans) != 3 || cc.Timespans[0].GetDuration() != 70*time.Second || cc.Timespans[1].GetDuration() != 5*time.Second || cc.Timespans[2].GetDuration() != 5*time.Second { - for _, ts := range cc.Timespans { - t.Log(ts.GetDuration()) - } - t.Error("Error truncating extra timespans: ", len(cc.Timespans)) - }*/ -} - func TestAccountdebitBalance(t *testing.T) { ub := &Account{ Id: "rif", diff --git a/engine/balances.go b/engine/balances.go index ca4678913..56fac1c74 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -314,6 +314,12 @@ func (b *Balance) DebitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala }, } prefix, destid := b.getMatchingPrefixAndDestId(cd.Destination) + if prefix == "" { + prefix = cd.Destination + } + if destid == "" { + destid = utils.ANY + } ts.setRatingInfo(&RatingInfo{ MatchedSubject: b.Uuid, MatchedPrefix: prefix, diff --git a/engine/calldesc.go b/engine/calldesc.go index 60e82a074..01ba1da85 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -525,6 +525,9 @@ func (origCD *CallDescriptor) getMaxSessionDuration(origAcc *Account) (time.Dura return utils.MinDuration(initialDuration, totalDuration), nil } } + if ts.Increments == nil { + ts.createIncrementsSlice() + } for _, incr := range ts.Increments { totalCost += incr.Cost if defaultBalance.Value < 0 && incr.BalanceInfo.MoneyBalanceUuid == defaultBalance.Uuid { diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 4d6291b0c..e9423bc0b 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -828,7 +828,7 @@ func TestMaxSesionTimeEmptyBalance(t *testing.T) { acc, _ := accountingStorage.GetAccount("*out:vdf:luna") allowedTime, err := cd.getMaxSessionDuration(acc) if err != nil || allowedTime != 0 { - t.Error("Error get max session for 0 acount") + t.Error("Error get max session for 0 acount", err) } } @@ -846,7 +846,7 @@ func TestMaxSesionTimeEmptyBalanceAndNoCost(t *testing.T) { acc, _ := accountingStorage.GetAccount("*out:vdf:luna") allowedTime, err := cd.getMaxSessionDuration(acc) if err != nil || allowedTime == 0 { - t.Error("Error get max session for 0 acount") + t.Error("Error get max session for 0 acount", err) } } @@ -906,6 +906,43 @@ func TestDebitFromEmptyShare(t *testing.T) { } } +func TestDebitNegatve(t *testing.T) { + ap, _ := ratingStorage.GetActionPlans("POST_AT") + for _, at := range ap { + at.Execute() + } + + cd := &CallDescriptor{ + TimeStart: time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC), + Direction: "*out", + Category: "0", + Tenant: "vdf", + Subject: "rif", + Account: "post", + Destination: "0723", + } + cc, err := cd.MaxDebit() + //utils.PrintFull(cc) + if err != nil || cc.Cost != 2.5 { + t.Errorf("Debit from empty share error: %+v, %v", cc, err) + } + acc, _ := cd.getAccount() + //utils.PrintFull(acc) + balanceMap := acc.BalanceMap[utils.MONETARY+OUTBOUND] + if len(balanceMap) != 1 || balanceMap[0].Value != -2.5 { + t.Errorf("Error debiting from empty share: %+v", balanceMap[0].Value) + } + cc, err = cd.MaxDebit() + //utils.PrintFull(cc) + if err != nil || cc.Cost != 2.5 { + t.Errorf("Debit from empty share error: %+v, %v", cc, err) + } + if len(balanceMap) != 1 || balanceMap[0].Value != -5 { + t.Errorf("Error debiting from empty share: %+v", balanceMap[0].Value) + } +} + func TestMaxDebitZeroDefinedRate(t *testing.T) { ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") for _, at := range ap { diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index f988faadb..f3a85049a 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -160,6 +160,7 @@ SE10,*topup,,,*monetary,*out,,,,,*unlimited,,10,10,10 EE0,*topup_reset,,,*monetary,*out,,,,SG3,*unlimited,,0,10,10 EE0,*allow_negative,,,*monetary,*out,,,,,*unlimited,,0,10,10 DEFEE,*cdrlog,"{""Category"":""^ddi"",""MediationRunId"":""^did_run""}",,,,,,,,,,,,10 +NEG,*allow_negative,,,*monetary,*out,,,,,*unlimited,,0,10,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 @@ -169,6 +170,7 @@ TOPUP10_AT,TOPUP10_AC1,*asap,10 TOPUP_SHARED0_AT,SE0,*asap,10 TOPUP_SHARED10_AT,SE10,*asap,10 TOPUP_EMPTY_AT,EE0,*asap,10 +POST_AT,NEG,*asap,10 ` actionTriggers = ` @@ -190,6 +192,7 @@ vdf,empty0,*out,TOPUP_SHARED0_AT, vdf,empty10,*out,TOPUP_SHARED10_AT, vdf,emptyX,*out,TOPUP_EMPTY_AT, vdf,emptyY,*out,TOPUP_EMPTY_AT, +vdf,post,*out,POST_AT, ` derivedCharges = ` @@ -765,8 +768,8 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 8 { - t.Error("Failed to load actions: ", csvr.actions) + if len(csvr.actions) != 9 { + t.Error("Failed to load actions: ", len(csvr.actions)) } as1 := csvr.actions["MINI"] expected := []*Action{ @@ -928,8 +931,8 @@ func TestLoadLCRs(t *testing.T) { } func TestLoadActionTimings(t *testing.T) { - if len(csvr.actionsTimings) != 5 { - t.Error("Failed to load action timings: ", csvr.actionsTimings) + if len(csvr.actionsTimings) != 6 { + t.Error("Failed to load action timings: ", len(csvr.actionsTimings)) } atm := csvr.actionsTimings["MORE_MINUTES"][0] expected := &ActionPlan{ @@ -990,8 +993,8 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 6 { - t.Error("Failed to load account actions: ", csvr.accountActions) + if len(csvr.accountActions) != 7 { + t.Error("Failed to load account actions: ", len(csvr.accountActions)) } aa := csvr.accountActions["*out:vdf:minitsboy"] expected := &Account{ diff --git a/engine/stats.go b/engine/stats.go index 512cd4646..c6482f6b4 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -135,7 +135,7 @@ func (s *Stats) ResetQueues(ids []string, out *int) error { return nil } -// change the xisting ones +// change the existing ones // add new ones // delete the ones missing from the new list func (s *Stats) UpdateQueues(css []*CdrStats, out *int) error {