TPRatingProfile data remodeling, apis with remove and set without duplication checking: TPDestinationRates, TPRatingPlans, TPRatingProfiles

This commit is contained in:
DanB
2013-11-15 22:42:45 +01:00
parent 9cf1aed058
commit 3a26ba86ea
9 changed files with 125 additions and 110 deletions

View File

@@ -31,11 +31,6 @@ func (self *ApierV1) SetTPDestinationRate(attrs utils.TPDestinationRate, reply *
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationRateId", "DestinationRates"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if exists, err := self.StorDb.ExistsTPDestinationRate(attrs.TPid, attrs.DestinationRateId); 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.SetTPDestinationRates(attrs.TPid, map[string][]*utils.DestinationRate{attrs.DestinationRateId: attrs.DestinationRates}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
@@ -81,3 +76,16 @@ func (self *ApierV1) GetTPDestinationRateIds(attrs AttrGetTPRateIds, reply *[]st
}
return nil
}
// Removes specific DestinationRate on Tariff plan
func (self *ApierV1) RemTPDestinationRate(attrs AttrGetTPDestinationRate, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationRateId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_DESTINATION_RATES, attrs.TPid, attrs.DestinationRateId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

View File

@@ -28,14 +28,9 @@ import (
// Creates a new DestinationRateTiming profile within a tariff plan
func (self *ApierV1) SetTPRatingPlan(attrs utils.TPRatingPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestRateTimingId", "DestRateTimings"}); len(missing) != 0 {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId", "RatingPlans"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if exists, err := self.StorDb.ExistsTPRatingPlan(attrs.TPid, attrs.RatingPlanId); 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.SetTPRatingPlans(attrs.TPid, map[string][]*utils.RatingPlan{attrs.RatingPlanId: attrs.RatingPlans}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
@@ -81,3 +76,16 @@ func (self *ApierV1) GetTPRatingPlanIds(attrs AttrGetTPRatingPlanIds, reply *[]s
}
return nil
}
// Removes specific RatingPlan on Tariff plan
func (self *ApierV1) RemTPRatingPlan(attrs AttrGetTPRatingPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_RATING_PLANS, attrs.TPid, attrs.RatingPlanId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

View File

@@ -28,15 +28,10 @@ import (
// Creates a new RatingProfile within a tariff plan
func (self *ApierV1) SetTPRatingProfile(attrs utils.TPRatingProfile, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingProfileId", "Tenant", "TOR", "Direction", "Subject", "RatingActivations"}); len(missing) != 0 {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingProfileId", "Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if exists, err := self.StorDb.ExistsTPRatingProfile(attrs.TPid, attrs.RatingProfileId); 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.SetTPRatingProfiles(attrs.TPid, map[string][]*utils.TPRatingProfile{attrs.RatingProfileId: []*utils.TPRatingProfile{&attrs}}); err != nil {
if err := self.StorDb.SetTPRatingProfiles(attrs.TPid, map[string]*utils.TPRatingProfile{attrs.RatingProfileId: &attrs}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
@@ -77,3 +72,17 @@ func (self *ApierV1) GetTPRatingProfileIds(attrs utils.AttrTPRatingProfileIds, r
}
return nil
}
// Removes specific RatingProfile on Tariff plan
func (self *ApierV1) RemTPRatingProfile(attrs AttrGetTPRatingProfile, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingProfileId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_RATE_PROFILES, attrs.TPid, attrs.RatingProfileId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

View File

@@ -82,7 +82,7 @@ CREATE TABLE `tp_rating_plans` (
PRIMARY KEY (`id`),
KEY `tpid` (`tpid`),
KEY `tpid_tag` (`tpid`,`tag`),
UNIQUE KEY `tpid_tag_destrates_timings_weight` (`tpid`,`tag`,`destrates_tag`,`timing_tag`,`weight`)
UNIQUE KEY `tpid_tag_destrates_timings_weight` (`tpid`,`tag`,`destrates_tag`,`timing_tag`)
);
--
@@ -99,7 +99,7 @@ CREATE TABLE `tp_rating_profiles` (
`subject` varchar(64) NOT NULL,
`activation_time` varchar(24) NOT NULL,
`rating_plan_tag` varchar(64) NOT NULL,
`fallback_subject` varchar(64),
`fallback_subjects` varchar(64),
PRIMARY KEY (`id`),
KEY `tpid_tag` (`tpid`, `tag`),
UNIQUE KEY `tpid_tag_tenant_tor_dir_subj_atime` (`tpid`,`tag`, `tenant`,`tor`,`direction`,`subject`,`activation_time`)

View File

@@ -23,6 +23,7 @@ import (
"fmt"
"github.com/cgrates/cgrates/utils"
"log"
"strings"
)
type DbReader struct {
@@ -207,30 +208,32 @@ func (dbr *DbReader) LoadRatingPlans() error {
}
func (dbr *DbReader) LoadRatingProfiles() error {
rpfs, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, "")
mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, "") //map[string]*utils.TPRatingProfile
if err != nil {
return err
}
for _, rp := range rpfs {
rpf := &RatingProfile{Id: rp.RatingProfileId}
at, err := utils.ParseDate(rp.ActivationTime)
if err != nil {
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", rp.ActivationTime))
}
_, exists := dbr.ratingPlans[rp.RatingPlanId]
if !exists {
if dbExists, err := dbr.dataDb.ExistsData(RATING_PLAN_PREFIX, rp.RatingPlanId); err != nil {
return err
} else if !dbExists {
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", rp.RatingPlanId))
for _, tpRpf := range mpTpRpfs {
rpf := &RatingProfile{Id: tpRpf.RatingProfileId}
for _, tpRa := range tpRpf.RatingPlanActivations {
at, err := utils.ParseDate(tpRa.ActivationTime)
if err != nil {
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime))
}
_, exists := dbr.ratingPlans[rpf.Id]
if !exists {
if dbExists, err := dbr.dataDb.ExistsData(RATING_PLAN_PREFIX, rpf.Id); err != nil {
return err
} else if !dbExists {
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", rpf.Id))
}
}
rpf.RatingPlanActivations = append(rpf.RatingPlanActivations,
&RatingPlanActivation{
ActivationTime: at,
RatingPlanId: tpRa.RatingPlanId,
FallbackKeys: strings.Split(tpRa.FallbackSubjects,FALLBACK_SEP),
})
}
rpf.RatingPlanActivations = append(rpf.RatingPlanActivations,
&RatingPlanActivation{
ActivationTime: at,
RatingPlanId: rp.RatingPlanId,
FallbackKeys: rp.FallbackKeys,
})
dbr.ratingProfiles[rpf.Id] = rpf
}
return nil
@@ -287,27 +290,30 @@ func (dbr *DbReader) LoadRatingPlanByTag(tag string) error {
func (dbr *DbReader) LoadRatingProfileByTag(tag string) error {
resultRatingProfile := &RatingProfile{}
rpfs, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag)
if err != nil || len(rpfs) == 0 {
mpTpRpfs, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag) //map[string]*utils.TPRatingProfile
if err != nil || len(mpTpRpfs) == 0 {
return fmt.Errorf("No RateProfile with id %s: %v", tag, err)
}
for _, rp := range rpfs {
Logger.Debug(fmt.Sprintf("Rating profile: %v", rpfs))
resultRatingProfile.Id = rp.RatingProfileId
at, err := utils.ParseDate(rp.ActivationTime)
if err != nil {
return fmt.Errorf("Cannot parse activation time from %v", rp.ActivationTime)
}
// Check if referenced RatingPlan exists
_, exists := dbr.ratingPlans[rp.RatingPlanId]
if !exists {
if dbExists, err := dbr.dataDb.ExistsData(RATING_PLAN_PREFIX, rp.RatingPlanId); err != nil {
return err
} else if !dbExists {
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", rp.RatingPlanId))
for _, tpRpf := range mpTpRpfs {
Logger.Debug(fmt.Sprintf("Rating profile: %v", tpRpf))
resultRatingProfile.Id = tpRpf.RatingProfileId
///
for _, tpRa := range tpRpf.RatingPlanActivations {
at, err := utils.ParseDate(tpRa.ActivationTime)
if err != nil {
return errors.New(fmt.Sprintf("Cannot parse activation time from %v", tpRa.ActivationTime))
}
_, exists := dbr.ratingPlans[resultRatingProfile.Id]
if !exists {
if dbExists, err := dbr.dataDb.ExistsData(RATING_PLAN_PREFIX, resultRatingProfile.Id); err != nil {
return err
} else if !dbExists {
return errors.New(fmt.Sprintf("Could not load rating plans for tag: %v", resultRatingProfile.Id))
}
}
resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations,
&RatingPlanActivation{at, tpRa.RatingPlanId, strings.Split(tpRa.FallbackSubjects,FALLBACK_SEP)})
}
resultRatingProfile.RatingPlanActivations = append(resultRatingProfile.RatingPlanActivations, &RatingPlanActivation{at, rp.RatingPlanId, rp.FallbackKeys})
}
return dbr.dataDb.SetRatingProfile(resultRatingProfile)
}

View File

@@ -123,7 +123,7 @@ type LoadStorage interface {
GetTPRatingPlan(string, string) (*utils.TPRatingPlan, error)
GetTPRatingPlanIds(string) ([]string, error)
ExistsTPRatingProfile(string, string) (bool, error)
SetTPRatingProfiles(string, map[string][]*utils.TPRatingProfile) error
SetTPRatingProfiles(string, map[string]*utils.TPRatingProfile) error
GetTPRatingProfile(string, string) (*utils.TPRatingProfile, error)
GetTPRatingProfileIds(*utils.AttrTPRatingProfileIds) ([]string, error)
ExistsTPActions(string, string) (bool, error)

View File

@@ -23,7 +23,6 @@ import (
"encoding/json"
"fmt"
"github.com/cgrates/cgrates/utils"
"strings"
"time"
)
@@ -302,18 +301,19 @@ func (self *SQLStorage) SetTPDestinationRates(tpid string, drs map[string][]*uti
if len(drs) == 0 {
return nil //Nothing to set
}
qry := fmt.Sprintf("INSERT INTO %s (tpid, tag, destinations_tag, rates_tag) VALUES ", utils.TBL_TP_DESTINATION_RATES)
vals := ""
i := 0
for drId, drRows := range drs {
for _, dr := range drRows {
if i != 0 { //Consecutive values after the first will be prefixed with "," as separator
qry += ","
vals += ","
}
qry += fmt.Sprintf("('%s','%s','%s','%s')",
vals += fmt.Sprintf("('%s','%s','%s','%s')",
tpid, drId, dr.DestinationId, dr.RateId)
i++
}
}
qry := fmt.Sprintf("INSERT INTO %s (tpid,tag,destinations_tag,rates_tag) VALUES %s ON DUPLICATE KEY UPDATE destinations_tag=values(destinations_tag),rates_tag=values(rates_tag)", utils.TBL_TP_DESTINATION_RATES, vals)
if _, err := self.Db.Exec(qry); err != nil {
return err
}
@@ -379,18 +379,19 @@ func (self *SQLStorage) SetTPRatingPlans(tpid string, drts map[string][]*utils.R
if len(drts) == 0 {
return nil //Nothing to set
}
qry := fmt.Sprintf("INSERT INTO %s (tpid, tag, destrates_tag, timing_tag, weight) VALUES ", utils.TBL_TP_RATING_PLANS)
vals := ""
i := 0
for drtId, drtRows := range drts {
for _, drt := range drtRows {
if i != 0 { //Consecutive values after the first will be prefixed with "," as separator
qry += ","
vals += ","
}
qry += fmt.Sprintf("('%s','%s','%s','%s',%f)",
vals += fmt.Sprintf("('%s','%s','%s','%s',%f)",
tpid, drtId, drt.DestinationRatesId, drt.TimingId, drt.Weight)
i++
}
}
qry := fmt.Sprintf("INSERT INTO %s (tpid, tag, destrates_tag, timing_tag, weight) VALUES %s ON DUPLICATE KEY UPDATE weight=values(weight)", utils.TBL_TP_RATING_PLANS, vals)
if _, err := self.Db.Exec(qry); err != nil {
return err
}
@@ -453,24 +454,23 @@ func (self *SQLStorage) ExistsTPRatingProfile(tpid, rpId string) (bool, error) {
return exists, nil
}
func (self *SQLStorage) SetTPRatingProfiles(tpid string, rps map[string][]*utils.TPRatingProfile) error {
func (self *SQLStorage) SetTPRatingProfiles(tpid string, rps map[string]*utils.TPRatingProfile) error {
if len(rps) == 0 {
return nil //Nothing to set
}
qry := fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subject) VALUES ",
utils.TBL_TP_RATE_PROFILES)
vals := ""
i := 0
for rpId, rp := range rps {
for _, rpa := range rp {
for _, rp := range rps {
for _, rpa := range rp.RatingPlanActivations {
if i != 0 { //Consecutive values after the first will be prefixed with "," as separator
qry += ","
vals += ","
}
qry += fmt.Sprintf("('%s', '%s', '%s', '%s', '%s', '%s', '%s','%s','%s')", tpid, rpId, rpa.Tenant, rpa.TOR, rpa.Direction,
rpa.Subject, rpa.ActivationTime, rpa.RatingPlanId, rpa.FallbackKeys)
vals += fmt.Sprintf("('%s', '%s', '%s', '%s', '%s', '%s', '%s','%s','%s')", tpid, rp.RatingProfileId, rp.Tenant, rp.TOR, rp.Direction,
rp.Subject, rpa.ActivationTime, rpa.RatingPlanId, rpa.FallbackSubjects)
i++
}
}
qry := fmt.Sprintf("INSERT INTO %s (tpid,tag,tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subjects) VALUES %s ON DUPLICATE KEY UPDATE fallback_subjects=values(fallback_subjects)", utils.TBL_TP_RATE_PROFILES, vals)
if _, err := self.Db.Exec(qry); err != nil {
return err
}
@@ -478,7 +478,7 @@ func (self *SQLStorage) SetTPRatingProfiles(tpid string, rps map[string][]*utils
}
func (self *SQLStorage) GetTPRatingProfile(tpid, rpId string) (*utils.TPRatingProfile, error) {
rows, err := self.Db.Query(fmt.Sprintf("SELECT tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subject FROM %s WHERE tpid='%s' AND tag='%s'", utils.TBL_TP_RATE_PROFILES, tpid, rpId))
rows, err := self.Db.Query(fmt.Sprintf("SELECT tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subjects FROM %s WHERE tpid='%s' AND tag='%s'", utils.TBL_TP_RATE_PROFILES, tpid, rpId))
if err != nil {
return nil, err
}
@@ -497,9 +497,8 @@ func (self *SQLStorage) GetTPRatingProfile(tpid, rpId string) (*utils.TPRatingPr
rp.TOR = tor
rp.Direction = direction
rp.Subject = subject
rp.FallbackKeys = strings.Split(fallbackSubj, FALLBACK_SEP)
}
rp.RatingPlanActivations = append(rp.RatingPlanActivations, &utils.RatingActivation{aTime, drtId})
rp.RatingPlanActivations = append(rp.RatingPlanActivations, &utils.TPRatingActivation{aTime, drtId, fallbackSubj})
}
if i == 0 {
return nil, nil
@@ -1113,7 +1112,7 @@ func (self *SQLStorage) GetTpRatingPlans(tpid, tag string) (*utils.TPRatingPlan,
func (self *SQLStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*utils.TPRatingProfile, error) {
rpfs := make(map[string]*utils.TPRatingProfile)
q := fmt.Sprintf("SELECT tag,tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subject FROM %s WHERE tpid='%s'",
q := fmt.Sprintf("SELECT tag,tenant,tor,direction,subject,activation_time,rating_plan_tag,fallback_subjects FROM %s WHERE tpid='%s'",
utils.TBL_TP_RATE_PROFILES, tpid)
if tag != "" {
q += fmt.Sprintf(" AND tag='%s'", tag)
@@ -1124,26 +1123,17 @@ func (self *SQLStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*utils
}
defer rows.Close()
for rows.Next() {
var tag, tenant, tor, direction, subject, fallback_subject, rating_plan_tag, activation_time string
if err := rows.Scan(&tag, &tenant, &tor, &direction, &subject, &activation_time, &rating_plan_tag, &fallback_subject); err != nil {
var rcvTag, tenant, tor, direction, subject, fallback_subjects, rating_plan_tag, activation_time string
if err := rows.Scan(&rcvTag, &tenant, &tor, &direction, &subject, &activation_time, &rating_plan_tag, &fallback_subjects); err != nil {
return nil, err
}
key := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, subject)
rp, ok := rpfs[key]
if !ok || rp.RatingProfileId != tag {
rp = &utils.TPRatingProfile{RatingProfileId: key, Tag: tag}
rpfs[key] = rp
}
rp.RatingPlanId = rating_plan_tag
rp.ActivationTime = activation_time
if fallback_subject != "" {
for _, fbs := range strings.Split(fallback_subject, ";") {
newKey := fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fbs)
var sslice utils.StringSlice = rp.FallbackKeys
if !sslice.Contains(newKey) {
rp.FallbackKeys = append(rp.FallbackKeys, newKey)
}
}
if _,ok := rpfs[rcvTag]; !ok {
rpfs[rcvTag] = &utils.TPRatingProfile{TPid: tpid, RatingProfileId: rcvTag, Tenant: tenant, TOR: tor, Direction:direction, Subject:subject,
RatingPlanActivations: []*utils.TPRatingActivation{
&utils.TPRatingActivation{ActivationTime:activation_time, RatingPlanId:rating_plan_tag, FallbackSubjects:fallback_subjects}}}
} else {
rpfs[rcvTag].RatingPlanActivations = append( rpfs[rcvTag].RatingPlanActivations,
&utils.TPRatingActivation{ActivationTime:activation_time, RatingPlanId:rating_plan_tag, FallbackSubjects:fallback_subjects} )
}
}
return rpfs, nil

View File

@@ -24,7 +24,6 @@ import (
"io/ioutil"
"log"
"strconv"
"strings"
)
// Import tariff plan from csv into storDb
@@ -245,7 +244,7 @@ func (self *TPCSVImporter) importRatingProfiles(fn string) error {
}
continue
}
tenant, tor, direction, subject, destRatesTimingTag, fallbacksubject := record[0], record[1], record[2], record[3], record[5], record[6]
tenant, tor, direction, subject, ratingPlanTag, fallbacksubject := record[0], record[1], record[2], record[3], record[5], record[6]
_, err = utils.ParseDate(record[4])
if err != nil {
if self.Verbose {
@@ -258,16 +257,15 @@ func (self *TPCSVImporter) importRatingProfiles(fn string) error {
rpTag += "_" + self.ImportId
}
rp := &utils.TPRatingProfile{
Tag: rpTag,
RatingProfileId:rpTag,
Tenant: tenant,
TOR: tor,
Direction: direction,
Subject: subject,
ActivationTime: record[4],
RatingPlanId: destRatesTimingTag,
FallbackKeys: strings.Split(fallbacksubject, FALLBACK_SEP),
RatingPlanActivations: []*utils.TPRatingActivation{
&utils.TPRatingActivation{ ActivationTime: record[4], RatingPlanId: ratingPlanTag, FallbackSubjects: fallbacksubject}},
}
if err := self.StorDb.SetTPRatingProfiles(self.TPid, map[string][]*utils.TPRatingProfile{rpTag: []*utils.TPRatingProfile{rp}}); err != nil {
if err := self.StorDb.SetTPRatingProfiles(self.TPid, map[string]*utils.TPRatingProfile{rpTag: rp}); err != nil {
if self.Verbose {
log.Printf("Ignoring line %d, storDb operational error: <%s> ", lineNr, err.Error())
}

View File

@@ -123,22 +123,18 @@ func(self *RatingPlan) Timing() *TPTiming {
type TPRatingProfile struct {
TPid string // Tariff plan id
Tag string
RatingProfileId string // RatingProfile id
RatingPlanId string
Tenant string // Tenant's Id
TOR string // TypeOfRecord
Direction string // Traffic direction, OUT is the only one supported for now
Subject string // Rating subject, usually the same as account
FallbackKeys []string // Fallback on this subject if rates not found for destination
ActivationTime string
RatingPlanActivations []*RatingActivation // Activate rate profiles at specific time
RatingPlanActivations []*TPRatingActivation // Activate rate profiles at specific time
}
type RatingActivation struct {
type TPRatingActivation struct {
ActivationTime string // Time when this profile will become active, defined as unix epoch time
DestRateTimingId string // Id of DestRateTiming profile
// FallbackKeys []string
RatingPlanId string // Id of RatingPlan profile
FallbackSubjects string // So we follow the api
}
type AttrTPRatingProfileIds struct {