diff --git a/apier/apier.go b/apier/apier.go index a89b6899f..4b4e34278 100644 --- a/apier/apier.go +++ b/apier/apier.go @@ -19,6 +19,7 @@ along with this program. If not, see package apier import ( + "errors" "fmt" "github.com/cgrates/cgrates/rater" "github.com/cgrates/cgrates/scheduler" diff --git a/apier/tpactions.go b/apier/tpactions.go new file mode 100644 index 000000000..fee05140b --- /dev/null +++ b/apier/tpactions.go @@ -0,0 +1,90 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2013 ITsysCOM + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package apier + +import ( + "errors" + "fmt" + "github.com/cgrates/cgrates/utils" +) + +// Creates a new Actions profile within a tariff plan +func (self *Apier) SetTPActions(attrs utils.TPActions, reply *string) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionsId", "Actions"}); len(missing) != 0 { + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + for _, action := range attrs.Actions { + requiredFields := []string{"Identifier", "Weight"} + if action.BalanceId != "" { // Add some inter-dependent parameters - if balanceType then we are not talking about simply calling actions + requiredFields = append(requiredFields, "Direction", "Units", "ExpirationTime") + } + if missing := utils.MissingStructFields(&action, requiredFields); len(missing) != 0 { + return fmt.Errorf("%s:Action:%s:%v", utils.ERR_MANDATORY_IE_MISSING, action.Identifier, missing) + } + } + if exists, err := self.StorDb.ExistsTPActions(attrs.TPid, attrs.ActionsId); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if exists { + return errors.New(utils.ERR_DUPLICATE) + } + if err := self.StorDb.SetTPActions(&attrs); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + *reply = "OK" + return nil +} + +type AttrGetTPActions struct { + TPid string // Tariff plan id + ActionsId string // Actions id +} + +// Queries specific Actions on tariff plan +func (self *Apier) GetTPActions(attrs AttrGetTPActions, reply *utils.TPActions) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionsId"}); len(missing) != 0 { //Params missing + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + if acts, err := self.StorDb.GetTPActions(attrs.TPid, attrs.ActionsId); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if acts == nil { + return errors.New(utils.ERR_NOT_FOUND) + } else { + *reply = *acts + } + return nil +} + +type AttrGetTPActionIds struct { + TPid string // Tariff plan id +} + +// Queries Actions identities on specific tariff plan. +func (self *Apier) GetTPActionIds(attrs AttrGetTPActionIds, reply *[]string) error { + if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + if ids, err := self.StorDb.GetTPActionIds(attrs.TPid); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } else if ids == nil { + return errors.New(utils.ERR_NOT_FOUND) + } else { + *reply = ids + } + return nil +} diff --git a/apier/tprateprofiles.go b/apier/tprateprofiles.go index 36c1bc5aa..4ec4eefd7 100644 --- a/apier/tprateprofiles.go +++ b/apier/tprateprofiles.go @@ -44,8 +44,8 @@ func (self *Apier) SetTPRateProfile(attrs utils.TPRateProfile, reply *string) er } type AttrGetTPRateProfile struct { - TPid string // Tariff plan id - RateProfileId string // RateProfile id + TPid string // Tariff plan id + RateProfileId string // RateProfile id } // Queries specific RateProfile on tariff plan diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index 8a0431e96..5d4e52983 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -114,10 +114,11 @@ CREATE TABLE `tp_actions` ( `tpid` char(40) NOT NULL, `tag` varchar(24) NOT NULL, `action` varchar(24) NOT NULL, - `balances_tag` varchar(24) NOT NULL, + `balance_tag` varchar(24) NOT NULL, `direction` varchar(8) NOT NULL, `units` DECIMAL(5,2) NOT NULL, - `destinations_tag` varchar(24) NOT NULL, + `expiration_time` int(11) NOT NULL, + `destination_tag` varchar(24) NOT NULL, `rate_type` varchar(8) NOT NULL, `rate` DECIMAL(5,4) NOT NULL, `minutes_weight` DECIMAL(5,2) NOT NULL, @@ -135,7 +136,7 @@ CREATE TABLE `tp_action_timings` ( `tpid` char(40) NOT NULL, `tag` varchar(24) NOT NULL, `actions_tag` varchar(24) NOT NULL, - `timings_tag` varchar(24) NOT NULL, + `timing_tag` varchar(24) NOT NULL, `weight` DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`) @@ -149,10 +150,11 @@ CREATE TABLE `tp_action_triggers` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tpid` char(40) NOT NULL, `tag` varchar(24) NOT NULL, - `balances_tag` varchar(24) NOT NULL, + `balance_tag` varchar(24) NOT NULL, `direction` varchar(8) NOT NULL, - `threshold` DECIMAL(5,4) NOT NULL, - `destinations_tag` varchar(24) NOT NULL, + `threshold_value` DECIMAL(5,4) NOT NULL, + `tpid` char(7) NOT NULL, + `destination_tag` varchar(24) NOT NULL, `actions_tag` varchar(24) NOT NULL, `weight` DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`), @@ -166,6 +168,7 @@ CREATE TABLE `tp_action_triggers` ( CREATE TABLE `tp_account_actions` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tpid` char(40) NOT NULL, + `tag` varchar(24) NOT NULL, `tenant` varchar(64) NOT NULL, `account` varchar(64) NOT NULL, `direction` varchar(8) NOT NULL, diff --git a/rater/loader_db.go b/rater/loader_db.go index 7fffc230f..1dd1a2b7e 100644 --- a/rater/loader_db.go +++ b/rater/loader_db.go @@ -206,7 +206,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) (*RatingProfile, error) rpm, err := dbr.storDB.GetTpRatingProfiles(dbr.tpid, tag) if err != nil { return nil, err - } else if len(rpm)==0 { + } else if len(rpm) == 0 { return nil, fmt.Errorf("No RateProfile with id: %s", tag) } for _, ratingProfile := range rpm { @@ -215,28 +215,28 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) (*RatingProfile, error) drtm, err := dbr.storDB.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.destRatesTimingTag) if err != nil { return nil, err - } else if len(drtm)==0 { + } else if len(drtm) == 0 { return nil, fmt.Errorf("No DestRateTimings profile with id: %s", ratingProfile.destRatesTimingTag) } for _, destrateTiming := range drtm { tm, err := dbr.storDB.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag) if err != nil { return nil, err - } else if len(tm)==0 { + } else if len(tm) == 0 { return nil, fmt.Errorf("No Timings profile with id: %s", destrateTiming.TimingsTag) } destrateTiming.timing = tm[destrateTiming.TimingsTag] drm, err := dbr.storDB.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag) if err != nil { return nil, err - } else if len(drm)==0 { + } else if len(drm) == 0 { return nil, fmt.Errorf("No Timings profile with id: %s", destrateTiming.DestinationRatesTag) } for _, drate := range drm[destrateTiming.DestinationRatesTag] { rt, err := dbr.storDB.GetTpRates(dbr.tpid, drate.RateTag) if err != nil { return nil, err - } else if len(rt)==0 { + } else if len(rt) == 0 { return nil, fmt.Errorf("No Rates profile with id: %s", drate.RateTag) } drate.Rate = rt[drate.RateTag] diff --git a/rater/loader_helpers.go b/rater/loader_helpers.go index d4f866cbd..1bd9008cd 100644 --- a/rater/loader_helpers.go +++ b/rater/loader_helpers.go @@ -43,11 +43,11 @@ type TPLoader interface { } type Rate struct { - Tag string + Tag string ConnectFee, Price, PricedUnits, RateIncrements float64 - RoundingMethod string - RoundingDecimals int - Weight float64 + RoundingMethod string + RoundingDecimals int + Weight float64 } func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, weight string) (r *Rate, err error) { diff --git a/rater/ratingprofile.go b/rater/ratingprofile.go index 12586140f..70a88cfdc 100644 --- a/rater/ratingprofile.go +++ b/rater/ratingprofile.go @@ -29,11 +29,11 @@ const ( ) type RatingProfile struct { - Id string - FallbackKey string - DestinationMap map[string][]*ActivationPeriod + Id string + FallbackKey string + DestinationMap map[string][]*ActivationPeriod tag, destRatesTimingTag string // used only for loading - activationTime int64 + activationTime int64 } // Adds an activation period that applyes to current rating profile if not already present. diff --git a/rater/storage_interface.go b/rater/storage_interface.go index 97116205f..a28054afa 100644 --- a/rater/storage_interface.go +++ b/rater/storage_interface.go @@ -83,6 +83,10 @@ type DataStorage interface { SetTPRateProfile(*utils.TPRateProfile) error GetTPRateProfile(string, string) (*utils.TPRateProfile, error) GetTPRateProfileIds(*utils.AttrTPRateProfileIds) ([]string, error) + ExistsTPActions(string, string) (bool, error) + SetTPActions(*utils.TPActions) error + GetTPActions(string, string) (*utils.TPActions, error) + GetTPActionIds(string) ([]string, error) // End Apier functions GetActions(string) (Actions, error) SetActions(string, Actions) error diff --git a/rater/storage_map.go b/rater/storage_map.go index ef52b65fc..c3672ba90 100644 --- a/rater/storage_map.go +++ b/rater/storage_map.go @@ -173,6 +173,22 @@ func (ms *MapStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ( return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (ms *MapStorage) ExistsTPActions(tpid, aId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MapStorage) SetTPActions(ap *utils.TPActions) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MapStorage) GetTPActions(tpid, aId string) (*utils.TPActions, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MapStorage) GetTPActionIds(tpid string) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + func (ms *MapStorage) GetActions(key string) (as Actions, err error) { if values, ok := ms.dict[ACTION_PREFIX+key]; ok { err = ms.ms.Unmarshal(values, &as) diff --git a/rater/storage_mongo.go b/rater/storage_mongo.go index d008a4464..1fbcab6a3 100644 --- a/rater/storage_mongo.go +++ b/rater/storage_mongo.go @@ -248,6 +248,22 @@ func (ms *MongoStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (ms *MongoStorage) ExistsTPActions(tpid, aId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) SetTPActions(ap *utils.TPActions) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) GetTPActions(tpid, aId string) (*utils.TPActions, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (ms *MongoStorage) GetTPActionIds(tpid string) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + func (ms *MongoStorage) GetActions(key string) (as Actions, err error) { result := AcKeyValue{} err = ms.db.C("actions").Find(bson.M{"key": key}).One(&result) diff --git a/rater/storage_redis.go b/rater/storage_redis.go index 736858248..b97de290d 100644 --- a/rater/storage_redis.go +++ b/rater/storage_redis.go @@ -203,6 +203,22 @@ func (rs *RedisStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) } +func (rs *RedisStorage) ExistsTPActions(tpid, aId string) (bool, error) { + return false, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) SetTPActions(ap *utils.TPActions) error { + return errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) GetTPActions(tpid, aId string) (*utils.TPActions, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + +func (rs *RedisStorage) GetTPActionIds(tpid string) ([]string, error) { + return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) +} + func (rs *RedisStorage) GetActions(key string) (as Actions, err error) { var values string if values, err = rs.db.Get(ACTION_PREFIX + key); err == nil { diff --git a/rater/storage_sql.go b/rater/storage_sql.go index 02dab59e1..1ffae83b4 100644 --- a/rater/storage_sql.go +++ b/rater/storage_sql.go @@ -211,8 +211,8 @@ func (self *SQLStorage) ExistsTPRate(tpid, rtId string) (bool, error) { func (self *SQLStorage) SetTPRate(rt *utils.TPRate) error { for _, rtSlot := range rt.RateSlots { if _, err := self.Db.Exec(fmt.Sprintf("INSERT INTO %s (tpid, tag, connect_fee, rate, rated_units, rate_increments, rounding_method, rounding_decimals, weight) VALUES ('%s', '%s', %f, %f, %d, %d,'%s', %d, %f)", - utils.TBL_TP_RATES, rt.TPid, rt.RateId, rtSlot.ConnectFee, rtSlot.Rate, rtSlot.RatedUnits, rtSlot.RateIncrements, - rtSlot.RoundingMethod, rtSlot.RoundingDecimals, rtSlot.Weight)); err != nil { + utils.TBL_TP_RATES, rt.TPid, rt.RateId, rtSlot.ConnectFee, rtSlot.Rate, rtSlot.RatedUnits, rtSlot.RateIncrements, + rtSlot.RoundingMethod, rtSlot.RoundingDecimals, rtSlot.Weight)); err != nil { return err } } @@ -426,13 +426,13 @@ func (self *SQLStorage) ExistsTPRateProfile(tpid, rpId string) (bool, error) { func (self *SQLStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { var qry string if len(rp.RatingActivations) == 0 { // Possibility to only set fallback rate subject - qry = fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', 0,'','%s')", + qry = fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', 0,'','%s')", utils.TBL_TP_RATE_PROFILES, rp.TPid, rp.RateProfileId, rp.Tenant, rp.TOR, rp.Direction, rp.Subject, rp.RatesFallbackSubject) } else { qry = fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject) VALUES ", utils.TBL_TP_RATE_PROFILES) // Using multiple values in query to spare some network processing time for idx, rpa := range rp.RatingActivations { - if idx!=0 { //Consecutive values after the first will be prefixed with "," as separator + if idx != 0 { //Consecutive values after the first will be prefixed with "," as separator qry += "," } qry += fmt.Sprintf("('%s', '%s', '%s', '%s', '%s', '%s', %d,'%s','%s')", rp.TPid, rp.RateProfileId, rp.Tenant, rp.TOR, rp.Direction, rp.Subject, rpa.ActivationTime, rpa.DestRateTimingId, rp.RatesFallbackSubject) @@ -441,7 +441,7 @@ func (self *SQLStorage) SetTPRateProfile(rp *utils.TPRateProfile) error { if _, err := self.Db.Exec(qry); err != nil { return err } - return nil + return nil } func (self *SQLStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfile, error) { @@ -472,7 +472,7 @@ func (self *SQLStorage) GetTPRateProfile(tpid, rpId string) (*utils.TPRateProfil if i == 0 { return nil, nil } - return rp, nil + return rp, nil } func (self *SQLStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) ([]string, error) { @@ -511,6 +511,43 @@ func (self *SQLStorage) GetTPRateProfileIds(filters *utils.AttrTPRateProfileIds) return ids, nil } +func (self *SQLStorage) ExistsTPActions(tpid, actsId string) (bool, error) { + var exists bool + err := self.Db.QueryRow(fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM %s WHERE tpid='%s' AND tag='%s')", utils.TBL_TP_ACTIONS, tpid, actsId)).Scan(&exists) + if err != nil { + return false, err + } + return exists, nil +} + +func (self *SQLStorage) SetTPActions(acts *utils.TPActions) error { + if len(acts.Actions) == 0 { + return nil //Nothing to set + } + // Using multiple values in query to spare some network processing time + qry := fmt.Sprintf("INSERT INTO %s (tpid,tag,action,balance_tag,direction,units,expiration_time,destination_tag,rate_type,rate, minutes_weight,weight) VALUES ", utils.TBL_TP_ACTIONS) + for idx, act := range acts.Actions { + if idx != 0 { //Consecutive values after the first will be prefixed with "," as separator + qry += "," + } + qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,%d,'%s','%s',%f,%f,%f)", + acts.TPid, acts.ActionsId, act.Identifier, act.BalanceId, act.Direction, act.Units, act.ExpirationTime, + act.DestinationId, act.RateType, act.Rate, act.MinutesWeight, act.Weight) + } + if _, err := self.Db.Exec(qry); err != nil { + return err + } + return nil +} + +func (self *SQLStorage) GetTPActions(tpid, aId string) (*utils.TPActions, error) { + return nil, nil +} + +func (self *SQLStorage) GetTPActionIds(tpid string) ([]string, error) { + return nil, nil +} + func (self *SQLStorage) GetActions(string) (as Actions, err error) { return } @@ -680,14 +717,14 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { return nil, err } r := &Rate{ - Tag: tag, - ConnectFee: connect_fee, - Price: rate, - PricedUnits: priced_units, - RateIncrements: rate_increments, - RoundingMethod: roundingMethod, + Tag: tag, + ConnectFee: connect_fee, + Price: rate, + PricedUnits: priced_units, + RateIncrements: rate_increments, + RoundingMethod: roundingMethod, RoundingDecimals: roundingDecimals, - Weight: weight, + Weight: weight, } rts[tag] = r } @@ -774,7 +811,7 @@ func (self *SQLStorage) GetTpDestinationRateTimings(tpid, tag string) ([]*Destin func (self *SQLStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*RatingProfile, error) { rpfs := make(map[string]*RatingProfile) - q := fmt.Sprintf("SELECT tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject FROM %s WHERE tpid='%s'", + q := fmt.Sprintf("SELECT tag,tenant,tor,direction,subject,activation_time,destrates_timing_tag,rates_fallback_subject FROM %s WHERE tpid='%s'", utils.TBL_TP_RATE_PROFILES, tpid) if tag != "" { q += fmt.Sprintf(" AND tag='%s'", tag) diff --git a/utils/tpdata.go b/utils/tpdata.go index cdcf187ab..d7cdd91c2 100644 --- a/utils/tpdata.go +++ b/utils/tpdata.go @@ -27,13 +27,13 @@ type TPRate struct { } type RateSlot struct { - ConnectFee float64 // ConnectFee applied once the call is answered - Rate float64 // Rate applied - RatedUnits int // Number of billing units this rate applies to - RateIncrements int // This rate will apply in increments of duration - RoundingMethod string // Use this method to round the cost - RoundingDecimals int // Round the cost number of decimals - Weight float64 // Rate's priority when dealing with grouped rates + ConnectFee float64 // ConnectFee applied once the call is answered + Rate float64 // Rate applied + RatedUnits int // Number of billing units this rate applies to + RateIncrements int // This rate will apply in increments of duration + RoundingMethod string // Use this method to round the cost + RoundingDecimals int // Round the cost number of decimals + Weight float64 // Rate's priority when dealing with grouped rates } type TPDestinationRate struct { @@ -82,3 +82,22 @@ type AttrTPRateProfileIds struct { Direction string // Traffic direction Subject string // Rating subject, usually the same as account } + +type TPActions struct { + TPid string // Tariff plan id + ActionsId string // Actions id + Actions []Action // Set of actions this Actions profile will perform +} + +type Action struct { + Identifier string // Identifier mapped in the code + BalanceId string // Type of balance the action will operate on + Direction string // Balance direction + Units float64 // Number of units to add/deduct + ExpirationTime int64 // Time when the units will expire + DestinationId string // Destination profile id + RateType string // Type of price + Rate float64 // Price value + MinutesWeight float64 // Minutes weight + Weight float64 // Action's weight +}