rating subject can have multiple predefined values

Valid values can start with *zero + any duration 1m, 15m, 1h10m
This commit is contained in:
Radu Ioan Fericean
2014-02-07 22:26:39 +02:00
parent 77ea2754cb
commit 914fe77117
7 changed files with 151 additions and 139 deletions

View File

@@ -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}})
}

View File

@@ -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)
}

View File

@@ -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 (

View File

@@ -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",

View File

@@ -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 (

View File

@@ -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)
}

View File

@@ -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)
}
}
}