diff --git a/engine/balances.go b/engine/balances.go index c8e9217ba..f1f067849 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -136,56 +136,50 @@ func (b *Balance) DebitMinutes(cc *CallCost, count bool, ub *UserBalance, moneyB if increment.paid { continue } - if b.RateSubject == ZEROSECOND || b.RateSubject == "" { - amount := increment.Duration.Seconds() - if b.Value >= amount { - b.Value -= amount - increment.SetMinuteBalance(b.Uuid) - increment.MinuteInfo = &MinuteInfo{cc.Destination, amount} - increment.Cost = 0 - increment.paid = true - if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) - } + if duration, err := utils.ParseZeroRatingSubject(b.RateSubject); err == nil { + seconds := duration.Seconds() + amount := seconds + if seconds == 1 { + amount = increment.Duration.Seconds() } - continue - } - if b.RateSubject == ZEROMINUTE { - amount := time.Minute.Seconds() if b.Value >= amount { // balance has at least 60 seconds newTs := ts - if incrementIndex != 0 { - // if increment it's not at the begining we must split the timespan - newTs = ts.SplitByIncrement(incrementIndex) - } - newTs.RoundToDuration(time.Minute) - newTs.RateInterval = &RateInterval{ - Rating: &RIRate{ - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0, - RateIncrement: time.Minute, - RateUnit: time.Minute, + inc := increment + if seconds > 1 { // we need to recreate increments + if incrementIndex != 0 { + // if increment it's not at the begining we must split the timespan + newTs = ts.SplitByIncrement(incrementIndex) + } + newTs.RoundToDuration(time.Minute) + newTs.RateInterval = &RateInterval{ + Rating: &RIRate{ + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0, + RateIncrement: time.Minute, + RateUnit: time.Minute, + }, }, }, - }, + } + newTs.createIncrementsSlice() + // insert the new timespan + if newTs != ts { + tsIndex++ + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) + cc.Timespans[tsIndex] = newTs + tsWasSplit = true + } + cc.Timespans.RemoveOverlapedFromIndex(tsIndex) + inc = newTs.Increments[0] } - newTs.createIncrementsSlice() - // insert the new timespan - if newTs != ts { - tsIndex++ - cc.Timespans = append(cc.Timespans, nil) - copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) - cc.Timespans[tsIndex] = newTs - tsWasSplit = true - } - cc.Timespans.RemoveOverlapedFromIndex(tsIndex) b.Value -= amount - newTs.Increments[0].SetMinuteBalance(b.Uuid) - newTs.Increments[0].MinuteInfo = &MinuteInfo{cc.Destination, amount} - newTs.Increments[0].Cost = 0 - newTs.Increments[0].paid = true + inc.SetMinuteBalance(b.Uuid) + inc.MinuteInfo = &MinuteInfo{cc.Destination, amount} + inc.Cost = 0 + inc.paid = true if count { ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) } diff --git a/engine/calldesc.go b/engine/calldesc.go index 13f5dce1c..8be71b04d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -531,7 +531,6 @@ func (cd *CallDescriptor) MaxDebit() (cc *CallCost, err error) { if err != nil || remainingDuration == 0 { return new(CallCost), errors.New("no more credit") } - log.Print("REM_DUR: ", remainingDuration) if remainingDuration > 0 { // for postpaying client returns -1 cd.TimeEnd = cd.TimeStart.Add(remainingDuration) } diff --git a/engine/userbalance.go b/engine/userbalance.go index e61ebfd4c..ce1c05a2d 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -48,9 +48,6 @@ const ( TRIGGER_MAX_COUNTER = "*max_counter" TRIGGER_MIN_BALANCE = "*min_balance" TRIGGER_MAX_BALANCE = "*max_balance" - // minute subjects - ZEROSECOND = "*zerosecond" - ZEROMINUTE = "*zerominute" ) var ( diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 4f91fd63f..b176d841b 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -214,7 +214,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } func TestDebitCreditZeroSecond(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -243,7 +243,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { } func TestDebitCreditZeroMinute(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -276,8 +276,8 @@ func TestDebitCreditZeroMinute(t *testing.T) { } } func TestDebitCreditZeroMixedMinute(t *testing.T) { - b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1m"} + b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -311,7 +311,7 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { } func TestDebitCreditNoCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -351,7 +351,7 @@ func TestDebitCreditNoCredit(t *testing.T) { } func TestDebitCreditHasCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -393,7 +393,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } func TestDebitCreditSplitMinutesMoney(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -430,7 +430,7 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } func TestDebitCreditMoreTimespans(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -467,8 +467,8 @@ func TestDebitCreditMoreTimespans(t *testing.T) { } func TestDebitCreditMoreTimespansMixed(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} + b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: "*zero1s"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -506,7 +506,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { } func TestDebitCreditNoConectFeeCredit(t *testing.T) { - b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: "*zero1m"} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", diff --git a/utils/consts.go b/utils/consts.go index 8cadcd6ef..33982b065 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1,87 +1,88 @@ package utils const ( - VERSION = "0.9.1c4" - POSTGRES = "postgres" - MYSQL = "mysql" - MONGO = "mongo" - REDIS = "redis" - LOCALHOST = "127.0.0.1" - FSCDR_FILE_CSV = "freeswitch_file_csv" - FSCDR_HTTP_JSON = "freeswitch_http_json" - NOT_IMPLEMENTED = "not implemented" - PREPAID = "prepaid" - POSTPAID = "postpaid" - PSEUDOPREPAID = "pseudoprepaid" - RATED = "rated" - ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" - ERR_SERVER_ERROR = "SERVER_ERROR" - ERR_NOT_FOUND = "NOT_FOUND" - ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" - ERR_EXISTS = "EXISTS" - ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" - TBL_TP_TIMINGS = "tp_timings" - TBL_TP_DESTINATIONS = "tp_destinations" - TBL_TP_RATES = "tp_rates" - TBL_TP_DESTINATION_RATES = "tp_destination_rates" - TBL_TP_RATING_PLANS = "tp_rating_plans" - TBL_TP_RATE_PROFILES = "tp_rating_profiles" - TBL_TP_ACTIONS = "tp_actions" - TBL_TP_ACTION_PLANS = "tp_action_plans" - TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" - TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" - TBL_CDRS_PRIMARY = "cdrs_primary" - TBL_CDRS_EXTRA = "cdrs_extra" - TBL_COST_DETAILS = "cost_details" - TBL_RATED_CDRS = "rated_cdrs" - TIMINGS_CSV = "Timings.csv" - DESTINATIONS_CSV = "Destinations.csv" - RATES_CSV = "Rates.csv" - DESTINATION_RATES_CSV = "DestinationRates.csv" - RATING_PLANS_CSV = "RatingPlans.csv" - RATING_PROFILES_CSV = "RatingProfiles.csv" - ACTIONS_CSV = "Actions.csv" - ACTION_PLANS_CSV = "ActionPlans.csv" - ACTION_TRIGGERS_CSV = "ActionTriggers.csv" - ACCOUNT_ACTIONS_CSV = "AccountActions.csv" - TIMINGS_NRCOLS = 6 - DESTINATIONS_NRCOLS = 2 - RATES_NRCOLS = 8 - DESTINATION_RATES_NRCOLS = 3 - DESTRATE_TIMINGS_NRCOLS = 4 - RATE_PROFILES_NRCOLS = 7 - ACTIONS_NRCOLS = 11 - ACTION_PLANS_NRCOLS = 4 - ACTION_TRIGGERS_NRCOLS = 8 - ACCOUNT_ACTIONS_NRCOLS = 5 - ROUNDING_UP = "*up" - ROUNDING_MIDDLE = "*middle" - ROUNDING_DOWN = "*down" - ANY = "*any" - COMMENT_CHAR = '#' - CSV_SEP = ',' - FALLBACK_SEP = ';' - JSON = "json" - MSGPACK = "msgpack" - CSV_LOAD = "CSVLOAD" - CGRID = "cgrid" - ACCID = "accid" - CDRHOST = "cdrhost" - CDRSOURCE = "cdrsource" - REQTYPE = "reqtype" - DIRECTION = "direction" - TENANT = "tenant" - TOR = "tor" - ACCOUNT = "account" - SUBJECT = "subject" - DESTINATION = "destination" - ANSWER_TIME = "answer_time" - DURATION = "duration" - DEFAULT_RUNID = "default" - STATIC_VALUE_PREFIX = "^" - CDRE_CSV = "csv" - CDRE_DRYRUN = "dry_run" - INTERNAL = "internal" + VERSION = "0.9.1c4" + POSTGRES = "postgres" + MYSQL = "mysql" + MONGO = "mongo" + REDIS = "redis" + LOCALHOST = "127.0.0.1" + FSCDR_FILE_CSV = "freeswitch_file_csv" + FSCDR_HTTP_JSON = "freeswitch_http_json" + NOT_IMPLEMENTED = "not implemented" + PREPAID = "prepaid" + POSTPAID = "postpaid" + PSEUDOPREPAID = "pseudoprepaid" + RATED = "rated" + ERR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + ERR_SERVER_ERROR = "SERVER_ERROR" + ERR_NOT_FOUND = "NOT_FOUND" + ERR_MANDATORY_IE_MISSING = "MANDATORY_IE_MISSING" + ERR_EXISTS = "EXISTS" + ERR_BROKEN_REFERENCE = "BROKEN_REFERENCE" + TBL_TP_TIMINGS = "tp_timings" + TBL_TP_DESTINATIONS = "tp_destinations" + TBL_TP_RATES = "tp_rates" + TBL_TP_DESTINATION_RATES = "tp_destination_rates" + TBL_TP_RATING_PLANS = "tp_rating_plans" + TBL_TP_RATE_PROFILES = "tp_rating_profiles" + TBL_TP_ACTIONS = "tp_actions" + TBL_TP_ACTION_PLANS = "tp_action_plans" + TBL_TP_ACTION_TRIGGERS = "tp_action_triggers" + TBL_TP_ACCOUNT_ACTIONS = "tp_account_actions" + TBL_CDRS_PRIMARY = "cdrs_primary" + TBL_CDRS_EXTRA = "cdrs_extra" + TBL_COST_DETAILS = "cost_details" + TBL_RATED_CDRS = "rated_cdrs" + TIMINGS_CSV = "Timings.csv" + DESTINATIONS_CSV = "Destinations.csv" + RATES_CSV = "Rates.csv" + DESTINATION_RATES_CSV = "DestinationRates.csv" + RATING_PLANS_CSV = "RatingPlans.csv" + RATING_PROFILES_CSV = "RatingProfiles.csv" + ACTIONS_CSV = "Actions.csv" + ACTION_PLANS_CSV = "ActionPlans.csv" + ACTION_TRIGGERS_CSV = "ActionTriggers.csv" + ACCOUNT_ACTIONS_CSV = "AccountActions.csv" + TIMINGS_NRCOLS = 6 + DESTINATIONS_NRCOLS = 2 + RATES_NRCOLS = 8 + DESTINATION_RATES_NRCOLS = 3 + DESTRATE_TIMINGS_NRCOLS = 4 + RATE_PROFILES_NRCOLS = 7 + ACTIONS_NRCOLS = 11 + ACTION_PLANS_NRCOLS = 4 + ACTION_TRIGGERS_NRCOLS = 8 + ACCOUNT_ACTIONS_NRCOLS = 5 + ROUNDING_UP = "*up" + ROUNDING_MIDDLE = "*middle" + ROUNDING_DOWN = "*down" + ANY = "*any" + COMMENT_CHAR = '#' + CSV_SEP = ',' + FALLBACK_SEP = ';' + JSON = "json" + MSGPACK = "msgpack" + CSV_LOAD = "CSVLOAD" + CGRID = "cgrid" + ACCID = "accid" + CDRHOST = "cdrhost" + CDRSOURCE = "cdrsource" + REQTYPE = "reqtype" + DIRECTION = "direction" + TENANT = "tenant" + TOR = "tor" + ACCOUNT = "account" + SUBJECT = "subject" + DESTINATION = "destination" + ANSWER_TIME = "answer_time" + DURATION = "duration" + DEFAULT_RUNID = "default" + STATIC_VALUE_PREFIX = "^" + CDRE_CSV = "csv" + CDRE_DRYRUN = "dry_run" + INTERNAL = "internal" + ZERO_RATING_SUBJECT_PREFIX = "*zero" ) var ( diff --git a/utils/coreutils.go b/utils/coreutils.go index 08506ac1c..80ebb4d5b 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -195,3 +195,14 @@ func MinDuration(d1, d2 time.Duration) time.Duration { } return d2 } + +func ParseZeroRatingSubject(rateSubj string) (time.Duration, error) { + if rateSubj == "" { + rateSubj = ZERO_RATING_SUBJECT_PREFIX + "1s" + } + if !strings.HasPrefix(rateSubj, ZERO_RATING_SUBJECT_PREFIX) { + return 0, errors.New("malformed rating subject: " + rateSubj) + } + durStr := rateSubj[len(ZERO_RATING_SUBJECT_PREFIX):] + return time.ParseDuration(durStr) +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 36f53d1a7..b99df7647 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -346,3 +346,13 @@ func TestMinDuration(t *testing.T) { t.Error("Error getting min duration: ", minD1, minD2) } } + +func TestParseZeroRatingSubject(t *testing.T) { + subj := []string{"", "*zero1s", "*zero5m", "*zero10h"} + dur := []time.Duration{time.Second, time.Second, 5 * time.Minute, 10 * time.Hour} + for i, s := range subj { + if d, err := ParseZeroRatingSubject(s); err != nil || d != dur[i] { + t.Error("Error parsing rating subject: ", s, d, err) + } + } +}