From 411af6ba74259fed29c4af36526e7d81904da1b3 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 27 Aug 2013 14:48:01 +0300 Subject: [PATCH 01/39] splited storage interface in multiple interfaces --- apier/v1/apier.go | 2 +- cdrs/cdrs.go | 10 ++-- cmd/cgr-engine/cgr-engine.go | 47 +++++++++-------- cmd/cgr-loader/cgr-loader.go | 13 ++--- engine/actions_test.go | 4 +- engine/calldesc.go | 20 +++++--- engine/calldesc_test.go | 2 +- engine/loader_csv.go | 2 +- engine/loader_db.go | 6 +-- engine/responder.go | 2 +- engine/storage_interface.go | 54 +++++++++++++------- engine/storage_map.go | 2 +- engine/storage_mongo.go | 2 +- engine/storage_mysql.go | 2 +- engine/storage_postgres.go | 2 +- engine/storage_redis.go | 2 +- engine/storage_sql.go | 4 +- engine/storage_utils.go | 82 +++++++++++++++++++++++++++--- engine/tpimporter_csv.go | 2 +- mediator/mediator.go | 21 ++++---- sessionmanager/fssessionmanager.go | 6 +-- sessionmanager/sessionmanager.go | 2 +- 22 files changed, 192 insertions(+), 97 deletions(-) diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 521e37eac..455fef218 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -31,7 +31,7 @@ const ( ) type ApierV1 struct { - StorDb engine.DataStorage + StorDb engine.LoadStorage DataDb engine.DataStorage Sched *scheduler.Scheduler } diff --git a/cdrs/cdrs.go b/cdrs/cdrs.go index 6da450eb4..7cb0688a1 100644 --- a/cdrs/cdrs.go +++ b/cdrs/cdrs.go @@ -29,7 +29,7 @@ import ( var ( cfg *config.CGRConfig // Share the configuration with the rest of the package - storage engine.DataStorage + storage engine.CdrStorage medi *mediator.Mediator ) @@ -43,7 +43,7 @@ func fsCdrHandler(w http.ResponseWriter, r *http.Request) { } else { //TODO: use the connection to mediator } - } () + }() } else { engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %v", err)) } @@ -68,7 +68,7 @@ func cgrCdrHandler(w http.ResponseWriter, r *http.Request) { type CDRS struct{} -func New(s engine.DataStorage, m *mediator.Mediator, c *config.CGRConfig) *CDRS { +func New(s engine.CdrStorage, m *mediator.Mediator, c *config.CGRConfig) *CDRS { storage = s medi = m cfg = c @@ -76,7 +76,7 @@ func New(s engine.DataStorage, m *mediator.Mediator, c *config.CGRConfig) *CDRS } func (cdrs *CDRS) StartCapturingCDRs() { - http.HandleFunc("/cgr_json", cgrCdrHandler) // Attach CGR CDR Handler - http.HandleFunc("/freeswitch_json", fsCdrHandler) // Attach FreeSWITCH JSON CDR Handler + http.HandleFunc("/cgr_json", cgrCdrHandler) // Attach CGR CDR Handler + http.HandleFunc("/freeswitch_json", fsCdrHandler) // Attach FreeSWITCH JSON CDR Handler http.ListenAndServe(cfg.CDRSListen, nil) } diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 1b7e3a8cc..cf7c96d53 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -64,7 +64,7 @@ var ( err error ) -func listenToRPCRequests(rpcResponder interface{}, apier *apier.ApierV1, rpcAddress string, rpc_encoding string, getter engine.DataStorage, loggerDb engine.DataStorage) { +func listenToRPCRequests(rpcResponder interface{}, apier *apier.ApierV1, rpcAddress string, rpc_encoding string, getter engine.DataStorage, loggerDb engine.LogStorage) { l, err := net.Listen("tcp", rpcAddress) if err != nil { engine.Logger.Crit(fmt.Sprintf(" Could not listen to %v: %v", rpcAddress, err)) @@ -94,7 +94,7 @@ func listenToRPCRequests(rpcResponder interface{}, apier *apier.ApierV1, rpcAddr } } -func startMediator(responder *engine.Responder, loggerDb engine.DataStorage) { +func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrDb engine.CdrStorage) { var connector engine.Connector if cfg.MediatorRater == INTERNAL { connector = responder @@ -125,7 +125,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.DataStorage) { connector = &engine.RPCClientConnector{Client: client} } var err error - medi, err = mediator.NewMediator(connector, loggerDb, cfg) + medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, cfg) if err != nil { engine.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err)) exitChan <- true @@ -136,7 +136,7 @@ func startMediator(responder *engine.Responder, loggerDb engine.DataStorage) { } } -func startSessionManager(responder *engine.Responder, loggerDb engine.DataStorage) { +func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage) { var connector engine.Connector if cfg.SMRater == INTERNAL { connector = responder @@ -183,7 +183,7 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.DataStorag exitChan <- true } -func startCDRS(responder *engine.Responder, loggerDb engine.DataStorage) { +func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage) { if cfg.CDRSMediator == INTERNAL { for i := 0; i < 3; i++ { // ToDo: If the right approach, make the reconnects configurable time.Sleep(time.Duration(i/2) * time.Second) @@ -196,7 +196,7 @@ func startCDRS(responder *engine.Responder, loggerDb engine.DataStorage) { exitChan <- true } } - cs := cdrs.New(loggerDb, medi, cfg) + cs := cdrs.New(cdrDb, medi, cfg) cs.StartCapturingCDRs() exitChan <- true } @@ -308,25 +308,28 @@ func main() { return } - var getter, loggerDb engine.DataStorage - getter, err = engine.ConfigureDatabase(cfg.DataDBType, cfg.DataDBHost, cfg.DataDBPort, cfg.DataDBName, cfg.DataDBUser, cfg.DataDBPass) + var dataDb engine.DataStorage + var logDb engine.LogStorage + var loadDb engine.LoadStorage + var cdrDb engine.CdrStorage + dataDb, err = engine.ConfigureDataStorage(cfg.DataDBType, cfg.DataDBHost, cfg.DataDBPort, cfg.DataDBName, cfg.DataDBUser, cfg.DataDBPass) if err != nil { // Cannot configure getter database, show stopper engine.Logger.Crit(fmt.Sprintf("Could not configure dataDb: %s exiting!", err)) return } - defer getter.Close() - engine.SetDataStorage(getter) + defer dataDb.Close() + engine.SetDataStorage(dataDb) if cfg.StorDBType == SAME { - loggerDb = getter + logDb = dataDb.(engine.LogStorage) } else { - loggerDb, err = engine.ConfigureDatabase(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass) + logDb, err = engine.ConfigureLogStorage(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass) if err != nil { // Cannot configure logger database, show stopper engine.Logger.Crit(fmt.Sprintf("Could not configure logger database: %s exiting!", err)) return } } - defer loggerDb.Close() - engine.SetStorageLogger(loggerDb) + defer logDb.Close() + engine.SetStorageLogger(logDb) engine.SetRoundingMethodAndDecimals(cfg.RoundingMethod, cfg.RoundingDecimals) if cfg.SMDebitInterval > 0 { @@ -341,16 +344,16 @@ func main() { go stopRaterSingnalHandler() } responder := &engine.Responder{ExitChan: exitChan} - apier := &apier.ApierV1{StorDb: loggerDb, DataDb: getter} + apier := &apier.ApierV1{StorDb: loadDb, DataDb: dataDb} if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterListen != INTERNAL { engine.Logger.Info(fmt.Sprintf("Starting CGRateS Rater on %s.", cfg.RaterListen)) - go listenToRPCRequests(responder, apier, cfg.RaterListen, cfg.RPCEncoding, getter, loggerDb) + go listenToRPCRequests(responder, apier, cfg.RaterListen, cfg.RPCEncoding, dataDb, logDb) } if cfg.BalancerEnabled { engine.Logger.Info(fmt.Sprintf("Starting CGRateS Balancer on %s.", cfg.BalancerListen)) go stopBalancerSingnalHandler() responder.Bal = bal - go listenToRPCRequests(responder, apier, cfg.BalancerListen, cfg.RPCEncoding, getter, loggerDb) + go listenToRPCRequests(responder, apier, cfg.BalancerListen, cfg.RPCEncoding, dataDb, logDb) if cfg.RaterEnabled { engine.Logger.Info("Starting internal engine.") bal.AddClient("local", new(engine.ResponderWorker)) @@ -361,28 +364,28 @@ func main() { engine.Logger.Info("Starting CGRateS Scheduler.") go func() { sched := scheduler.NewScheduler() - go reloadSchedulerSingnalHandler(sched, getter) + go reloadSchedulerSingnalHandler(sched, dataDb) apier.Sched = sched - sched.LoadActionTimings(getter) + sched.LoadActionTimings(dataDb) sched.Loop() }() } if cfg.SMEnabled { engine.Logger.Info("Starting CGRateS SessionManager.") - go startSessionManager(responder, loggerDb) + go startSessionManager(responder, logDb) // close all sessions on shutdown go shutdownSessionmanagerSingnalHandler() } if cfg.MediatorEnabled { engine.Logger.Info("Starting CGRateS Mediator.") - go startMediator(responder, loggerDb) + go startMediator(responder, logDb, cdrDb) } if cfg.CDRSEnabled { engine.Logger.Info("Starting CGRateS CDR Server.") - go startCDRS(responder, loggerDb) + go startCDRS(responder, cdrDb) } if cfg.HistoryServerEnabled || cfg.HistoryAgentEnabled { diff --git a/cmd/cgr-loader/cgr-loader.go b/cmd/cgr-loader/cgr-loader.go index 984122ae3..aff61c0f1 100644 --- a/cmd/cgr-loader/cgr-loader.go +++ b/cmd/cgr-loader/cgr-loader.go @@ -66,18 +66,19 @@ func main() { return } var errDataDb, errStorDb, err error - var dataDb, storDb engine.DataStorage + var dataDb engine.DataStorage + var storDb engine.LoadStorage // Init necessary db connections if *fromStorDb { - dataDb, errDataDb = engine.ConfigureDatabase(*data_db_type, *data_db_host, *data_db_port, *data_db_name, *data_db_user, *data_db_pass) - storDb, errStorDb = engine.ConfigureDatabase(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass) + dataDb, errDataDb = engine.ConfigureDataStorage(*data_db_type, *data_db_host, *data_db_port, *data_db_name, *data_db_user, *data_db_pass) + storDb, errStorDb = engine.ConfigureLoadStorage(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass) } else if *toStorDb { // Import from csv files to storDb - storDb, errStorDb = engine.ConfigureDatabase(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass) + storDb, errStorDb = engine.ConfigureLoadStorage(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass) } else { // Default load from csv files to dataDb - dataDb, errDataDb = engine.ConfigureDatabase(*data_db_type, *data_db_host, *data_db_port, *data_db_name, *data_db_user, *data_db_pass) + dataDb, errDataDb = engine.ConfigureDataStorage(*data_db_type, *data_db_host, *data_db_port, *data_db_name, *data_db_user, *data_db_pass) } // Defer databases opened to be closed when we are done - for _, db := range []engine.DataStorage{dataDb, storDb} { + for _, db := range []engine.Storage{dataDb, storDb} { if db != nil { defer db.Close() } diff --git a/engine/actions_test.go b/engine/actions_test.go index 964e49dec..87fea53ee 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -744,7 +744,7 @@ func TestActionTriggerLogging(t *testing.T) { if err != nil { t.Error("Error getting actions for the action timing: ", err) } - storageGetter.LogActionTrigger("rif", RATER_SOURCE, at, as) + storageLogger.LogActionTrigger("rif", RATER_SOURCE, at, as) //expected := "rif*some_uuid;MONETARY;OUT;NAT;TEST_ACTIONS;100;10;false*|TOPUP|MONETARY|OUT|10|0" var key string atMap, _ := storageGetter.GetAllActionTimings() @@ -784,7 +784,7 @@ func TestActionTimingLogging(t *testing.T) { if err != nil { t.Error("Error getting actions for the action trigger: ", err) } - storageGetter.LogActionTiming(SCHED_SOURCE, at, as) + storageLogger.LogActionTiming(SCHED_SOURCE, at, as) //expected := "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1|10|TEST_ACTIONS*|TOPUP|MONETARY|OUT|10|0" var key string atMap, _ := storageGetter.GetAllActionTimings() diff --git a/engine/calldesc.go b/engine/calldesc.go index 60ee36878..78da15fa5 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -36,6 +36,14 @@ func init() { Logger = new(utils.StdLogger) Logger.Err(fmt.Sprintf("Could not connect to syslog: %v", err)) } + //db_server := "127.0.0.1" + //db_server := "192.168.0.17" + m, _ := NewMapStorage() + storageGetter, _ = m.(DataStorage) + //storageGetter, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "") + //storageGetter, _ = NewRedisStorage(db_server+":6379", 11, "") + + storageLogger = storageGetter.(LogStorage) } const ( @@ -45,13 +53,9 @@ const ( ) var ( - Logger utils.LoggerInterface - db_server = "127.0.0.1" - //db_server = "192.168.0.17" - storageGetter, _ = NewMapStorage() - //storageGetter, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "") - //storageGetter, _ = NewRedisStorage(db_server+":6379", 11, "") - storageLogger = storageGetter + Logger utils.LoggerInterface + storageGetter DataStorage + storageLogger LogStorage debitPeriod = 10 * time.Second roundingMethod = "*middle" roundingDecimals = 4 @@ -73,7 +77,7 @@ func SetRoundingMethodAndDecimals(rm string, rd int) { /* Sets the database for logging (can be de same as storage getter or different db) */ -func SetStorageLogger(sg DataStorage) { +func SetStorageLogger(sg LogStorage) { storageLogger = sg } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index cffa333d5..f5337cc6d 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -53,7 +53,7 @@ func populateDB() { }, } if storageGetter != nil { - storageGetter.Flush() + storageGetter.(Storage).Flush() storageGetter.SetUserBalance(broker) storageGetter.SetUserBalance(minu) } else { diff --git a/engine/loader_csv.go b/engine/loader_csv.go index b0ae606df..657c96c47 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -101,7 +101,7 @@ func (csvr *CSVReader) WriteToDatabase(flush, verbose bool) (err error) { return errors.New("No database connection!") } if flush { - storage.Flush() + storage.(Storage).Flush() } if verbose { log.Print("Destinations") diff --git a/engine/loader_db.go b/engine/loader_db.go index e585d7a97..4c55a7030 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -27,7 +27,7 @@ import ( type DbReader struct { tpid string - storDb DataStorage + storDb LoadStorage dataDb DataStorage actions map[string][]*Action actionsTimings map[string][]*ActionTiming @@ -41,7 +41,7 @@ type DbReader struct { ratingProfiles map[string]*RatingProfile } -func NewDbReader(storDB DataStorage, storage DataStorage, tpid string) *DbReader { +func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader { c := new(DbReader) c.storDb = storDB c.dataDb = storage @@ -54,7 +54,7 @@ func NewDbReader(storDB DataStorage, storage DataStorage, tpid string) *DbReader func (dbr *DbReader) WriteToDatabase(flush, verbose bool) (err error) { storage := dbr.dataDb if flush { - storage.Flush() + storage.(Storage).Flush() } if verbose { log.Print("Destinations") diff --git a/engine/responder.go b/engine/responder.go index 413ccfad8..631194729 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -169,7 +169,7 @@ func (rs *Responder) Shutdown(arg string, reply *string) (err error) { if rs.Bal != nil { rs.Bal.Shutdown("Responder.Shutdown") } - storageGetter.Close() + storageGetter.(Storage).Close() defer func() { rs.ExitChan <- true }() *reply = "Done!" return diff --git a/engine/storage_interface.go b/engine/storage_interface.go index cdcdd24da..c6187e848 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -47,16 +47,49 @@ const ( RATER_SOURCE = "RAT" ) +type Storage interface { + Close() + Flush() error +} + /* Interface for storage providers. */ type DataStorage interface { - Close() - Flush() error + Storage GetRatingProfile(string) (*RatingProfile, error) SetRatingProfile(*RatingProfile) error GetDestination(string) (*Destination, error) SetDestination(*Destination) error + // End Apier functions + GetActions(string) (Actions, error) + SetActions(string, Actions) error + GetUserBalance(string) (*UserBalance, error) + SetUserBalance(*UserBalance) error + GetActionTimings(string) (ActionTimings, error) + SetActionTimings(string, ActionTimings) error + GetAllActionTimings() (map[string]ActionTimings, error) +} + +type CdrStorage interface { + Storage + SetCdr(utils.CDR) error + SetRatedCdr(utils.CDR, *CallCost, string) error + GetAllRatedCdr() ([]utils.CDR, error) +} + +type LogStorage interface { + Storage + //GetAllActionTimingsLogs() (map[string]ActionsTimings, error) + LogCallCost(uuid, source string, cc *CallCost) error + LogError(uuid, source, errstr string) error + LogActionTrigger(ubId, source string, at *ActionTrigger, as Actions) error + LogActionTiming(source string, at *ActionTiming, as Actions) error + GetCallCostLog(uuid, source string) (*CallCost, error) +} + +type LoadStorage interface { + Storage // Apier functions GetTPIds() ([]string, error) SetTPTiming(string, *Timing) error @@ -97,23 +130,6 @@ type DataStorage interface { ExistsTPAccountActions(string, string) (bool, error) SetTPAccountActions(string, map[string]*AccountAction) error GetTPAccountActionIds(string) ([]string, error) - // End Apier functions - GetActions(string) (Actions, error) - SetActions(string, Actions) error - GetUserBalance(string) (*UserBalance, error) - SetUserBalance(*UserBalance) error - GetActionTimings(string) (ActionTimings, error) - SetActionTimings(string, ActionTimings) error - GetAllActionTimings() (map[string]ActionTimings, error) - SetCdr(utils.CDR) error - SetRatedCdr(utils.CDR, *CallCost, string) error - GetAllRatedCdr() ([]utils.CDR, error) - //GetAllActionTimingsLogs() (map[string]ActionsTimings, error) - LogCallCost(uuid, source string, cc *CallCost) error - LogError(uuid, source, errstr string) error - LogActionTrigger(ubId, source string, at *ActionTrigger, as Actions) error - LogActionTiming(source string, at *ActionTiming, as Actions) error - GetCallCostLog(uuid, source string) (*CallCost, error) // loader functions GetTpDestinations(string, string) ([]*Destination, error) GetTpTimings(string, string) (map[string]*Timing, error) diff --git a/engine/storage_map.go b/engine/storage_map.go index 40192bbc5..2e108267d 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -32,7 +32,7 @@ type MapStorage struct { ms Marshaler } -func NewMapStorage() (DataStorage, error) { +func NewMapStorage() (Storage, error) { return &MapStorage{dict: make(map[string][]byte), ms: new(MsgpackMarshaler)}, nil } diff --git a/engine/storage_mongo.go b/engine/storage_mongo.go index 313093139..7bf735a60 100644 --- a/engine/storage_mongo.go +++ b/engine/storage_mongo.go @@ -34,7 +34,7 @@ type MongoStorage struct { db *mgo.Database } -func NewMongoStorage(host, port, db, user, pass string) (DataStorage, error) { +func NewMongoStorage(host, port, db, user, pass string) (Storage, error) { dial := fmt.Sprintf(host) if user != "" && pass != "" { dial = fmt.Sprintf("%s:%s@%s", user, pass, dial) diff --git a/engine/storage_mysql.go b/engine/storage_mysql.go index c9792d638..6e3338ed9 100644 --- a/engine/storage_mysql.go +++ b/engine/storage_mysql.go @@ -28,7 +28,7 @@ type MySQLStorage struct { *SQLStorage } -func NewMySQLStorage(host, port, name, user, password string) (DataStorage, error) { +func NewMySQLStorage(host, port, name, user, password string) (Storage, error) { db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", user, password, host, port, name)) if err != nil { return nil, err diff --git a/engine/storage_postgres.go b/engine/storage_postgres.go index 3ad3076b8..420ee39f8 100644 --- a/engine/storage_postgres.go +++ b/engine/storage_postgres.go @@ -28,7 +28,7 @@ type PostgresStorage struct { *SQLStorage } -func NewPostgresStorage(host, port, name, user, password string) (DataStorage, error) { +func NewPostgresStorage(host, port, name, user, password string) (Storage, error) { db, err := sql.Open("postgres", fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable", host, port, name, user, password)) if err != nil { return nil, err diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 9f1d107d7..572ca34ac 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -35,7 +35,7 @@ type RedisStorage struct { ms Marshaler } -func NewRedisStorage(address string, db int, pass string) (DataStorage, error) { +func NewRedisStorage(address string, db int, pass string) (Storage, error) { addrSplit := strings.Split(address, ":") host := addrSplit[0] port := 6379 diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 8c239a02e..00674df31 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -30,7 +30,9 @@ type SQLStorage struct { Db *sql.DB } -func (self *SQLStorage) Close() {} +func (self *SQLStorage) Close() { + self.Close() +} func (self *SQLStorage) Flush() (err error) { return diff --git a/engine/storage_utils.go b/engine/storage_utils.go index b791f056a..7436118f7 100644 --- a/engine/storage_utils.go +++ b/engine/storage_utils.go @@ -26,7 +26,8 @@ import ( // Various helpers to deal with database -func ConfigureDatabase(db_type, host, port, name, user, pass string) (db DataStorage, err error) { +func ConfigureDataStorage(db_type, host, port, name, user, pass string) (db DataStorage, err error) { + var d Storage switch db_type { case utils.REDIS: var db_nb int @@ -38,13 +39,80 @@ func ConfigureDatabase(db_type, host, port, name, user, pass string) (db DataSto if port != "" { host += ":" + port } - db, err = NewRedisStorage(host, db_nb, pass) + d, err = NewRedisStorage(host, db_nb, pass) + db = d.(DataStorage) case utils.MONGO: - db, err = NewMongoStorage(host, port, name, user, pass) - case utils.POSTGRES: - db, err = NewPostgresStorage(host, port, name, user, pass) - case utils.MYSQL: - db, err = NewMySQLStorage(host, port, name, user, pass) + d, err = NewMongoStorage(host, port, name, user, pass) + db = d.(DataStorage) + default: + err = errors.New("unknown db") + } + if err != nil { + return nil, err + } + return db, nil +} + +func ConfigureLogStorage(db_type, host, port, name, user, pass string) (db LogStorage, err error) { + var d Storage + switch db_type { + case utils.REDIS: + var db_nb int + db_nb, err = strconv.Atoi(name) + if err != nil { + Logger.Crit("Redis db name must be an integer!") + return nil, err + } + if port != "" { + host += ":" + port + } + d, err = NewRedisStorage(host, db_nb, pass) + db = d.(LogStorage) + case utils.MONGO: + d, err = NewMongoStorage(host, port, name, user, pass) + db = d.(LogStorage) + case utils.POSTGRES: + d, err = NewPostgresStorage(host, port, name, user, pass) + db = d.(LogStorage) + case utils.MYSQL: + d, err = NewMySQLStorage(host, port, name, user, pass) + db = d.(LogStorage) + default: + err = errors.New("unknown db") + } + if err != nil { + return nil, err + } + return db, nil +} + +func ConfigureLoadStorage(db_type, host, port, name, user, pass string) (db LoadStorage, err error) { + var d Storage + switch db_type { + case utils.POSTGRES: + d, err = NewPostgresStorage(host, port, name, user, pass) + db = d.(LoadStorage) + case utils.MYSQL: + d, err = NewMySQLStorage(host, port, name, user, pass) + db = d.(LoadStorage) + default: + err = errors.New("unknown db") + } + if err != nil { + return nil, err + } + return db, nil +} + +func ConfigureCdrStorage(db_type, host, port, name, user, pass string) (db CdrStorage, err error) { + var d Storage + switch db_type { + case utils.POSTGRES: + d, err = NewPostgresStorage(host, port, name, user, pass) + db = d.(CdrStorage) + case utils.MYSQL: + d, err = NewMySQLStorage(host, port, name, user, pass) + db = d.(CdrStorage) default: err = errors.New("unknown db") } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index b4cdd77b3..56824abd4 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -30,7 +30,7 @@ import ( // Import tariff plan from csv into storDb type TPCSVImporter struct { TPid string // Load data on this tpid - StorDb DataStorage // StorDb connection handle + StorDb LoadStorage // StorDb connection handle DirPath string // Directory path to import from Sep rune // Separator in the csv file Verbose bool // If true will print a detailed information instead of silently discarding it diff --git a/mediator/mediator.go b/mediator/mediator.go index b1685cbe9..263fbbe26 100644 --- a/mediator/mediator.go +++ b/mediator/mediator.go @@ -35,10 +35,10 @@ import ( "time" ) -func NewMediator(connector engine.Connector, storDb engine.DataStorage, cfg *config.CGRConfig) (m *Mediator, err error) { +func NewMediator(connector engine.Connector, logDb engine.LogStorage, cdrDb engine.CdrStorage, cfg *config.CGRConfig) (m *Mediator, err error) { m = &Mediator{ connector: connector, - storDb: storDb, + logDb: logDb, cgrCfg: cfg, } m.fieldNames = make(map[string][]string) @@ -52,7 +52,8 @@ func NewMediator(connector engine.Connector, storDb engine.DataStorage, cfg *con type Mediator struct { connector engine.Connector - storDb engine.DataStorage + logDb engine.LogStorage + cdrDb engine.CdrStorage cgrCfg *config.CGRConfig cdrInDir, cdrOutDir string accIdField string @@ -166,8 +167,8 @@ func (self *Mediator) TrackCDRFiles() (err error) { // Retrive the cost from logging database func (self *Mediator) getCostsFromDB(cdr utils.CDR) (cc *engine.CallCost, err error) { for i := 0; i < 3; i++ { // Mechanism to avoid concurrency between SessionManager writing the costs and mediator picking them up - cc, err = self.storDb.GetCallCostLog(cdr.GetCgrId(), engine.SESSION_MANAGER_SOURCE) //ToDo: What are we getting when there is no log? - if cc != nil { // There were no errors, chances are that we got what we are looking for + cc, err = self.logDb.GetCallCostLog(cdr.GetCgrId(), engine.SESSION_MANAGER_SOURCE) //ToDo: What are we getting when there is no log? + if cc != nil { // There were no errors, chances are that we got what we are looking for break } time.Sleep(time.Duration(i) * time.Second) @@ -204,10 +205,10 @@ func (self *Mediator) getCostsFromRater(cdr utils.CDR) (*engine.CallCost, error) err = self.connector.GetCost(cd, cc) } if err != nil { - self.storDb.LogError(cdr.GetCgrId(), engine.MEDIATOR_SOURCE, err.Error()) + self.logDb.LogError(cdr.GetCgrId(), engine.MEDIATOR_SOURCE, err.Error()) } else { // If the mediator calculated a price it will write it to logdb - self.storDb.LogCallCost(cdr.GetCgrId(), engine.MEDIATOR_SOURCE, cc) + self.logDb.LogCallCost(cdr.GetCgrId(), engine.MEDIATOR_SOURCE, cc) } return cc, err } @@ -273,9 +274,9 @@ func (self *Mediator) MediateCSVCDR(cdrfn string) (err error) { return } -func (self *Mediator) MediateDBCDR(cdr utils.CDR, db engine.DataStorage) error { +func (self *Mediator) MediateDBCDR(cdr utils.CDR, db engine.CdrStorage) error { var qryCC *engine.CallCost - cc := &engine.CallCost{Cost:-1} + cc := &engine.CallCost{Cost: -1} var errCost error if cdr.GetReqType() == utils.PREPAID || cdr.GetReqType() == utils.POSTPAID { // Should be previously calculated and stored in DB @@ -293,5 +294,5 @@ func (self *Mediator) MediateDBCDR(cdr utils.CDR, db engine.DataStorage) error { if errCost != nil { extraInfo = errCost.Error() } - return self.storDb.SetRatedCdr(cdr, cc, extraInfo) + return self.cdrDb.SetRatedCdr(cdr, cc, extraInfo) } diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 1a439e9a2..fe989f0a9 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -42,10 +42,10 @@ type FSSessionManager struct { sessions []*Session connector engine.Connector debitPeriod time.Duration - loggerDB engine.DataStorage + loggerDB engine.LogStorage } -func NewFSSessionManager(storage engine.DataStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager { +func NewFSSessionManager(storage engine.LogStorage, connector engine.Connector, debitPeriod time.Duration) *FSSessionManager { return &FSSessionManager{loggerDB: storage, connector: connector, debitPeriod: debitPeriod} } @@ -344,7 +344,7 @@ func (sm *FSSessionManager) GetDebitPeriod() time.Duration { return sm.debitPeriod } -func (sm *FSSessionManager) GetDbLogger() engine.DataStorage { +func (sm *FSSessionManager) GetDbLogger() engine.LogStorage { return sm.loggerDB } diff --git a/sessionmanager/sessionmanager.go b/sessionmanager/sessionmanager.go index b254e3dcd..ef7311e75 100644 --- a/sessionmanager/sessionmanager.go +++ b/sessionmanager/sessionmanager.go @@ -30,6 +30,6 @@ type SessionManager interface { RemoveSession(*Session) LoopAction(*Session, *engine.CallDescriptor, float64) GetDebitPeriod() time.Duration - GetDbLogger() engine.DataStorage + GetDbLogger() engine.LogStorage Shutdown() error } From 5acbdc69346d236a8247dc308a79d42a3899aa95 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 27 Aug 2013 15:07:50 +0300 Subject: [PATCH 02/39] cleaned unimplemented methods from db adaptors --- engine/calldesc.go | 4 +- engine/storage_mongo.go | 207 ---------------------------------------- engine/storage_redis.go | 204 --------------------------------------- engine/storage_sql.go | 37 ------- 4 files changed, 2 insertions(+), 450 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 78da15fa5..b0f2e4f80 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -39,9 +39,9 @@ func init() { //db_server := "127.0.0.1" //db_server := "192.168.0.17" m, _ := NewMapStorage() + //m, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "") + //m, _ = NewRedisStorage(db_server+":6379", 11, "") storageGetter, _ = m.(DataStorage) - //storageGetter, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "") - //storageGetter, _ = NewRedisStorage(db_server+":6379", 11, "") storageLogger = storageGetter.(LogStorage) } diff --git a/engine/storage_mongo.go b/engine/storage_mongo.go index 7bf735a60..f39fea872 100644 --- a/engine/storage_mongo.go +++ b/engine/storage_mongo.go @@ -19,10 +19,8 @@ along with this program. If not, see package engine import ( - "errors" "fmt" "github.com/cgrates/cgrates/history" - "github.com/cgrates/cgrates/utils" "labix.org/v2/mgo" "labix.org/v2/mgo/bson" "log" @@ -156,163 +154,6 @@ func (ms *MongoStorage) SetDestination(dest *Destination) error { return ms.db.C("destinations").Insert(dest) } -func (ms *MongoStorage) GetTPIds() ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPTiming(tpid string, tm *Timing) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPTiming(tpid, tmId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPTiming(tpid, tmId string) (*Timing, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPDestinationIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPDestination(tpid, destTag string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -// Extracts destinations from StorDB on specific tariffplan id -func (ms *MongoStorage) GetTPDestination(tpid, destTag string) (*Destination, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPDestination(tpid string, dest *Destination) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPRate(tpid, rtId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPRates(tpid string, rts map[string][]*Rate) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPRate(tpid, rtId string) (*utils.TPRate, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPRateIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPDestinationRate(tpid, drId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPDestinationRates(tpid string, drs map[string][]*DestinationRate) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPDestinationRate(tpid, drId string) (*utils.TPDestinationRate, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPDestinationRateIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPDestRateTiming(tpid, drtId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPDestRateTimings(tpid string, drts map[string][]*DestinationRateTiming) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPDestRateTiming(tpid, drtId string) (*utils.TPDestRateTiming, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPRatingProfile(tpid, rpId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPRatingProfiles(tpid string, rps map[string][]*RatingProfile) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPRatingProfile(tpid, rpId string) (*utils.TPRatingProfile, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPRatingProfileIds(filters *utils.AttrTPRatingProfileIds) ([]string, error) { - 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(tpid string, acts map[string][]*Action) 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) ExistsTPActionTimings(tpid, atId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPActionTimings(tpid string, ats map[string][]*ActionTiming) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPActionTimings(tpid, atId string) (map[string][]*utils.TPActionTimingsRow, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPActionTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPActionTriggers(tpid, atId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPActionTriggers(tpid string, ats map[string][]*ActionTrigger) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPActionTriggerIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) ExistsTPAccountActions(tpid, aaId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) SetTPAccountActions(tpid string, aa map[string]*AccountAction) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetTPAccountActionIds(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) @@ -375,51 +216,3 @@ func (ms *MongoStorage) LogActionTiming(source string, at *ActionTiming, as Acti func (ms *MongoStorage) LogError(uuid, source, errstr string) (err error) { return ms.db.C("errlog").Insert(&LogErrEntry{uuid, errstr, source}) } - -func (ms *MongoStorage) SetCdr(utils.CDR) error { - return nil -} - -func (ms *MongoStorage) SetRatedCdr(cdr utils.CDR, cc *CallCost, extraInfo string) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (ms *MongoStorage) GetAllRatedCdr() ([]utils.CDR, error) { - return nil, nil -} - -func (ms *MongoStorage) GetDestinations(tpid string) ([]*Destination, error) { - return nil, nil -} - -func (ms *MongoStorage) GetTpDestinations(tpid, tag string) ([]*Destination, error) { - return nil, nil -} - -func (ms *MongoStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpDestinationRates(tpid, tag string) (map[string][]*DestinationRate, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpTimings(tpid, tag string) (map[string]*Timing, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpDestinationRateTimings(tpid, tag string) ([]*DestinationRateTiming, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*RatingProfile, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpActions(tpid, tag string) (map[string][]*Action, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpActionTimings(tpid, tag string) (map[string][]*ActionTiming, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*ActionTrigger, error) { - return nil, nil -} -func (ms *MongoStorage) GetTpAccountActions(tpid, tag string) (map[string]*AccountAction, error) { - return nil, nil -} diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 572ca34ac..e49294844 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -19,10 +19,8 @@ along with this program. If not, see package engine import ( - "errors" "fmt" "github.com/cgrates/cgrates/history" - "github.com/cgrates/cgrates/utils" "menteslibres.net/gosexy/redis" "strconv" "strings" @@ -110,163 +108,6 @@ func (rs *RedisStorage) SetDestination(dest *Destination) (err error) { return } -func (rs *RedisStorage) GetTPIds() ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPTiming(tpid string, tm *Timing) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPTiming(tpid, tmId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPTiming(tpid, tmId string) (*Timing, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPDestinationIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPDestination(tpid, destTag string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -// Extracts destinations from StorDB on specific tariffplan id -func (rs *RedisStorage) GetTPDestination(tpid, destTag string) (*Destination, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPDestination(tpid string, dest *Destination) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPRate(tpid, rtId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPRates(tpid string, rts map[string][]*Rate) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPRate(tpid, rtId string) (*utils.TPRate, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPRateIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPDestinationRate(tpid, drId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPDestinationRates(tpid string, drs map[string][]*DestinationRate) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPDestinationRate(tpid, drId string) (*utils.TPDestinationRate, error) { - return nil, nil -} - -func (rs *RedisStorage) GetTPDestinationRateIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPDestRateTiming(tpid, drtId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPDestRateTimings(tpid string, drts map[string][]*DestinationRateTiming) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPDestRateTiming(tpid, drtId string) (*utils.TPDestRateTiming, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPDestRateTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPRatingProfile(tpid, rpId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPRatingProfiles(tpid string, rps map[string][]*RatingProfile) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPRatingProfile(tpid, rpId string) (*utils.TPRatingProfile, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPRatingProfileIds(filters *utils.AttrTPRatingProfileIds) ([]string, error) { - 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(tpid string, acts map[string][]*Action) 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) ExistsTPActionTimings(tpid, atId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPActionTimings(tpid string, ats map[string][]*ActionTiming) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPActionTimings(tpid, atId string) (map[string][]*utils.TPActionTimingsRow, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPActionTimingIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPActionTriggers(tpid, atId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPActionTriggers(tpid string, ats map[string][]*ActionTrigger) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPActionTriggerIds(tpid string) ([]string, error) { - return nil, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) ExistsTPAccountActions(tpid, aaId string) (bool, error) { - return false, errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) SetTPAccountActions(tpid string, aa map[string]*AccountAction) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetTPAccountActionIds(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 { @@ -383,48 +224,3 @@ func (rs *RedisStorage) LogError(uuid, source, errstr string) (err error) { _, err = rs.db.Set(LOG_ERR+source+"_"+uuid, errstr) return } - -func (rs *RedisStorage) SetCdr(utils.CDR) error { - return nil -} - -func (rs *RedisStorage) SetRatedCdr(cdr utils.CDR, cc *CallCost, extraInfo string) error { - return errors.New(utils.ERR_NOT_IMPLEMENTED) -} - -func (rs *RedisStorage) GetAllRatedCdr() ([]utils.CDR, error) { - return nil, nil -} - -func (rs *RedisStorage) GetTpDestinations(tpid, tag string) ([]*Destination, error) { - return nil, nil -} - -func (rs *RedisStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { - return nil, nil -} - -func (ms *RedisStorage) GetTpDestinationRates(tpid, tag string) (map[string][]*DestinationRate, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpTimings(tpid, tag string) (map[string]*Timing, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpDestinationRateTimings(tpid, tag string) ([]*DestinationRateTiming, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpRatingProfiles(tpid, tag string) (map[string]*RatingProfile, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpActions(tpid, tag string) (map[string][]*Action, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpActionTimings(tpid, tag string) (map[string][]*ActionTiming, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpActionTriggers(tpid, tag string) (map[string][]*ActionTrigger, error) { - return nil, nil -} -func (rs *RedisStorage) GetTpAccountActions(tpid, tag string) (map[string]*AccountAction, error) { - return nil, nil -} diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 00674df31..7c83e7756 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -38,25 +38,6 @@ func (self *SQLStorage) Flush() (err error) { return } -func (self *SQLStorage) GetRatingProfile(string) (rp *RatingProfile, err error) { - /*row := self.Db.QueryRow(fmt.Sprintf("SELECT * FROM ratingprofiles WHERE id='%s'", id)) - err = row.Scan(&rp, &cc.Direction, &cc.Tenant, &cc.TOR, &cc.Subject, &cc.Destination, &cc.Cost, &cc.ConnectFee, ×pansJson) - err = json.Unmarshal([]byte(timespansJson), cc.Timespans)*/ - return -} - -func (self *SQLStorage) SetRatingProfile(rp *RatingProfile) (err error) { - return -} - -func (self *SQLStorage) GetDestination(string) (d *Destination, err error) { - return -} - -func (self *SQLStorage) SetDestination(d *Destination) (err error) { - return -} - // Return a list with all TPids defined in the system, even if incomplete, isolated in some table. func (self *SQLStorage) GetTPIds() ([]string, error) { rows, err := self.Db.Query( @@ -800,24 +781,6 @@ func (self *SQLStorage) GetTPAccountActionIds(tpid string) ([]string, error) { return ids, nil } -func (self *SQLStorage) GetUserBalance(string) (ub *UserBalance, err error) { return } - -func (self *SQLStorage) SetUserBalance(ub *UserBalance) (err error) { return } - -func (self *SQLStorage) GetActions(string) (as Actions, err error) { - return -} - -func (self *SQLStorage) SetActions(key string, as Actions) (err error) { return } - -func (self *SQLStorage) GetActionTimings(key string) (ats ActionTimings, err error) { return } - -func (self *SQLStorage) SetActionTimings(key string, ats ActionTimings) (err error) { return } - -func (self *SQLStorage) GetAllActionTimings() (ats map[string]ActionTimings, err error) { - return -} - func (self *SQLStorage) LogCallCost(uuid, source string, cc *CallCost) (err error) { //ToDo: Add cgrid to logCallCost if self.Db == nil { From ed6c1fc15188e5e9dcdf5c4d04af6092db08be96 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 27 Aug 2013 16:01:44 +0300 Subject: [PATCH 03/39] logdb, cdrdb and loaddb are all stordb --- cmd/cgr-engine/cgr-engine.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index cf7c96d53..97a58fc0c 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -330,6 +330,9 @@ func main() { } defer logDb.Close() engine.SetStorageLogger(logDb) + // loadDb,cdrDb and logDb are all mapped on the same stordb storage + loadDb = logDb.(engine.LoadStorage) + cdrDb = logDb.(engine.CdrStorage) engine.SetRoundingMethodAndDecimals(cfg.RoundingMethod, cfg.RoundingDecimals) if cfg.SMDebitInterval > 0 { From 47442dda3c8b10ded983c605a7c8f90d8504898c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 27 Aug 2013 19:01:30 +0300 Subject: [PATCH 04/39] small typo --- engine/calldesc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index b0f2e4f80..6eb0a256a 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -101,7 +101,7 @@ type CallDescriptor struct { TOR string Tenant, Subject, Account, Destination string TimeStart, TimeEnd time.Time - LoopIndex float64 // indicates the postion of this segment in a cost request loop + LoopIndex float64 // indicates the position of this segment in a cost request loop CallDuration time.Duration // the call duration so far (partial or final) Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject From 637e146ba264c361610e654933f445b01c716405 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 30 Aug 2013 13:26:06 +0300 Subject: [PATCH 05/39] absolute/percent const rename --- engine/action_trigger.go | 2 +- engine/actions_test.go | 30 +++++++++++++++--------------- engine/minute_buckets.go | 6 ------ engine/minute_buckets_test.go | 8 ++++---- engine/storage_test.go | 4 ++-- engine/units_counter_test.go | 4 ++-- engine/userbalance.go | 8 ++++++++ engine/userbalance_test.go | 10 +++++----- 8 files changed, 37 insertions(+), 35 deletions(-) diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 2add33bc1..5fae1ca24 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -28,7 +28,7 @@ type ActionTrigger struct { Id string // uniquely identify the trigger BalanceId string Direction string - ThresholdType string + ThresholdType string //*min_counter, *max_counter, *min_balance, *max_balance ThresholdValue float64 DestinationId string Weight float64 diff --git a/engine/actions_test.go b/engine/actions_test.go index 964e49dec..23a5301ff 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -428,7 +428,7 @@ func TestActionResetTriggres(t *testing.T) { Id: "TEST_UB", BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetTriggersAction(ub, nil) @@ -455,7 +455,7 @@ func TestActionResetTriggresActionFilter(t *testing.T) { Id: "TEST_UB", BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetTriggersAction(ub, &Action{BalanceId: SMS}) @@ -470,7 +470,7 @@ func TestActionSetPostpaid(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } setPostpaidAction(ub, nil) @@ -485,7 +485,7 @@ func TestActionSetPrepaid(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } setPrepaidAction(ub, nil) @@ -500,7 +500,7 @@ func TestActionResetPrepaid(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPrepaidAction(ub, nil) @@ -519,7 +519,7 @@ func TestActionResetPostpaid(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPostpaidAction(ub, nil) @@ -538,7 +538,7 @@ func TestActionTopupResetCredit(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -558,7 +558,7 @@ func TestActionTopupResetMinutes(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} @@ -579,7 +579,7 @@ func TestActionTopupCredit(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -599,7 +599,7 @@ func TestActionTopupMinutes(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} @@ -620,7 +620,7 @@ func TestActionDebitCredit(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -640,7 +640,7 @@ func TestActionDebitMinutes(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} @@ -661,7 +661,7 @@ func TestActionResetAllCounters(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetCountersAction(ub, nil) @@ -688,7 +688,7 @@ func TestActionResetCounterMinutes(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES} @@ -716,7 +716,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND} diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go index ae34d0fa7..ca6a994b2 100644 --- a/engine/minute_buckets.go +++ b/engine/minute_buckets.go @@ -34,11 +34,6 @@ type MinuteBucket struct { precision int } -const ( - PERCENT = "*percent" - ABSOLUTE = "*absolute" -) - // Returns the available number of seconds for a specified credit func (mb *MinuteBucket) GetSecondsForCredit(credit float64) (seconds float64) { seconds = mb.Seconds @@ -64,7 +59,6 @@ func (mb *MinuteBucket) Equal(o *MinuteBucket) bool { return mb.DestinationId == o.DestinationId && mb.Weight == o.Weight && mb.Price == o.Price && - mb.PriceType == o.PriceType && mb.ExpirationDate.Equal(o.ExpirationDate) } diff --git a/engine/minute_buckets_test.go b/engine/minute_buckets_test.go index 55bc2f44f..acdf5972e 100644 --- a/engine/minute_buckets_test.go +++ b/engine/minute_buckets_test.go @@ -57,16 +57,16 @@ func TestMinutBucketSortPrice(t *testing.T) { } func TestMinutBucketEqual(t *testing.T) { - mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: ABSOLUTE, DestinationId: ""} - mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: ABSOLUTE, DestinationId: ""} - mb3 := &MinuteBucket{Weight: 1, precision: 1, Price: 2, PriceType: ABSOLUTE, DestinationId: ""} + mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb3 := &MinuteBucket{Weight: 1, precision: 1, Price: 2, PriceType: PRICE_ABSOLUTE, DestinationId: ""} if !mb1.Equal(mb2) || mb2.Equal(mb3) { t.Error("Equal failure!", mb1, mb2, mb3) } } func TestMinutBucketClone(t *testing.T) { - mb1 := &MinuteBucket{Seconds: 1, Weight: 2, Price: 3, PriceType: ABSOLUTE, DestinationId: "5"} + mb1 := &MinuteBucket{Seconds: 1, Weight: 2, Price: 3, PriceType: PRICE_ABSOLUTE, DestinationId: "5"} mb2 := mb1.Clone() if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) { t.Error("Cloning failure: ", mb1, mb2) diff --git a/engine/storage_test.go b/engine/storage_test.go index 9fd35aeb1..d13865c03 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -78,7 +78,7 @@ func GetUB() *UserBalance { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } at := &ActionTrigger{ Id: "some_uuid", @@ -95,7 +95,7 @@ func GetUB() *UserBalance { Id: "rif", Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, UnitCounters: []*UnitsCounter{uc, uc}, ActionTriggers: ActionTriggerPriotityList{at, at, at}, } diff --git a/engine/units_counter_test.go b/engine/units_counter_test.go index 0c942d483..e7f8a2c43 100644 --- a/engine/units_counter_test.go +++ b/engine/units_counter_test.go @@ -27,7 +27,7 @@ func TestUnitsCounterAddMinuteBucket(t *testing.T) { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } uc.addMinutes(20, "test") if len(uc.MinuteBuckets) != 2 { @@ -40,7 +40,7 @@ func TestUnitsCounterAddMinuteBucketExists(t *testing.T) { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } uc.addMinutes(5, "0723") if len(uc.MinuteBuckets) != 2 || uc.MinuteBuckets[0].Seconds != 15 { diff --git a/engine/userbalance.go b/engine/userbalance.go index 7c298de31..9aa25b2df 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -38,6 +38,9 @@ const ( TRAFFIC = "*internet" TRAFFIC_TIME = "*internet_time" MINUTES = "*minutes" + // action price type + PRICE_PERCENT = "*percent" + PRICE_ABSOLUTE = "*absolute" ) var ( @@ -54,6 +57,9 @@ type UserBalance struct { MinuteBuckets []*MinuteBucket UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList + // group information + GroupIds []string + UserIds []string } type Balance struct { @@ -61,6 +67,8 @@ type Balance struct { Value float64 ExpirationDate time.Time Weight float64 + GroupIds []string + Percent float64 } func (b *Balance) Equal(o *Balance) bool { diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 562c1e712..b3daec4db 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -343,7 +343,7 @@ func TestUserBalancedebitMinuteBucket(t *testing.T) { Id: "rif", Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } newMb := &MinuteBucket{Weight: 20, Price: 1, DestinationId: "NEW"} ub.debitMinuteBucket(newMb) @@ -358,7 +358,7 @@ func TestUserBalancedebitMinuteBucketExists(t *testing.T) { Id: "rif", Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } newMb := &MinuteBucket{Seconds: -10, Weight: 20, Price: 1, DestinationId: "NAT"} ub.debitMinuteBucket(newMb) @@ -372,7 +372,7 @@ func TestUserBalanceAddMinuteNil(t *testing.T) { Id: "rif", Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } ub.debitMinuteBucket(nil) if len(ub.MinuteBuckets) != 2 { @@ -404,7 +404,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { Id: "TEST_UB", BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) @@ -429,7 +429,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { Id: "TEST_UB", BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, + MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "*min_counter", ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) From 8f804b8bc53c64cb465a9d29917a99952a1c61a6 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 30 Aug 2013 19:02:05 +0300 Subject: [PATCH 06/39] refactorings, started groups --- engine/action_trigger.go | 16 ++++++ engine/actions_test.go | 104 +++++++++++++++++++++++++++++++++ engine/balances.go | 119 ++++++++++++++++++++++++++++++++++++++ engine/calldesc.go | 7 ++- engine/groups.go | 46 +++++++++++++++ engine/userbalance.go | 120 ++++----------------------------------- 6 files changed, 300 insertions(+), 112 deletions(-) create mode 100644 engine/balances.go create mode 100644 engine/groups.go diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 5fae1ca24..3a8aaa281 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -64,6 +64,22 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) { return } +// returns true if the field of the action timing are equeal to the non empty +// fields of the action +func (at *ActionTrigger) Match(a *Action) bool { + if a == nil { + return true + } + id := a.BalanceId == "" || at.BalanceId == a.BalanceId + direction := a.Direction == "" || at.Direction == a.Direction + thresholdType, thresholdValue := true, true + if a.MinuteBucket != nil { + thresholdType = a.MinuteBucket.PriceType == "" || at.ThresholdType == a.MinuteBucket.PriceType + thresholdValue = a.MinuteBucket.Price == 0 || at.ThresholdValue == a.MinuteBucket.Price + } + return id && direction && thresholdType && thresholdValue +} + // Structure to store actions according to weight type ActionTriggerPriotityList []*ActionTrigger diff --git a/engine/actions_test.go b/engine/actions_test.go index 23a5301ff..3c9d2480c 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -400,6 +400,110 @@ func TestActionTimingPriotityListWeight(t *testing.T) { } } +func TestActionTriggerMatchNil(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + var a *Action + if !at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatchAllBlank(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{} + if !at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatchMinuteBucketBlank(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT} + if !at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatchMinuteBucketFull(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + if !at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatchAllFull(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + if !at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatchSomeFalse(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{Direction: INBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + if at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatcMinuteBucketFalse(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 3}} + if at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + +func TestActionTriggerMatcAllFalse(t *testing.T) { + at := &ActionTrigger{ + Direction: OUTBOUND, + BalanceId: CREDIT, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 2, + } + a := &Action{Direction: INBOUND, BalanceId: MINUTES, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_COUNTER, Price: 3}} + if at.Match(a) { + t.Errorf("Action trigger [%v] does not match action [%v]", at, a) + } +} + func TestActionTriggerPriotityList(t *testing.T) { at1 := &ActionTrigger{Weight: 10} at2 := &ActionTrigger{Weight: 20} diff --git a/engine/balances.go b/engine/balances.go new file mode 100644 index 000000000..cffdf95a1 --- /dev/null +++ b/engine/balances.go @@ -0,0 +1,119 @@ +/* +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 engine + +import ( + "sort" + "time" +) + +type Balance struct { + Id string + Value float64 + ExpirationDate time.Time + Weight float64 + GroupIds []string + SpecialPercent float64 +} + +func (b *Balance) Equal(o *Balance) bool { + return b.ExpirationDate.Equal(o.ExpirationDate) || + b.Weight == o.Weight +} + +func (b *Balance) IsExpired() bool { + return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now()) +} + +func (b *Balance) Clone() *Balance { + return &Balance{ + Id: b.Id, + Value: b.Value, + ExpirationDate: b.ExpirationDate, + Weight: b.Weight, + } +} + +/* +Structure to store minute buckets according to weight, precision or price. +*/ +type BalanceChain []*Balance + +func (bc BalanceChain) Len() int { + return len(bc) +} + +func (bc BalanceChain) Swap(i, j int) { + bc[i], bc[j] = bc[j], bc[i] +} + +func (bc BalanceChain) Less(j, i int) bool { + return bc[i].Weight < bc[j].Weight +} + +func (bc BalanceChain) Sort() { + sort.Sort(bc) +} + +func (bc BalanceChain) GetTotalValue() (total float64) { + for _, b := range bc { + if !b.IsExpired() { + total += b.Value + } + } + return +} + +func (bc BalanceChain) Debit(amount float64) float64 { + bc.Sort() + for i, b := range bc { + if b.IsExpired() { + continue + } + if b.Value >= amount || i == len(bc)-1 { // if last one go negative + b.Value -= amount + break + } + b.Value = 0 + amount -= b.Value + } + return bc.GetTotalValue() +} + +func (bc BalanceChain) Equal(o BalanceChain) bool { + if len(bc) != len(o) { + return false + } + bc.Sort() + o.Sort() + for i := 0; i < len(bc); i++ { + if !bc[i].Equal(o[i]) { + return false + } + } + return true +} + +func (bc BalanceChain) Clone() BalanceChain { + var newChain BalanceChain + for _, b := range bc { + newChain = append(newChain, b.Clone()) + } + return newChain +} diff --git a/engine/calldesc.go b/engine/calldesc.go index 26904dc62..7b7551698 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -369,7 +369,9 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { } for _, ts := range cc.Timespans { if ts.MinuteInfo != nil { - userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true) + if err = userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true); err != nil { + return cc, err + } } } } @@ -387,8 +389,7 @@ func (cd *CallDescriptor) MaxDebit(startTime time.Time) (cc *CallCost, err error return new(CallCost), errors.New("no more credit") } if remainingSeconds > 0 { // for postpaying client returns -1 - rs, _ := time.ParseDuration(fmt.Sprintf("%vs", remainingSeconds)) - cd.TimeEnd = cd.TimeStart.Add(rs) + cd.TimeEnd = cd.TimeStart.Add(time.Duration(remainingSeconds) * time.Second) } return cd.Debit() } diff --git a/engine/groups.go b/engine/groups.go new file mode 100644 index 000000000..baa650fc5 --- /dev/null +++ b/engine/groups.go @@ -0,0 +1,46 @@ +/* +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 engine + +import ( + "sort" +) + +type GroupLink struct { + Id string + Weight float64 +} + +type GroupLinks []*GroupLink + +func (gls GroupLinks) Len() int { + return len(gls) +} + +func (gls GroupLinks) Swap(i, j int) { + gls[i], gls[j] = gls[j], gls[i] +} + +func (gls GroupLinks) Less(j, i int) bool { + return gls[i].Weight < gls[j].Weight +} + +func (gls GroupLinks) Sort() { + sort.Sort(gls) +} diff --git a/engine/userbalance.go b/engine/userbalance.go index 9aa25b2df..d43f7ca50 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -21,9 +21,7 @@ package engine import ( "errors" "github.com/cgrates/cgrates/utils" - "sort" "strings" - "time" ) const ( @@ -41,6 +39,11 @@ const ( // action price type PRICE_PERCENT = "*percent" PRICE_ABSOLUTE = "*absolute" + // action trigger threshold types + TRIGGER_MIN_COUNTER = "*min_counter" + TRIGGER_MAX_COUNTER = "*max_counter" + TRIGGER_MIN_BALANCE = "*min_balance" + TRIGGER_MAX_BALANCE = "*max_balance" ) var ( @@ -49,6 +52,7 @@ var ( /* Structure containing information about user's credit (minutes, cents, sms...).' +This can represent a user or a shared group. */ type UserBalance struct { Id string @@ -57,104 +61,10 @@ type UserBalance struct { MinuteBuckets []*MinuteBucket UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList + + Groups GroupLinks // user info about groups // group information - GroupIds []string - UserIds []string -} - -type Balance struct { - Id string - Value float64 - ExpirationDate time.Time - Weight float64 - GroupIds []string - Percent float64 -} - -func (b *Balance) Equal(o *Balance) bool { - return b.ExpirationDate.Equal(o.ExpirationDate) || - b.Weight == o.Weight -} - -func (b *Balance) IsExpired() bool { - return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now()) -} - -func (b *Balance) Clone() *Balance { - return &Balance{ - Id: b.Id, - Value: b.Value, - ExpirationDate: b.ExpirationDate, - Weight: b.Weight, - } -} - -/* -Structure to store minute buckets according to weight, precision or price. -*/ -type BalanceChain []*Balance - -func (bc BalanceChain) Len() int { - return len(bc) -} - -func (bc BalanceChain) Swap(i, j int) { - bc[i], bc[j] = bc[j], bc[i] -} - -func (bc BalanceChain) Less(j, i int) bool { - return bc[i].Weight < bc[j].Weight -} - -func (bc BalanceChain) Sort() { - sort.Sort(bc) -} - -func (bc BalanceChain) GetTotalValue() (total float64) { - for _, b := range bc { - if !b.IsExpired() { - total += b.Value - } - } - return -} - -func (bc BalanceChain) Debit(amount float64) float64 { - bc.Sort() - for i, b := range bc { - if b.IsExpired() { - continue - } - if b.Value >= amount || i == len(bc)-1 { // if last one go negative - b.Value -= amount - break - } - b.Value = 0 - amount -= b.Value - } - return bc.GetTotalValue() -} - -func (bc BalanceChain) Equal(o BalanceChain) bool { - if len(bc) != len(o) { - return false - } - bc.Sort() - o.Sort() - for i := 0; i < len(bc); i++ { - if !bc[i].Equal(o[i]) { - return false - } - } - return true -} - -func (bc BalanceChain) Clone() BalanceChain { - var newChain BalanceChain - for _, b := range bc { - newChain = append(newChain, b.Clone()) - } - return newChain + UserIds []string // group info about users } /* @@ -309,11 +219,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { // the next reset (see RESET_TRIGGERS action type) continue } - if a != nil && (at.BalanceId != a.BalanceId || - at.Direction != a.Direction || - (a.MinuteBucket != nil && - (at.ThresholdType != a.MinuteBucket.PriceType || - at.ThresholdValue != a.MinuteBucket.Price))) { + if !at.Match(a) { continue } if strings.Contains(at.ThresholdType, "counter") { @@ -386,11 +292,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { // If the action is not nil it acts like a filter func (ub *UserBalance) resetActionTriggers(a *Action) { for _, at := range ub.ActionTriggers { - if a != nil && (at.BalanceId != a.BalanceId || - at.Direction != a.Direction || - (a.MinuteBucket != nil && - (at.ThresholdType != a.MinuteBucket.PriceType || - at.ThresholdValue != a.MinuteBucket.Price))) { + if !at.Match(a) { continue } at.Executed = false From 5516c8ff1fa4006bed5f3d7dc960222f3f1467dc Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 4 Sep 2013 18:18:49 +0300 Subject: [PATCH 07/39] started bucket balance merge --- engine/actions_test.go | 2 +- engine/balances.go | 31 ++++++++++++++++++++++++++++--- engine/userbalance_test.go | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/engine/actions_test.go b/engine/actions_test.go index 01c783131..e735bc4e2 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -87,7 +87,7 @@ func TestActionTimingOnlyMonthdays(t *testing.T) { tomorrow := time.Date(y, m, d, 0, 0, 0, 0, time.Local).AddDate(0, 0, 1) at := &ActionTiming{Timing: &Interval{MonthDays: MonthDays{1, 25, 2, tomorrow.Day()}}} st := at.GetNextStartTime() - expected := time.Date(y, m, tomorrow.Day(), 0, 0, 0, 0, time.Local) + expected := tomorrow if !st.Equal(expected) { t.Errorf("Expected %v was %v", expected, st) } diff --git a/engine/balances.go b/engine/balances.go index cffdf95a1..132cce5ff 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -19,21 +19,33 @@ along with this program. If not, see package engine import ( + "math" "sort" "time" ) +// Can hold different units as seconds or monetary type Balance struct { Id string Value float64 ExpirationDate time.Time Weight float64 GroupIds []string - SpecialPercent float64 + SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) + DestinationId string + precision int } +/*func (b *Balance) Equal(o *Balance) bool { + return b.ExpirationDate.Equal(o.ExpirationDate) && + b.Weight == o.Weight && + b.Value == o.Value && + b.SpecialPrice == b.SpecialPrice && + b.DestinationId == o.DestinationId +}*/ +// TODO: why func (b *Balance) Equal(o *Balance) bool { - return b.ExpirationDate.Equal(o.ExpirationDate) || + return b.ExpirationDate.Equal(o.ExpirationDate) && b.Weight == o.Weight } @@ -45,11 +57,22 @@ func (b *Balance) Clone() *Balance { return &Balance{ Id: b.Id, Value: b.Value, + SpecialPrice: b.SpecialPrice, + DestinationId: b.DestinationId, ExpirationDate: b.ExpirationDate, Weight: b.Weight, } } +// Returns the available number of seconds for a specified credit +func (b *Balance) GetSecondsForCredit(credit float64) (seconds float64) { + seconds = b.Value + if b.SpecialPrice > 0 { + seconds = math.Min(credit/b.SpecialPrice, b.Value) + } + return +} + /* Structure to store minute buckets according to weight, precision or price. */ @@ -64,7 +87,9 @@ func (bc BalanceChain) Swap(i, j int) { } func (bc BalanceChain) Less(j, i int) bool { - return bc[i].Weight < bc[j].Weight + return bc[i].Weight < bc[j].Weight || + bc[i].precision < bc[j].precision || + bc[i].SpecialPrice > bc[j].SpecialPrice } func (bc BalanceChain) Sort() { diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index b3daec4db..bc1d3b92d 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -405,7 +405,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { From f82a2c58a85291706155eb55e997d31c62ba37e1 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 5 Sep 2013 16:55:06 +0300 Subject: [PATCH 08/39] better balance Equal function --- engine/balances.go | 12 +++--------- engine/userbalance.go | 6 ++++-- engine/userbalance_test.go | 7 ++++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 132cce5ff..4f68fb8e7 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -36,17 +36,11 @@ type Balance struct { precision int } -/*func (b *Balance) Equal(o *Balance) bool { - return b.ExpirationDate.Equal(o.ExpirationDate) && - b.Weight == o.Weight && - b.Value == o.Value && - b.SpecialPrice == b.SpecialPrice && - b.DestinationId == o.DestinationId -}*/ -// TODO: why func (b *Balance) Equal(o *Balance) bool { return b.ExpirationDate.Equal(o.ExpirationDate) && - b.Weight == o.Weight + b.Weight == o.Weight && + b.SpecialPrice == b.SpecialPrice && + b.DestinationId == o.DestinationId } func (b *Balance) IsExpired() bool { diff --git a/engine/userbalance.go b/engine/userbalance.go index d43f7ca50..698bfa69a 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -182,7 +182,9 @@ func (ub *UserBalance) debitBalanceAction(a *Action) float64 { newBalance := &Balance{ Id: utils.GenUUID(), ExpirationDate: a.ExpirationDate, - Weight: a.Weight, + } + if a.MinuteBucket != nil { + newBalance.Weight = a.MinuteBucket.Weight } found := false id := a.BalanceId + a.Direction @@ -225,7 +227,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { if strings.Contains(at.ThresholdType, "counter") { for _, uc := range ub.UnitCounters { if uc.BalanceId == at.BalanceId { - if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety + if at.BalanceId == MINUTES { for _, mb := range uc.MinuteBuckets { if strings.Contains(at.ThresholdType, "*max") { if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index bc1d3b92d..0b9f8ab53 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -19,7 +19,6 @@ along with this program. If not, see package engine import ( - //"log" "testing" "time" ) @@ -430,7 +429,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "*min_counter", ActionsId: "TEST_ACTIONS"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: TRIGGER_MIN_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { @@ -443,10 +442,12 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { Id: "TEST_UB_OREDER", BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS_ORDER"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { + t.Log(ub.BalanceMap[CREDIT+OUTBOUND][0]) + t.Log(ub.BalanceMap[CREDIT+OUTBOUND][1]) t.Error("Error executing triggered actions in order", ub.BalanceMap[CREDIT+OUTBOUND]) } } From 931c6efb1aa807d54fae26d5603359c2489799d4 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 5 Sep 2013 19:14:22 +0300 Subject: [PATCH 09/39] converted minutebucket to balance, compiled but test fail --- engine/action.go | 39 ++--- engine/action_timing.go | 5 +- engine/action_trigger.go | 12 +- engine/actions_test.go | 122 ++++++-------- engine/balances.go | 17 +- engine/calldesc.go | 10 +- engine/calldesc_test.go | 21 ++- engine/loader_csv.go | 73 ++++---- engine/minute_buckets.go | 90 ---------- engine/minute_buckets_test.go | 28 ++-- engine/storage_sql.go | 43 ++--- engine/storage_test.go | 11 +- engine/timespans.go | 14 +- engine/timespans_test.go | 46 ++--- engine/units_counter.go | 24 +-- engine/units_counter_test.go | 24 +-- engine/userbalance.go | 101 +++++------ engine/userbalance_test.go | 308 +++++++++++++++++----------------- 18 files changed, 419 insertions(+), 569 deletions(-) delete mode 100644 engine/minute_buckets.go diff --git a/engine/action.go b/engine/action.go index c667326ac..1dcf33baf 100644 --- a/engine/action.go +++ b/engine/action.go @@ -28,17 +28,16 @@ import ( Structure to be filled for each tariff plan with the bonus value for received calls minutes. */ type Action struct { - Id string - ActionType string - BalanceId string - Direction string - ExpirationString string - ExpirationDate time.Time - Units float64 - Weight float64 - MinuteBucket *MinuteBucket - DestinationTag, RateType string // From here for import/load purposes only - RateValue, MinutesWeight float64 + Id string + ActionType string + BalanceId string + Direction string + ExpirationString string + Weight float64 + Balance *Balance + DestinationTag, RateType string // From here for import/load purposes only + ExpirationDate time.Time + Units, RateValue, MinutesWeight float64 } const ( @@ -86,7 +85,7 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { } func logAction(ub *UserBalance, a *Action) (err error) { - Logger.Info(fmt.Sprintf("%v %v %v", a.BalanceId, a.Units, a.MinuteBucket)) + Logger.Info(fmt.Sprintf("%v %v %v", a.BalanceId, a.Balance)) return } @@ -116,11 +115,7 @@ func resetPrepaidAction(ub *UserBalance, a *Action) (err error) { } func topupResetAction(ub *UserBalance, a *Action) (err error) { - if a.BalanceId == MINUTES { - ub.MinuteBuckets = make([]*MinuteBucket, 0) - } else { - ub.BalanceMap[a.BalanceId+a.Direction] = BalanceChain{&Balance{Value: 0}} // ToDo: can ub be empty here? - } + ub.BalanceMap[a.BalanceId+a.Direction] = BalanceChain{&Balance{Value: 0}} // ToDo: can ub be empty here? genericMakeNegative(a) genericDebit(ub, a) return @@ -157,11 +152,8 @@ func resetCountersAction(ub *UserBalance, a *Action) (err error) { } func genericMakeNegative(a *Action) { - if a.Units > 0 { // only apply if not allready negative - a.Units = -a.Units - } - if a.MinuteBucket != nil && a.MinuteBucket.Seconds > 0 { - a.MinuteBucket.Seconds = -a.MinuteBucket.Seconds + if a.Balance != nil && a.Balance.Value > 0 { // only apply if not allready negative + a.Balance.Value = -a.Balance.Value } } @@ -170,7 +162,7 @@ func genericDebit(ub *UserBalance, a *Action) (err error) { ub.BalanceMap = make(map[string]BalanceChain) } if a.BalanceId == MINUTES { - ub.debitMinuteBucket(a.MinuteBucket) + ub.debitMinuteBalance(a.Balance) } else { ub.debitBalanceAction(a) } @@ -181,7 +173,6 @@ func genericReset(ub *UserBalance) { for k, _ := range ub.BalanceMap { ub.BalanceMap[k] = BalanceChain{&Balance{Value: 0}} } - ub.MinuteBuckets = make([]*MinuteBucket, 0) ub.UnitCounters = make([]*UnitsCounter, 0) ub.resetActionTriggers(nil) } diff --git a/engine/action_timing.go b/engine/action_timing.go index c2ec2d8db..a608b9dcb 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -219,10 +219,7 @@ func (at *ActionTiming) Execute() (err error) { return } for _, a := range aac { - a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) - if a.MinuteBucket != nil { - a.MinuteBucket.ExpirationDate = a.ExpirationDate - } + a.Balance.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Crit(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 3a8aaa281..2b1c7e2a7 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -46,10 +46,10 @@ func (at *ActionTrigger) Execute(ub *UserBalance) (err error) { return } for _, a := range aac { - a.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) - if a.MinuteBucket != nil { - a.MinuteBucket.ExpirationDate = a.ExpirationDate + if a.Balance == nil { + a.Balance = &Balance{} } + a.Balance.ExpirationDate, _ = utils.ParseDate(a.ExpirationString) actionFunction, exists := getActionFunc(a.ActionType) if !exists { Logger.Warning(fmt.Sprintf("Function type %v not available, aborting execution!", a.ActionType)) @@ -73,9 +73,9 @@ func (at *ActionTrigger) Match(a *Action) bool { id := a.BalanceId == "" || at.BalanceId == a.BalanceId direction := a.Direction == "" || at.Direction == a.Direction thresholdType, thresholdValue := true, true - if a.MinuteBucket != nil { - thresholdType = a.MinuteBucket.PriceType == "" || at.ThresholdType == a.MinuteBucket.PriceType - thresholdValue = a.MinuteBucket.Price == 0 || at.ThresholdValue == a.MinuteBucket.Price + if a.Balance != nil { + thresholdType = a.Balance.SpecialPriceType == "" || at.ThresholdType == a.Balance.SpecialPriceType + thresholdValue = a.Balance.SpecialPrice == 0 || at.ThresholdValue == a.Balance.SpecialPrice } return id && direction && thresholdType && thresholdValue } diff --git a/engine/actions_test.go b/engine/actions_test.go index e735bc4e2..47d27bae2 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -338,10 +338,9 @@ func TestActionTimingOneTimeRun(t *testing.T) { func TestActionTimingLogFunction(t *testing.T) { a := &Action{ - ActionType: "*log", - BalanceId: "test", - Units: 1.1, - MinuteBucket: &MinuteBucket{}, + ActionType: "*log", + BalanceId: "test", + Balance: &Balance{Value: 1.1}, } at := &ActionTiming{ actions: []*Action{a}, @@ -446,7 +445,7 @@ func TestActionTriggerMatchMinuteBucketFull(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + a := &Action{Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} if !at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -459,7 +458,7 @@ func TestActionTriggerMatchAllFull(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} if !at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -472,20 +471,20 @@ func TestActionTriggerMatchSomeFalse(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: INBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 2}} + a := &Action{Direction: INBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } } -func TestActionTriggerMatcMinuteBucketFalse(t *testing.T) { +func TestActionTriggerMatcBalanceFalse(t *testing.T) { at := &ActionTrigger{ Direction: OUTBOUND, BalanceId: CREDIT, ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_BALANCE, Price: 3}} + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 3}} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -498,7 +497,7 @@ func TestActionTriggerMatcAllFalse(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: INBOUND, BalanceId: MINUTES, MinuteBucket: &MinuteBucket{PriceType: TRIGGER_MAX_COUNTER, Price: 3}} + a := &Action{Direction: INBOUND, BalanceId: MINUTES, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_COUNTER, SpecialPrice: 3}} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -522,7 +521,7 @@ func TestActionTriggerPriotityList(t *testing.T) { BalanceId: "BALANCE", Units: 10, Weight: 11, - MinuteBucket: &MinuteBucket{}, + Balance: &Balance{}, } logAction(nil, a) }*/ @@ -530,9 +529,8 @@ func TestActionTriggerPriotityList(t *testing.T) { func TestActionResetTriggres(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetTriggersAction(ub, nil) @@ -557,9 +555,8 @@ func TestActionResetTriggresExecutesThem(t *testing.T) { func TestActionResetTriggresActionFilter(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetTriggersAction(ub, &Action{BalanceId: SMS}) @@ -572,9 +569,8 @@ func TestActionSetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } setPostpaidAction(ub, nil) @@ -587,9 +583,8 @@ func TestActionSetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } setPrepaidAction(ub, nil) @@ -602,16 +597,15 @@ func TestActionResetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPrepaidAction(ub, nil) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - len(ub.MinuteBuckets) != 0 || + len(ub.BalanceMap[MINUTES]) != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { t.Error("Reset prepaid action failed!") } @@ -621,16 +615,15 @@ func TestActionResetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPostpaidAction(ub, nil) if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - len(ub.MinuteBuckets) != 0 || + len(ub.BalanceMap[MINUTES]) != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { t.Error("Reset postpaid action failed!") } @@ -640,9 +633,8 @@ func TestActionTopupResetCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -650,7 +642,7 @@ func TestActionTopupResetCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 10 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Errorf("Topup reset action failed: %#v", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -660,20 +652,19 @@ func TestActionTopupResetMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.MinuteBuckets[0].Seconds != 5 || + ub.BalanceMap[MINUTES][0].Value != 5 || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 1 || + len(ub.BalanceMap[MINUTES]) != 1 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup reset minutes action failed!", ub.MinuteBuckets[0]) + t.Error("Topup reset minutes action failed!", ub.BalanceMap[MINUTES][0]) } } @@ -681,9 +672,8 @@ func TestActionTopupCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -691,7 +681,7 @@ func TestActionTopupCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 110 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Error("Topup action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -701,20 +691,19 @@ func TestActionTopupMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.MinuteBuckets[0].Seconds != 15 || + ub.BalanceMap[MINUTES][0].Value != 15 || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup minutes action failed!", ub.MinuteBuckets[0]) + t.Error("Topup minutes action failed!", ub.BalanceMap[MINUTES][0]) } } @@ -722,9 +711,8 @@ func TestActionDebitCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} @@ -732,7 +720,7 @@ func TestActionDebitCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 90 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Error("Debit action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -742,20 +730,19 @@ func TestActionDebitMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.MinuteBuckets[0].Seconds != 5 || + ub.BalanceMap[MINUTES][0].Value != 5 || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Debit minutes action failed!", ub.MinuteBuckets[0]) + t.Error("Debit minutes action failed!", ub.BalanceMap[MINUTES][0]) } } @@ -763,25 +750,24 @@ func TestActionResetAllCounters(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetCountersAction(ub, nil) if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.UnitCounters[0].MinuteBuckets) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.UnitCounters[0].MinuteBalances) != 1 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { t.Error("Reset counters action failed!") } if len(ub.UnitCounters) < 1 { t.FailNow() } - mb := ub.UnitCounters[0].MinuteBuckets[0] - if mb.Weight != 20 || mb.Price != 1 || mb.Seconds != 10 || mb.DestinationId != "NAT" { + mb := ub.UnitCounters[0].MinuteBalances[0] + if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 10 || mb.DestinationId != "NAT" { t.Errorf("Minute bucked cloned incorrectly: %v!", mb) } } @@ -790,9 +776,8 @@ func TestActionResetCounterMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES} @@ -800,16 +785,16 @@ func TestActionResetCounterMinutes(t *testing.T) { if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 2 || - len(ub.UnitCounters[1].MinuteBuckets) != 1 || - len(ub.MinuteBuckets) != 2 || + len(ub.UnitCounters[1].MinuteBalances) != 1 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { - t.Error("Reset counters action failed!", ub.UnitCounters[1].MinuteBuckets) + t.Error("Reset counters action failed!", ub.UnitCounters[1].MinuteBalances) } - if len(ub.UnitCounters) < 2 || len(ub.UnitCounters[1].MinuteBuckets) < 1 { + if len(ub.UnitCounters) < 2 || len(ub.UnitCounters[1].MinuteBalances) < 1 { t.FailNow() } - mb := ub.UnitCounters[1].MinuteBuckets[0] - if mb.Weight != 20 || mb.Price != 1 || mb.Seconds != 10 || mb.DestinationId != "NAT" { + mb := ub.UnitCounters[1].MinuteBalances[0] + if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 10 || mb.DestinationId != "NAT" { t.Errorf("Minute bucked cloned incorrectly: %v!", mb) } } @@ -818,9 +803,8 @@ func TestActionResetCounterCREDIT(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: CREDIT, Direction: OUTBOUND} @@ -828,7 +812,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 2 || - len(ub.MinuteBuckets) != 2 || + len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { t.Error("Reset counters action failed!", ub.UnitCounters) } diff --git a/engine/balances.go b/engine/balances.go index 4f68fb8e7..036113c42 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -26,14 +26,15 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Id string - Value float64 - ExpirationDate time.Time - Weight float64 - GroupIds []string - SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) - DestinationId string - precision int + Id string + Value float64 + ExpirationDate time.Time + Weight float64 + GroupIds []string + SpecialPriceType string + SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) + DestinationId string + precision int } func (b *Balance) Equal(o *Balance) bool { diff --git a/engine/calldesc.go b/engine/calldesc.go index 88394e810..d72a97eb9 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -197,13 +197,13 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti timespans = append(timespans, firstSpan) // split on (free) minute buckets if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { - _, _, bucketList := userBalance.getSecondsForPrefix(cd.Destination) - for _, mb := range bucketList { + _, _, minuteBalances := userBalance.getSecondsForPrefix(cd.Destination) + for _, b := range minuteBalances { for i := 0; i < len(timespans); i++ { if timespans[i].MinuteInfo != nil { continue } - newTs := timespans[i].SplitByMinuteBucket(mb) + newTs := timespans[i].SplitByMinuteBalance(b) if newTs != nil { timespans = append(timespans, newTs) firstSpan = newTs // we move the firstspan to the newly created one for further spliting @@ -443,8 +443,8 @@ The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) AddRecievedCallSeconds() (err error) { if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { a := &Action{ - Direction: INBOUND, - MinuteBucket: &MinuteBucket{Seconds: cd.Amount, DestinationId: cd.Destination}, + Direction: INBOUND, + Balance: &Balance{Value: cd.Amount, DestinationId: cd.Destination}, } userBalance.countUnits(a) return nil diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 706bcd3c2..647dbd84b 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -36,21 +36,20 @@ func init() { func populateDB() { minu := &UserBalance{ - Id: "*out:vdf:minu", - Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}}, - MinuteBuckets: []*MinuteBucket{ - &MinuteBucket{Seconds: 200, DestinationId: "NAT", Weight: 10}, - &MinuteBucket{Seconds: 100, DestinationId: "RET", Weight: 20}, - }, + Id: "*out:vdf:minu", + Type: UB_TYPE_PREPAID, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}, MINUTES: BalanceChain{ + &Balance{Value: 200, DestinationId: "NAT", Weight: 10}, + &Balance{Value: 100, DestinationId: "RET", Weight: 20}, + }}, } broker := &UserBalance{ Id: "*out:vdf:broker", Type: UB_TYPE_PREPAID, - MinuteBuckets: []*MinuteBucket{ - &MinuteBucket{Seconds: 20, DestinationId: "NAT", Weight: 10, Price: 1}, - &MinuteBucket{Seconds: 100, DestinationId: "RET", Weight: 20}, - }, + BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{ + &Balance{Value: 20, DestinationId: "NAT", Weight: 10, SpecialPrice: 1}, + &Balance{Value: 100, DestinationId: "RET", Weight: 20}, + }}, } if storageGetter != nil { storageGetter.(Storage).Flush() diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 657c96c47..12e9939f4 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -348,50 +348,35 @@ func (csvr *CSVReader) LoadActions() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) } - var a *Action - if record[2] != MINUTES { - a = &Action{ - ActionType: record[1], - BalanceId: record[2], - Direction: record[3], - Units: units, - ExpirationString: record[5], - } - if _, err := utils.ParseDate(a.ExpirationString); err != nil { - return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) - } - } else { - value, err := strconv.ParseFloat(record[8], 64) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse action price: %v", err)) - } - minutesWeight, err := strconv.ParseFloat(record[9], 64) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse action minutes weight: %v", err)) - } - weight, err := strconv.ParseFloat(record[9], 64) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse action weight: %v", err)) - } - a = &Action{ - Id: utils.GenUUID(), - ActionType: record[1], - BalanceId: record[2], - Direction: record[3], - Weight: weight, - ExpirationString: record[5], - MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutesWeight, - Price: value, - PriceType: record[7], - DestinationId: record[6], - }, - } - if _, err := utils.ParseDate(a.ExpirationString); err != nil { - return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) - } - + value, err := strconv.ParseFloat(record[8], 64) + if err != nil { + return errors.New(fmt.Sprintf("Could not parse action price: %v", err)) + } + minutesWeight, err := strconv.ParseFloat(record[9], 64) + if err != nil { + return errors.New(fmt.Sprintf("Could not parse action minutes weight: %v", err)) + } + weight, err := strconv.ParseFloat(record[9], 64) + if err != nil { + return errors.New(fmt.Sprintf("Could not parse action weight: %v", err)) + } + a := &Action{ + Id: utils.GenUUID(), + ActionType: record[1], + BalanceId: record[2], + Direction: record[3], + Weight: weight, + ExpirationString: record[5], + Balance: &Balance{ + Value: units, + Weight: minutesWeight, + SpecialPrice: value, + SpecialPriceType: record[7], + DestinationId: record[6], + }, + } + if _, err := utils.ParseDate(a.ExpirationString); err != nil { + return errors.New(fmt.Sprintf("Could not parse expiration time: %v", err)) } csvr.actions[tag] = append(csvr.actions[tag], a) } diff --git a/engine/minute_buckets.go b/engine/minute_buckets.go deleted file mode 100644 index ca6a994b2..000000000 --- a/engine/minute_buckets.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -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 engine - -import ( - "math" - "sort" - "time" -) - -type MinuteBucket struct { - Seconds float64 - Weight float64 - Price float64 // percentage from standard price or absolute value depending on Type - PriceType string - DestinationId string - ExpirationDate time.Time - precision int -} - -// Returns the available number of seconds for a specified credit -func (mb *MinuteBucket) GetSecondsForCredit(credit float64) (seconds float64) { - seconds = mb.Seconds - if mb.Price > 0 { - seconds = math.Min(credit/mb.Price, mb.Seconds) - } - return -} - -// Creates a similar minute -func (mb *MinuteBucket) Clone() *MinuteBucket { - return &MinuteBucket{ - Seconds: mb.Seconds, - Weight: mb.Weight, - Price: mb.Price, - PriceType: mb.PriceType, - DestinationId: mb.DestinationId, - } -} - -// Equal method -func (mb *MinuteBucket) Equal(o *MinuteBucket) bool { - return mb.DestinationId == o.DestinationId && - mb.Weight == o.Weight && - mb.Price == o.Price && - mb.ExpirationDate.Equal(o.ExpirationDate) -} - -func (mb *MinuteBucket) IsExpired() bool { - return !mb.ExpirationDate.IsZero() && mb.ExpirationDate.Before(time.Now()) -} - -/* -Structure to store minute buckets according to weight, precision or price. -*/ -type bucketsorter []*MinuteBucket - -func (bs bucketsorter) Len() int { - return len(bs) -} - -func (bs bucketsorter) Swap(i, j int) { - bs[i], bs[j] = bs[j], bs[i] -} - -func (bs bucketsorter) Less(j, i int) bool { - return bs[i].Weight < bs[j].Weight || - bs[i].precision < bs[j].precision || - bs[i].Price > bs[j].Price -} - -func (bs bucketsorter) Sort() { - sort.Sort(bs) -} diff --git a/engine/minute_buckets_test.go b/engine/minute_buckets_test.go index acdf5972e..b218d2334 100644 --- a/engine/minute_buckets_test.go +++ b/engine/minute_buckets_test.go @@ -24,9 +24,9 @@ import ( ) func TestMinutBucketSortWeight(t *testing.T) { - mb1 := &MinuteBucket{Weight: 1, precision: 2, Price: 2} - mb2 := &MinuteBucket{Weight: 2, precision: 1, Price: 1} - var bs bucketsorter + mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} + mb2 := &Balance{Weight: 2, precision: 1, SpecialPrice: 1} + var bs BalanceChain bs = append(bs, mb2, mb1) bs.Sort() if bs[0] != mb1 || bs[1] != mb2 { @@ -35,9 +35,9 @@ func TestMinutBucketSortWeight(t *testing.T) { } func TestMinutBucketSortPrecision(t *testing.T) { - mb1 := &MinuteBucket{Weight: 1, precision: 2, Price: 2} - mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1} - var bs bucketsorter + mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} + mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} + var bs BalanceChain bs = append(bs, mb2, mb1) bs.Sort() if bs[0] != mb1 || bs[1] != mb2 { @@ -45,10 +45,10 @@ func TestMinutBucketSortPrecision(t *testing.T) { } } -func TestMinutBucketSortPrice(t *testing.T) { - mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1} - mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 2} - var bs bucketsorter +func TestMinutBucketSortSpecialPrice(t *testing.T) { + mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} + mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2} + var bs BalanceChain bs = append(bs, mb2, mb1) bs.Sort() if bs[0] != mb1 || bs[1] != mb2 { @@ -57,16 +57,16 @@ func TestMinutBucketSortPrice(t *testing.T) { } func TestMinutBucketEqual(t *testing.T) { - mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb3 := &MinuteBucket{Weight: 1, precision: 1, Price: 2, PriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb3 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} if !mb1.Equal(mb2) || mb2.Equal(mb3) { t.Error("Equal failure!", mb1, mb2, mb3) } } func TestMinutBucketClone(t *testing.T) { - mb1 := &MinuteBucket{Seconds: 1, Weight: 2, Price: 3, PriceType: PRICE_ABSOLUTE, DestinationId: "5"} + mb1 := &Balance{Value: 1, Weight: 2, SpecialPrice: 3, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "5"} mb2 := mb1.Clone() if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) { t.Error("Cloning failure: ", mb1, mb2) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 7c83e7756..2668849d8 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -540,7 +540,7 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err qry += "," } qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s','%s',%f,%f,%f)", - tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Units, act.ExpirationString, + tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Balance.Value, act.ExpirationString, act.DestinationTag, act.RateType, act.RateValue, act.MinutesWeight, act.Weight) i++ } @@ -1083,32 +1083,21 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er if err := rows.Scan(&id, &tpid, &tag, &action, &balance_type, &direction, &units, &expirationDate, &destinations_tag, &rate_type, &rate, &minutes_weight, &weight); err != nil { return nil, err } - var a *Action - if balance_type != MINUTES { - a = &Action{ - ActionType: action, - BalanceId: balance_type, - Direction: direction, - Units: units, - ExpirationString: expirationDate, - } - } else { - var price float64 - a = &Action{ - Id: utils.GenUUID(), - ActionType: action, - BalanceId: balance_type, - Direction: direction, - Weight: weight, - ExpirationString: expirationDate, - MinuteBucket: &MinuteBucket{ - Seconds: units, - Weight: minutes_weight, - Price: price, - PriceType: rate_type, - DestinationId: destinations_tag, - }, - } + var price float64 + a := &Action{ + Id: utils.GenUUID(), + ActionType: action, + BalanceId: balance_type, + Direction: direction, + Weight: weight, + ExpirationString: expirationDate, + Balance: &Balance{ + Value: units, + Weight: minutes_weight, + SpecialPrice: price, + SpecialPriceType: rate_type, + DestinationId: destinations_tag, + }, } as[tag] = append(as[tag], a) } diff --git a/engine/storage_test.go b/engine/storage_test.go index d13865c03..e77d99bf5 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -75,10 +75,10 @@ func TestMsgpackTime(t *testing.T) { func GetUB() *UserBalance { uc := &UnitsCounter{ - Direction: OUTBOUND, - BalanceId: SMS, - Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Direction: OUTBOUND, + BalanceId: SMS, + Units: 100, + MinuteBalances: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } at := &ActionTrigger{ Id: "some_uuid", @@ -94,8 +94,7 @@ func GetUB() *UserBalance { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{uc, uc}, ActionTriggers: ActionTriggerPriotityList{at, at, at}, } diff --git a/engine/timespans.go b/engine/timespans.go index 2b1134f5d..3a9da6d6a 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -181,7 +181,7 @@ func (ts *TimeSpan) SplitByActivationPeriod(ap *ActivationPeriod) (newTs *TimeSp /* Splits the given timespan on minute bucket's duration. */ -func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) { +func (ts *TimeSpan) SplitByMinuteBalance(mb *Balance) (newTs *TimeSpan) { // if mb expired skip it if !mb.ExpirationDate.IsZero() && (ts.TimeStart.Equal(mb.ExpirationDate) || ts.TimeStart.After(mb.ExpirationDate)) { return nil @@ -197,20 +197,20 @@ func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) { } s := ts.GetDuration().Seconds() - ts.MinuteInfo = &MinuteInfo{mb.DestinationId, s, mb.Price} - if s <= mb.Seconds { - mb.Seconds -= s + ts.MinuteInfo = &MinuteInfo{mb.DestinationId, s, mb.SpecialPrice} + if s <= mb.Value { + mb.Value -= s return newTs } - secDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", mb.Seconds)) + secDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", mb.Value)) newTimeEnd := ts.TimeStart.Add(secDuration) newTs = &TimeSpan{TimeStart: newTimeEnd, TimeEnd: ts.TimeEnd} ts.TimeEnd = newTimeEnd newTs.CallDuration = ts.CallDuration - ts.MinuteInfo.Quantity = mb.Seconds + ts.MinuteInfo.Quantity = mb.Value ts.SetNewCallDuration(newTs) - mb.Seconds = 0 + mb.Value = 0 return } diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 8ecc3f7e0..6e1f54d8a 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -220,9 +220,9 @@ func TestSetInterval(t *testing.T) { func TestTimespanSplitByMinuteBucketPlenty(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 180} + mb := &Balance{Value: 180} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { t.Error("Not enough minutes on minute bucket split") } @@ -231,12 +231,12 @@ func TestTimespanSplitByMinuteBucketPlenty(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketScarce(t *testing.T) { +func TestTimespanSplitByMinuteBalanceScarce(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 60} + mb := &Balance{Value: 60} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { t.Error("Not enough minutes on minute bucket split") } @@ -245,12 +245,12 @@ func TestTimespanSplitByMinuteBucketScarce(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketPlentyExpired(t *testing.T) { +func TestTimespanSplitByMinuteBalancePlentyExpired(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 39, 0, 0, time.UTC)} + mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 39, 0, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo != nil { t.Error("Not enough minutes on minute bucket split") } @@ -259,12 +259,12 @@ func TestTimespanSplitByMinuteBucketPlentyExpired(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketPlentyExpiring(t *testing.T) { +func TestTimespanSplitByMinuteBalancePlentyExpiring(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} + mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { t.Error("Not enough minutes on minute bucket split") } @@ -273,12 +273,12 @@ func TestTimespanSplitByMinuteBucketPlentyExpiring(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketPlentyExpiringEnd(t *testing.T) { +func TestTimespanSplitByMinuteBalancePlentyExpiringEnd(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC)} + mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { t.Error("Not enough minutes on minute bucket split") } @@ -287,12 +287,12 @@ func TestTimespanSplitByMinuteBucketPlentyExpiringEnd(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketScarceExpiringSame(t *testing.T) { +func TestTimespanSplitByMinuteBalanceScarceExpiringSame(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 120, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} + mb := &Balance{Value: 120, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { t.Error("Not enough minutes on minute bucket split") } @@ -301,12 +301,12 @@ func TestTimespanSplitByMinuteBucketScarceExpiringSame(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketScarceExpiringDifferentExpFirst(t *testing.T) { +func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentExpFirst(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 140, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 1, 0, time.UTC)} + mb := &Balance{Value: 140, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 1, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { t.Error("Not enough minutes on minute bucket split: ", ts.MinuteInfo.Quantity) } @@ -315,12 +315,12 @@ func TestTimespanSplitByMinuteBucketScarceExpiringDifferentExpFirst(t *testing.T } } -func TestTimespanSplitByMinuteBucketScarceExpiringDifferentScarceFirst(t *testing.T) { +func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentScarceFirst(t *testing.T) { t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &MinuteBucket{Seconds: 61, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 30, 0, time.UTC)} + mb := &Balance{Value: 61, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 30, 0, time.UTC)} ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBucket(mb) + newTs := ts.SplitByMinuteBalance(mb) if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { t.Error("Not enough minutes on minute bucket split") } diff --git a/engine/units_counter.go b/engine/units_counter.go index 2f129e567..fed75af11 100644 --- a/engine/units_counter.go +++ b/engine/units_counter.go @@ -24,39 +24,39 @@ import ( // Amount of a trafic of a certain type type UnitsCounter struct { - Direction string - BalanceId string - Units float64 - MinuteBuckets bucketsorter + Direction string + BalanceId string + Units float64 + MinuteBalances BalanceChain } func (uc *UnitsCounter) initMinuteBuckets(ats []*ActionTrigger) { - uc.MinuteBuckets = make(bucketsorter, 0) + uc.MinuteBalances = make(BalanceChain, 0) for _, at := range ats { acs, err := storageGetter.GetActions(at.ActionsId) if err != nil { continue } for _, a := range acs { - if a.MinuteBucket != nil { - uc.MinuteBuckets = append(uc.MinuteBuckets, a.MinuteBucket.Clone()) + if a.Balance != nil { + uc.MinuteBalances = append(uc.MinuteBalances, a.Balance.Clone()) } } } - uc.MinuteBuckets.Sort() + uc.MinuteBalances.Sort() } -// Adds the minutes from the received minute bucket to an existing bucket if the destination -// is the same or ads the minutye bucket to the list if none matches. +// Adds the minutes from the received minute balance to an existing bucket if the destination +// is the same or ads the minute balance to the list if none matches. func (uc *UnitsCounter) addMinutes(amount float64, prefix string) { - for _, mb := range uc.MinuteBuckets { + for _, mb := range uc.MinuteBalances { d, err := GetDestination(mb.DestinationId) if err != nil { Logger.Err(fmt.Sprintf("Minutes counter: unknown destination: %s", mb.DestinationId)) continue } if _, ok := d.containsPrefix(prefix); ok { - mb.Seconds += amount + mb.Value += amount break } } diff --git a/engine/units_counter_test.go b/engine/units_counter_test.go index e7f8a2c43..9917e4a79 100644 --- a/engine/units_counter_test.go +++ b/engine/units_counter_test.go @@ -22,28 +22,28 @@ import ( "testing" ) -func TestUnitsCounterAddMinuteBucket(t *testing.T) { +func TestUnitsCounterAddBalance(t *testing.T) { uc := &UnitsCounter{ - Direction: OUTBOUND, - BalanceId: SMS, - Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Direction: OUTBOUND, + BalanceId: SMS, + Units: 100, + MinuteBalances: []*Balance{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } uc.addMinutes(20, "test") - if len(uc.MinuteBuckets) != 2 { + if len(uc.MinuteBalances) != 2 { t.Error("Error adding minute bucket!") } } -func TestUnitsCounterAddMinuteBucketExists(t *testing.T) { +func TestUnitsCounterAddBalanceExists(t *testing.T) { uc := &UnitsCounter{ - Direction: OUTBOUND, - BalanceId: SMS, - Units: 100, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Direction: OUTBOUND, + BalanceId: SMS, + Units: 100, + MinuteBalances: []*Balance{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, } uc.addMinutes(5, "0723") - if len(uc.MinuteBuckets) != 2 || uc.MinuteBuckets[0].Seconds != 15 { + if len(uc.MinuteBalances) != 2 || uc.MinuteBalances[0].Value != 15 { t.Error("Error adding minute bucket!") } } diff --git a/engine/userbalance.go b/engine/userbalance.go index 698bfa69a..177bdd53b 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -55,10 +55,10 @@ Structure containing information about user's credit (minutes, cents, sms...).' This can represent a user or a shared group. */ type UserBalance struct { - Id string - Type string // prepaid-postpaid - BalanceMap map[string]BalanceChain - MinuteBuckets []*MinuteBucket + Id string + Type string // prepaid-postpaid + BalanceMap map[string]BalanceChain + //MinuteBuckets []*MinuteBucket UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList @@ -70,57 +70,60 @@ type UserBalance struct { /* Returns user's available minutes for the specified destination */ -func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, bucketList bucketsorter) { +func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, balances BalanceChain) { credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() - if len(ub.MinuteBuckets) == 0 { + if len(ub.BalanceMap[MINUTES]) == 0 { // Logger.Debug("There are no minute buckets to check for user: ", ub.Id) return } - for _, mb := range ub.MinuteBuckets { - if mb.IsExpired() { + for _, b := range ub.BalanceMap[MINUTES] { + if b.IsExpired() { continue } - d, err := GetDestination(mb.DestinationId) + d, err := GetDestination(b.DestinationId) if err != nil { continue } if precision, ok := d.containsPrefix(prefix); ok { - mb.precision = precision - if mb.Seconds > 0 { - bucketList = append(bucketList, mb) + b.precision = precision + if b.Value > 0 { + balances = append(balances, b) } } } - bucketList.Sort() // sorts the buckets according to priority, precision or price - for _, mb := range bucketList { - s := mb.GetSecondsForCredit(credit) - credit -= s * mb.Price + balances.Sort() // sorts the buckets according to priority, precision or price + for _, b := range balances { + s := b.GetSecondsForCredit(credit) + credit -= s * b.SpecialPrice seconds += s } return } // Debit seconds from specified minute bucket -func (ub *UserBalance) debitMinuteBucket(newMb *MinuteBucket) error { +func (ub *UserBalance) debitMinuteBalance(newMb *Balance) error { if newMb == nil { return errors.New("Nil minute bucket!") } + if ub.BalanceMap == nil { + ub.BalanceMap = make(map[string]BalanceChain, 0) + } found := false - for _, mb := range ub.MinuteBuckets { + for _, mb := range ub.BalanceMap[MINUTES] { if mb.IsExpired() { continue } if mb.Equal(newMb) { - mb.Seconds -= newMb.Seconds + mb.Value -= newMb.Value found = true break } } // if it is not found and the Seconds are negative (topup) // then we add it to the list - if !found && newMb.Seconds <= 0 { - newMb.Seconds = -newMb.Seconds - ub.MinuteBuckets = append(ub.MinuteBuckets, newMb) + if !found && newMb.Value <= 0 { + newMb.Value = -newMb.Value + ub.BalanceMap[MINUTES] = append(ub.BalanceMap[MINUTES], newMb) } return nil } @@ -133,7 +136,7 @@ debited and an error will be returned. */ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count bool) error { if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Seconds: amount, DestinationId: prefix}}) + ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: amount, DestinationId: prefix}}) } avaliableNbSeconds, _, bucketList := ub.getSecondsForPrefix(prefix) if avaliableNbSeconds < amount { @@ -144,13 +147,13 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count credit = bc.Clone() } for _, mb := range bucketList { - if mb.Seconds < amount { - if mb.Price > 0 { // debit the money if the bucket has price - credit.Debit(mb.Seconds * mb.Price) + if mb.Value < amount { + if mb.SpecialPrice > 0 { // debit the money if the bucket has price + credit.Debit(mb.Value * mb.SpecialPrice) } } else { - if mb.Price > 0 { // debit the money if the bucket has price - credit.Debit(amount * mb.Price) + if mb.SpecialPrice > 0 { // debit the money if the bucket has price + credit.Debit(amount * mb.SpecialPrice) } break } @@ -165,11 +168,11 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count ub.BalanceMap[CREDIT+OUTBOUND] = credit // credit is > 0 for _, mb := range bucketList { - if mb.Seconds < amount { - amount -= mb.Seconds - mb.Seconds = 0 + if mb.Value < amount { + amount -= mb.Value + mb.Value = 0 } else { - mb.Seconds -= amount + mb.Value -= amount break } } @@ -183,8 +186,8 @@ func (ub *UserBalance) debitBalanceAction(a *Action) float64 { Id: utils.GenUUID(), ExpirationDate: a.ExpirationDate, } - if a.MinuteBucket != nil { - newBalance.Weight = a.MinuteBucket.Weight + if a.Balance != nil { + newBalance.Weight = a.Balance.Weight } found := false id := a.BalanceId + a.Direction @@ -228,14 +231,14 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { for _, uc := range ub.UnitCounters { if uc.BalanceId == at.BalanceId { if at.BalanceId == MINUTES { - for _, mb := range uc.MinuteBuckets { + for _, mb := range uc.MinuteBalances { if strings.Contains(at.ThresholdType, "*max") { - if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { + if mb.DestinationId == at.DestinationId && mb.Value >= at.ThresholdValue { // run the actions at.Execute(ub) } } else { //MIN - if mb.DestinationId == at.DestinationId && mb.Seconds <= at.ThresholdValue { + if mb.DestinationId == at.DestinationId && mb.Value <= at.ThresholdValue { // run the actions at.Execute(ub) } @@ -259,14 +262,14 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { } else { // BALANCE for _, b := range ub.BalanceMap[at.BalanceId] { if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety - for _, mb := range ub.MinuteBuckets { + for _, mb := range ub.BalanceMap[MINUTES] { if strings.Contains(at.ThresholdType, "*max") { - if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { + if mb.DestinationId == at.DestinationId && mb.Value >= at.ThresholdValue { // run the actions at.Execute(ub) } } else { //MIN - if mb.DestinationId == at.DestinationId && mb.Seconds <= at.ThresholdValue { + if mb.DestinationId == at.DestinationId && mb.Value <= at.ThresholdValue { // run the actions at.Execute(ub) } @@ -329,8 +332,8 @@ func (ub *UserBalance) countUnits(a *Action) { unitsCounter = &UnitsCounter{BalanceId: a.BalanceId, Direction: direction} ub.UnitCounters = append(ub.UnitCounters, unitsCounter) } - if a.BalanceId == MINUTES && a.MinuteBucket != nil { - unitsCounter.addMinutes(a.MinuteBucket.Seconds, a.MinuteBucket.DestinationId) + if a.BalanceId == MINUTES && a.Balance != nil { + unitsCounter.addMinutes(a.Balance.Value, a.Balance.DestinationId) } else { unitsCounter.Units += a.Units } @@ -346,7 +349,7 @@ func (ub *UserBalance) initMinuteCounters() { continue } for _, a := range acs { - if a.MinuteBucket != nil { + if a.Balance != nil { direction := at.Direction if direction == "" { direction = OUTBOUND @@ -355,11 +358,11 @@ func (ub *UserBalance) initMinuteCounters() { if !exists { uc = &UnitsCounter{BalanceId: MINUTES, Direction: direction} ucTempMap[direction] = uc - uc.MinuteBuckets = bucketsorter{} + uc.MinuteBalances = BalanceChain{} ub.UnitCounters = append(ub.UnitCounters, uc) } - uc.MinuteBuckets = append(uc.MinuteBuckets, a.MinuteBucket.Clone()) - uc.MinuteBuckets.Sort() + uc.MinuteBalances = append(uc.MinuteBalances, a.Balance.Clone()) + uc.MinuteBalances.Sort() } } } @@ -376,9 +379,9 @@ func (ub *UserBalance) CleanExpiredBalancesAndBuckets() { } ub.BalanceMap[key] = bm } - for i := 0; i < len(ub.MinuteBuckets); i++ { - if ub.MinuteBuckets[i].IsExpired() { - ub.MinuteBuckets = append(ub.MinuteBuckets[:i], ub.MinuteBuckets[i+1:]...) + for i := 0; i < len(ub.BalanceMap[MINUTES]); i++ { + if ub.BalanceMap[MINUTES][i].IsExpired() { + ub.BalanceMap[MINUTES] = append(ub.BalanceMap[MINUTES][:i], ub.BalanceMap[MINUTES][i+1:]...) } } } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 0b9f8ab53..af60f3a2c 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -35,7 +35,7 @@ func init() { func populateTestActionsForTriggers() { ats := []*Action{ &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}, - &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Weight: 20, Price: 1, Seconds: 10, DestinationId: "NAT"}}, + &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Weight: 20, SpecialPrice: 1, Value: 10, DestinationId: "NAT"}}, } storageGetter.SetActions("TEST_ACTIONS", ats) ats1 := []*Action{ @@ -96,9 +96,9 @@ func TestBalanceChainStoreRestore(t *testing.T) { } func TestUserBalanceStorageStoreRestore(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) ub1, err := storageGetter.GetUserBalance("other") if err != nil || !ub1.BalanceMap[CREDIT+OUTBOUND].Equal(rifsBalance.BalanceMap[CREDIT+OUTBOUND]) { @@ -108,9 +108,9 @@ func TestUserBalanceStorageStoreRestore(t *testing.T) { } func TestGetSecondsForPrefix(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 110.0 if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { @@ -118,11 +118,11 @@ func TestGetSecondsForPrefix(t *testing.T) { } } -func TestGetPricedSeconds(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Price: 10, Weight: 10, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Price: 1, Weight: 20, DestinationId: "RET"} +func TestGetSpecialPricedSeconds(t *testing.T) { + b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 21.0 if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight { @@ -131,24 +131,24 @@ func TestGetPricedSeconds(t *testing.T) { } func TestUserBalanceStorageStore(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) result, err := storageGetter.GetUserBalance(rifsBalance.Id) if err != nil || rifsBalance.Id != result.Id || - len(rifsBalance.MinuteBuckets) < 2 || len(result.MinuteBuckets) < 2 || - !(rifsBalance.MinuteBuckets[0].Equal(result.MinuteBuckets[0])) || - !(rifsBalance.MinuteBuckets[1].Equal(result.MinuteBuckets[1])) || + len(rifsBalance.BalanceMap[MINUTES]) < 2 || len(result.BalanceMap[MINUTES]) < 2 || + !(rifsBalance.BalanceMap[MINUTES][0].Equal(result.BalanceMap[MINUTES][0])) || + !(rifsBalance.BalanceMap[MINUTES][1].Equal(result.BalanceMap[MINUTES][1])) || !rifsBalance.BalanceMap[CREDIT+OUTBOUND].Equal(result.BalanceMap[CREDIT+OUTBOUND]) { t.Errorf("Expected %v was %v", rifsBalance, result) } } func TestDebitMoneyBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 6, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 15 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 15, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -156,9 +156,9 @@ func TestDebitMoneyBalance(t *testing.T) { } func TestDebitAllMoneyBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} rifsBalance.debitBalance(CREDIT, 21, false) result := rifsBalance.debitBalance(CREDIT, 0, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { @@ -167,9 +167,9 @@ func TestDebitAllMoneyBalance(t *testing.T) { } func TestDebitMoreMoneyBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 22, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -1 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", -1, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -177,9 +177,9 @@ func TestDebitMoreMoneyBalance(t *testing.T) { } func TestDebitNegativeMoneyBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, -15, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -187,120 +187,120 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } func TestDebitMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(6, "0723", false) - if b2.Seconds != 94 || err != nil { + if b2.Value != 94 || err != nil { t.Log(err) - t.Errorf("Expected %v was %v", 94, b2.Seconds) + t.Errorf("Expected %v was %v", 94, b2.Value) } } func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(105, "0723", false) - if b2.Seconds != 0 || b1.Seconds != 5 || err != nil { + if b2.Value != 0 || b1.Value != 5 || err != nil { t.Log(err) - t.Errorf("Expected %v was %v", 0, b2.Seconds) + t.Errorf("Expected %v was %v", 0, b2.Value) } } func TestDebitAllMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(110, "0723", false) - if b2.Seconds != 0 || b1.Seconds != 0 || err != nil { - t.Errorf("Expected %v was %v", 0, b2.Seconds) + if b2.Value != 0 || b1.Value != 0 || err != nil { + t.Errorf("Expected %v was %v", 0, b2.Value) } } func TestDebitMoreMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(115, "0723", false) - if b2.Seconds != 100 || b1.Seconds != 10 || err == nil { - t.Errorf("Expected %v was %v", 1000, b2.Seconds) + if b2.Value != 100 || b1.Value != 10 || err == nil { + t.Errorf("Expected %v was %v", 1000, b2.Value) } } -func TestDebitPriceMinuteBalance0(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} +func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(5, "0723", false) - if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { + if b2.Value != 95 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } -func TestDebitPriceAllMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} +func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(21, "0723", false) - if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { + if b2.Value != 79 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } -func TestDebitPriceMoreMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} +func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) - if b2.Seconds != 75 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { - t.Log(b2.Seconds) - t.Log(b1.Seconds) + if b2.Value != 75 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { + t.Log(b2.Value) + t.Log(b1.Value) t.Log(err) t.Errorf("Expected %v was %v", -4, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } -func TestDebitPriceMoreMinuteBalancePrepay(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} +func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) expected := 21.0 - if b2.Seconds != 100 || b1.Seconds != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { - t.Log(b2.Seconds) - t.Log(b1.Seconds) + if b2.Value != 100 || b1.Value != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { + t.Log(b2.Value) + t.Log(b1.Value) t.Log(err) t.Errorf("Expected %v was %v", expected, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } -func TestDebitPriceNegativeMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} +func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { + if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitNegativeMinuteBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { + if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitSMSBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 12, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 88 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 88, rifsBalance.BalanceMap[SMS+OUTBOUND]) @@ -308,9 +308,9 @@ func TestDebitSMSBalance(t *testing.T) { } func TestDebitAllSMSBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 100, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[SMS+OUTBOUND]) @@ -318,9 +318,9 @@ func TestDebitAllSMSBalance(t *testing.T) { } func TestDebitMoreSMSBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 110, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != -10 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", -10, rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value) @@ -328,112 +328,107 @@ func TestDebitMoreSMSBalance(t *testing.T) { } func TestDebitNegativeSMSBalance(t *testing.T) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, -15, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 115 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 115, rifsBalance.BalanceMap[SMS+OUTBOUND]) } } -func TestUserBalancedebitMinuteBucket(t *testing.T) { +func TestUserBalancedebitBalance(t *testing.T) { ub := &UserBalance{ - Id: "rif", - Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Id: "rif", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } - newMb := &MinuteBucket{Weight: 20, Price: 1, DestinationId: "NEW"} - ub.debitMinuteBucket(newMb) - if len(ub.MinuteBuckets) != 3 || ub.MinuteBuckets[2] != newMb { - t.Error("Error adding minute bucket!", len(ub.MinuteBuckets), ub.MinuteBuckets) + newMb := &Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NEW"} + ub.debitMinuteBalance(newMb) + if len(ub.BalanceMap[MINUTES]) != 3 || ub.BalanceMap[MINUTES][2] != newMb { + t.Error("Error adding minute bucket!", len(ub.BalanceMap[MINUTES]), ub.BalanceMap[MINUTES]) } } -func TestUserBalancedebitMinuteBucketExists(t *testing.T) { +func TestUserBalancedebitBalanceExists(t *testing.T) { ub := &UserBalance{ - Id: "rif", - Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Id: "rif", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES: BalanceChain{&Balance{Value: 15, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } - newMb := &MinuteBucket{Seconds: -10, Weight: 20, Price: 1, DestinationId: "NAT"} - ub.debitMinuteBucket(newMb) - if len(ub.MinuteBuckets) != 2 || ub.MinuteBuckets[0].Seconds != 25 { + newMb := &Balance{Value: -10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"} + ub.debitMinuteBalance(newMb) + if len(ub.BalanceMap[MINUTES]) != 2 || ub.BalanceMap[MINUTES][0].Value != 25 { t.Error("Error adding minute bucket!") } } func TestUserBalanceAddMinuteNil(t *testing.T) { ub := &UserBalance{ - Id: "rif", - Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + Id: "rif", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } - ub.debitMinuteBucket(nil) - if len(ub.MinuteBuckets) != 2 { + ub.debitMinuteBalance(nil) + if len(ub.BalanceMap[MINUTES]) != 2 { t.Error("Error adding minute bucket!") } } func TestUserBalanceAddMinutBucketEmpty(t *testing.T) { - mb1 := &MinuteBucket{Seconds: -10, DestinationId: "NAT"} - mb2 := &MinuteBucket{Seconds: -10, DestinationId: "NAT"} - mb3 := &MinuteBucket{Seconds: -10, DestinationId: "OTHER"} + mb1 := &Balance{Value: -10, DestinationId: "NAT"} + mb2 := &Balance{Value: -10, DestinationId: "NAT"} + mb3 := &Balance{Value: -10, DestinationId: "OTHER"} ub := &UserBalance{} - ub.debitMinuteBucket(mb1) - if len(ub.MinuteBuckets) != 1 { - t.Error("Error adding minute bucket: ", ub.MinuteBuckets) + ub.debitMinuteBalance(mb1) + if len(ub.BalanceMap[MINUTES]) != 1 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) } - ub.debitMinuteBucket(mb2) - if len(ub.MinuteBuckets) != 1 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error adding minute bucket: ", ub.MinuteBuckets) + ub.debitMinuteBalance(mb2) + if len(ub.BalanceMap[MINUTES]) != 1 || ub.BalanceMap[MINUTES][0].Value != 20 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) } - ub.debitMinuteBucket(mb3) - if len(ub.MinuteBuckets) != 2 { - t.Error("Error adding minute bucket: ", ub.MinuteBuckets) + ub.debitMinuteBalance(mb3) + if len(ub.BalanceMap[MINUTES]) != 2 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) } } func TestUserBalanceExecuteTriggeredActions(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } // are set to executed ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } // we can reset them ub.resetActionTriggers(nil) ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.MinuteBuckets[0].Seconds != 30 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.BalanceMap[MINUTES][0].Value != 30 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } } func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: TRIGGER_MIN_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } } @@ -446,8 +441,6 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { } ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { - t.Log(ub.BalanceMap[CREDIT+OUTBOUND][0]) - t.Log(ub.BalanceMap[CREDIT+OUTBOUND][1]) t.Error("Error executing triggered actions in order", ub.BalanceMap[CREDIT+OUTBOUND]) } } @@ -458,17 +451,16 @@ func TestCleanExpired(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{ &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, - &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}}, - MinuteBuckets: []*MinuteBucket{ - &MinuteBucket{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, - &MinuteBucket{ExpirationDate: time.Now().Add(10 * time.Second)}, - }, + &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}, MINUTES: BalanceChain{ + &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, + &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, + }}, } ub.CleanExpiredBalancesAndBuckets() if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 2 { t.Error("Error cleaning expired balances!") } - if len(ub.MinuteBuckets) != 1 { + if len(ub.BalanceMap[MINUTES]) != 1 { t.Error("Error cleaning expired minute buckets!") } } @@ -521,10 +513,10 @@ func TestUserBalanceUnitCountingOutboundInbound(t *testing.T) { func BenchmarkGetSecondForPrefix(b *testing.B) { b.StopTimer() - b1 := &MinuteBucket{Seconds: 10, Price: 10, Weight: 10, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Price: 1, Weight: 20, DestinationId: "RET"} + b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + ub1 := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} b.StartTimer() for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") @@ -532,9 +524,9 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { } func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { storageGetter.SetUserBalance(rifsBalance) storageGetter.GetUserBalance(rifsBalance.Id) @@ -542,9 +534,9 @@ func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { } func BenchmarkGetSecondsForPrefix(b *testing.B) { - b1 := &MinuteBucket{Seconds: 10, Weight: 10, DestinationId: "NAT"} - b2 := &MinuteBucket{Seconds: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") } From f3f9d03c80364fffe700fdced5dcb49aa112ae7b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 5 Sep 2013 20:17:14 +0300 Subject: [PATCH 10/39] before switching to MINUTES+OUTBOUND --- engine/action.go | 6 ++-- engine/actions_test.go | 27 +++++++++-------- engine/balances.go | 15 +++++----- ...inute_buckets_test.go => balances_test.go} | 14 ++++----- engine/storage_sql.go | 2 +- engine/tpimporter_csv.go | 12 ++++---- engine/userbalance.go | 14 ++++----- engine/userbalance_test.go | 30 +++++++++---------- 8 files changed, 61 insertions(+), 59 deletions(-) rename engine/{minute_buckets_test.go => balances_test.go} (86%) diff --git a/engine/action.go b/engine/action.go index 1dcf33baf..744d9dea2 100644 --- a/engine/action.go +++ b/engine/action.go @@ -35,9 +35,9 @@ type Action struct { ExpirationString string Weight float64 Balance *Balance - DestinationTag, RateType string // From here for import/load purposes only - ExpirationDate time.Time - Units, RateValue, MinutesWeight float64 + destinationTag, rateType string // From here for import/load purposes only + expirationDate time.Time + units, rateValue, minutesWeight float64 } const ( diff --git a/engine/actions_test.go b/engine/actions_test.go index 47d27bae2..69b90da20 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -605,8 +605,9 @@ func TestActionResetPrepaid(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - len(ub.BalanceMap[MINUTES]) != 0 || + ub.BalanceMap[MINUTES][0].Value != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { + t.Log(ub.BalanceMap) t.Error("Reset prepaid action failed!") } } @@ -623,7 +624,7 @@ func TestActionResetPostpaid(t *testing.T) { if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - len(ub.BalanceMap[MINUTES]) != 0 || + ub.BalanceMap[MINUTES][0].Value != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { t.Error("Reset postpaid action failed!") } @@ -637,7 +638,7 @@ func TestActionTopupResetCredit(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} + a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}} topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 10 || @@ -650,9 +651,11 @@ func TestActionTopupResetCredit(t *testing.T) { func TestActionTopupResetMinutes(t *testing.T) { ub := &UserBalance{ - Id: "TEST_UB", - Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + Id: "TEST_UB", + Type: UB_TYPE_PREPAID, + BalanceMap: map[string]BalanceChain{ + CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, + MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -664,7 +667,7 @@ func TestActionTopupResetMinutes(t *testing.T) { len(ub.UnitCounters) != 1 || len(ub.BalanceMap[MINUTES]) != 1 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup reset minutes action failed!", ub.BalanceMap[MINUTES][0]) + t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap) } } @@ -676,7 +679,7 @@ func TestActionTopupCredit(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} + a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 110 || @@ -715,7 +718,7 @@ func TestActionDebitCredit(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} + a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 90 || @@ -889,13 +892,13 @@ func TestActionTimingLogging(t *testing.T) { } func TestActionMakeNegative(t *testing.T) { - a := &Action{Units: 10} + a := &Action{Balance: &Balance{Value: 10}} genericMakeNegative(a) - if a.Units > 0 { + if a.Balance.Value > 0 { t.Error("Failed to make negative: ", a) } genericMakeNegative(a) - if a.Units > 0 { + if a.Balance.Value > 0 { t.Error("Failed to preserve negative: ", a) } } diff --git a/engine/balances.go b/engine/balances.go index 036113c42..20de21adb 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -40,7 +40,7 @@ type Balance struct { func (b *Balance) Equal(o *Balance) bool { return b.ExpirationDate.Equal(o.ExpirationDate) && b.Weight == o.Weight && - b.SpecialPrice == b.SpecialPrice && + b.SpecialPrice == o.SpecialPrice && b.DestinationId == o.DestinationId } @@ -50,12 +50,13 @@ func (b *Balance) IsExpired() bool { func (b *Balance) Clone() *Balance { return &Balance{ - Id: b.Id, - Value: b.Value, - SpecialPrice: b.SpecialPrice, - DestinationId: b.DestinationId, - ExpirationDate: b.ExpirationDate, - Weight: b.Weight, + Id: b.Id, + Value: b.Value, + SpecialPrice: b.SpecialPrice, + SpecialPriceType: b.SpecialPriceType, + DestinationId: b.DestinationId, + ExpirationDate: b.ExpirationDate, + Weight: b.Weight, } } diff --git a/engine/minute_buckets_test.go b/engine/balances_test.go similarity index 86% rename from engine/minute_buckets_test.go rename to engine/balances_test.go index b218d2334..24db6b1a4 100644 --- a/engine/minute_buckets_test.go +++ b/engine/balances_test.go @@ -23,7 +23,7 @@ import ( "testing" ) -func TestMinutBucketSortWeight(t *testing.T) { +func TestBalanceSortWeight(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} mb2 := &Balance{Weight: 2, precision: 1, SpecialPrice: 1} var bs BalanceChain @@ -34,7 +34,7 @@ func TestMinutBucketSortWeight(t *testing.T) { } } -func TestMinutBucketSortPrecision(t *testing.T) { +func TestBalanceSortPrecision(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} var bs BalanceChain @@ -45,7 +45,7 @@ func TestMinutBucketSortPrecision(t *testing.T) { } } -func TestMinutBucketSortSpecialPrice(t *testing.T) { +func TestBalanceSortSpecialPrice(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2} var bs BalanceChain @@ -56,19 +56,19 @@ func TestMinutBucketSortSpecialPrice(t *testing.T) { } } -func TestMinutBucketEqual(t *testing.T) { +func TestBalanceEqual(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} mb3 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} if !mb1.Equal(mb2) || mb2.Equal(mb3) { - t.Error("Equal failure!", mb1, mb2, mb3) + t.Error("Equal failure!", mb1 == mb2, mb3) } } -func TestMinutBucketClone(t *testing.T) { +func TestBalanceClone(t *testing.T) { mb1 := &Balance{Value: 1, Weight: 2, SpecialPrice: 3, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "5"} mb2 := mb1.Clone() if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) { - t.Error("Cloning failure: ", mb1, mb2) + t.Errorf("Cloning failure: \n%v\n%v", mb1, mb2) } } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 2668849d8..58bbc2faf 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -541,7 +541,7 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err } qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s','%s',%f,%f,%f)", tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Balance.Value, act.ExpirationString, - act.DestinationTag, act.RateType, act.RateValue, act.MinutesWeight, act.Weight) + act.destinationTag, act.rateType, act.rateValue, act.minutesWeight, act.Weight) i++ } } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 56824abd4..09bd764ad 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -317,12 +317,12 @@ func (self *TPCSVImporter) importActions(fn string) error { ActionType: actionType, BalanceId: balanceType, Direction: direction, - Units: units, - ExpirationDate: expiryTime, - DestinationTag: destTag, - RateType: rateType, - RateValue: rateValue, - MinutesWeight: minutesWeight, + units: units, + expirationDate: expiryTime, + destinationTag: destTag, + rateType: rateType, + rateValue: rateValue, + minutesWeight: minutesWeight, Weight: weight, } if err := self.StorDb.SetTPActions(self.TPid, map[string][]*Action{actId: []*Action{act}}); err != nil { diff --git a/engine/userbalance.go b/engine/userbalance.go index 177bdd53b..bed8ac76a 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -182,23 +182,21 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count // Debits some amount of user's specified balance adding the balance if it does not exists. // Returns the remaining credit in user's balance. func (ub *UserBalance) debitBalanceAction(a *Action) float64 { - newBalance := &Balance{ - Id: utils.GenUUID(), - ExpirationDate: a.ExpirationDate, - } + newBalance := &Balance{Id: utils.GenUUID()} if a.Balance != nil { + newBalance.ExpirationDate = a.Balance.ExpirationDate newBalance.Weight = a.Balance.Weight } found := false id := a.BalanceId + a.Direction for _, b := range ub.BalanceMap[id] { if b.Equal(newBalance) { - b.Value -= a.Units + b.Value -= a.Balance.Value found = true } } if !found { - newBalance.Value -= a.Units + newBalance.Value -= a.Balance.Value ub.BalanceMap[id] = append(ub.BalanceMap[id], newBalance) } return ub.BalanceMap[a.BalanceId+OUTBOUND].GetTotalValue() @@ -209,7 +207,7 @@ Debits some amount of user's specified balance. Returns the remaining credit in */ func (ub *UserBalance) debitBalance(balanceId string, amount float64, count bool) float64 { if count { - ub.countUnits(&Action{BalanceId: balanceId, Direction: OUTBOUND, Units: amount}) + ub.countUnits(&Action{BalanceId: balanceId, Direction: OUTBOUND, Balance: &Balance{Value: amount}}) } ub.BalanceMap[balanceId+OUTBOUND].Debit(amount) return ub.BalanceMap[balanceId+OUTBOUND].GetTotalValue() @@ -335,7 +333,7 @@ func (ub *UserBalance) countUnits(a *Action) { if a.BalanceId == MINUTES && a.Balance != nil { unitsCounter.addMinutes(a.Balance.Value, a.Balance.DestinationId) } else { - unitsCounter.Units += a.Units + unitsCounter.Units += a.Balance.Value } ub.executeActionTriggers(nil) } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index af60f3a2c..421354a1a 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -34,12 +34,12 @@ func init() { func populateTestActionsForTriggers() { ats := []*Action{ - &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}, + &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}, &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Weight: 20, SpecialPrice: 1, Value: 10, DestinationId: "NAT"}}, } storageGetter.SetActions("TEST_ACTIONS", ats) ats1 := []*Action{ - &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10, Weight: 20}, + &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}, Weight: 20}, &Action{ActionType: "*reset_prepaid", Weight: 10}, } storageGetter.SetActions("TEST_ACTIONS_ORDER", ats1) @@ -402,18 +402,18 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS"}}, } - ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) + ub.countUnits(&Action{BalanceId: CREDIT, Balance: &Balance{Value: 1}}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } // are set to executed - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 1}}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } // we can reset them ub.resetActionTriggers(nil) - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.BalanceMap[MINUTES][0].Value != 30 { t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } @@ -426,7 +426,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: TRIGGER_MIN_COUNTER, ActionsId: "TEST_ACTIONS"}}, } - ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) + ub.countUnits(&Action{BalanceId: CREDIT, Balance: &Balance{Value: 1}}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) } @@ -439,7 +439,7 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS_ORDER"}}, } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 1}}) if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { t.Error("Error executing triggered actions in order", ub.BalanceMap[CREDIT+OUTBOUND]) } @@ -467,11 +467,11 @@ func TestCleanExpired(t *testing.T) { func TestUserBalanceUnitCounting(t *testing.T) { ub := &UserBalance{} - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 10 { t.Error("Error counting units") } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 20 { t.Error("Error counting units") } @@ -479,15 +479,15 @@ func TestUserBalanceUnitCounting(t *testing.T) { func TestUserBalanceUnitCountingOutbound(t *testing.T) { ub := &UserBalance{} - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 10 { t.Error("Error counting units") } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 20 { t.Error("Error counting units") } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 30 { t.Error("Error counting units") } @@ -495,15 +495,15 @@ func TestUserBalanceUnitCountingOutbound(t *testing.T) { func TestUserBalanceUnitCountingOutboundInbound(t *testing.T) { ub := &UserBalance{} - ub.countUnits(&Action{BalanceId: CREDIT, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 10 { t.Error("Error counting units") } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 1 && ub.UnitCounters[0].BalanceId != CREDIT || ub.UnitCounters[0].Units != 20 { t.Error("Error counting units") } - ub.countUnits(&Action{BalanceId: CREDIT, Direction: INBOUND, Units: 10}) + ub.countUnits(&Action{BalanceId: CREDIT, Direction: INBOUND, Balance: &Balance{Value: 10}}) if len(ub.UnitCounters) != 2 && ub.UnitCounters[1].BalanceId != CREDIT || ub.UnitCounters[0].Units != 20 || ub.UnitCounters[1].Units != 10 { t.Error("Error counting units") } From 2f6b1f3f84ce0df72d0190c0b3a367f3fc91e66f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 5 Sep 2013 23:28:04 +0300 Subject: [PATCH 11/39] tests passing with new balance struct --- apier/v1/apier.go | 2 +- engine/action.go | 8 +-- engine/actions_test.go | 86 ++++++++++++++++-------------- engine/calldesc_test.go | 19 ++++--- engine/storage_sql.go | 2 +- engine/tpimporter_csv.go | 12 ++--- engine/units_counter.go | 8 +-- engine/userbalance.go | 24 +++++---- engine/userbalance_test.go | 106 ++++++++++++++++++------------------- 9 files changed, 141 insertions(+), 126 deletions(-) diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 455fef218..a304c24ee 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -109,7 +109,7 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { attr.Direction = engine.OUTBOUND } - at.SetActions(engine.Actions{&engine.Action{ActionType: engine.TOPUP, BalanceId: attr.BalanceId, Direction: attr.Direction, Units: attr.Value}}) + at.SetActions(engine.Actions{&engine.Action{ActionType: engine.TOPUP, BalanceId: attr.BalanceId, Direction: attr.Direction, Balance: &engine.Balance{Value: attr.Value}}}) if err := at.Execute(); err != nil { *reply = err.Error() diff --git a/engine/action.go b/engine/action.go index 744d9dea2..ed48e6aab 100644 --- a/engine/action.go +++ b/engine/action.go @@ -35,9 +35,9 @@ type Action struct { ExpirationString string Weight float64 Balance *Balance - destinationTag, rateType string // From here for import/load purposes only - expirationDate time.Time - units, rateValue, minutesWeight float64 + DestinationTag, RateType string // From here for import/load purposes only + ExpirationDate time.Time + Units, RateValue, MinutesWeight float64 } const ( @@ -138,7 +138,7 @@ func resetCounterAction(ub *UserBalance, a *Action) (err error) { ub.UnitCounters = append(ub.UnitCounters, uc) } if a.BalanceId == MINUTES { - uc.initMinuteBuckets(ub.ActionTriggers) + uc.initMinuteBalances(ub.ActionTriggers) } else { uc.Units = 0 } diff --git a/engine/actions_test.go b/engine/actions_test.go index 69b90da20..f9e121f52 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -529,7 +529,7 @@ func TestActionTriggerPriotityList(t *testing.T) { func TestActionResetTriggres(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -555,7 +555,7 @@ func TestActionResetTriggresExecutesThem(t *testing.T) { func TestActionResetTriggresActionFilter(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -569,7 +569,7 @@ func TestActionSetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -583,7 +583,7 @@ func TestActionSetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -597,7 +597,7 @@ func TestActionResetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -605,7 +605,7 @@ func TestActionResetPrepaid(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - ub.BalanceMap[MINUTES][0].Value != 0 || + ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { t.Log(ub.BalanceMap) t.Error("Reset prepaid action failed!") @@ -616,7 +616,7 @@ func TestActionResetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -624,7 +624,7 @@ func TestActionResetPostpaid(t *testing.T) { if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || - ub.BalanceMap[MINUTES][0].Value != 0 || + ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { t.Error("Reset postpaid action failed!") } @@ -634,7 +634,7 @@ func TestActionTopupResetCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -643,7 +643,7 @@ func TestActionTopupResetCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 10 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Errorf("Topup reset action failed: %#v", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -654,20 +654,20 @@ func TestActionTopupResetMinutes(t *testing.T) { Id: "TEST_UB", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{ - CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, - MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, + MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[MINUTES][0].Value != 5 || + ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue() != 5 || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 1 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap) + t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue()) } } @@ -675,7 +675,7 @@ func TestActionTopupCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -684,7 +684,7 @@ func TestActionTopupCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 110 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Error("Topup action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -694,19 +694,19 @@ func TestActionTopupMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[MINUTES][0].Value != 15 || + ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 15 || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup minutes action failed!", ub.BalanceMap[MINUTES][0]) + t.Error("Topup minutes action failed!", ub.BalanceMap[MINUTES+OUTBOUND][0]) } } @@ -714,7 +714,7 @@ func TestActionDebitCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -723,7 +723,7 @@ func TestActionDebitCredit(t *testing.T) { if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 90 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { t.Error("Debit action failed!", ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue()) } @@ -733,27 +733,31 @@ func TestActionDebitMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[MINUTES][0].Value != 5 || + ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 5 || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Debit minutes action failed!", ub.BalanceMap[MINUTES][0]) + t.Error("Debit minutes action failed!", ub.BalanceMap[MINUTES+OUTBOUND][0]) } } func TestActionResetAllCounters(t *testing.T) { ub := &UserBalance{ - Id: "TEST_UB", - Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + Id: "TEST_UB", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]BalanceChain{ + CREDIT: BalanceChain{&Balance{Value: 100}}, + MINUTES: BalanceChain{ + &Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, + &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -764,22 +768,24 @@ func TestActionResetAllCounters(t *testing.T) { len(ub.UnitCounters[0].MinuteBalances) != 1 || len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { - t.Error("Reset counters action failed!") + t.Error("Reset counters action failed: ", ub.BalanceMap[MINUTES]) } if len(ub.UnitCounters) < 1 { t.FailNow() } mb := ub.UnitCounters[0].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 10 || mb.DestinationId != "NAT" { - t.Errorf("Minute bucked cloned incorrectly: %v!", mb) + if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { + t.Errorf("Balanxce cloned incorrectly: %v!", mb) } } func TestActionResetCounterMinutes(t *testing.T) { ub := &UserBalance{ - Id: "TEST_UB", - Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + Id: "TEST_UB", + Type: UB_TYPE_POSTPAID, + BalanceMap: map[string]BalanceChain{ + CREDIT: BalanceChain{&Balance{Value: 100}}, + MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -791,13 +797,15 @@ func TestActionResetCounterMinutes(t *testing.T) { len(ub.UnitCounters[1].MinuteBalances) != 1 || len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { + t.Logf("%#v", ub.UnitCounters[1].MinuteBalances[0]) + t.Logf("%#v", ub.UnitCounters[1].MinuteBalances[1]) t.Error("Reset counters action failed!", ub.UnitCounters[1].MinuteBalances) } if len(ub.UnitCounters) < 2 || len(ub.UnitCounters[1].MinuteBalances) < 1 { t.FailNow() } mb := ub.UnitCounters[1].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 10 || mb.DestinationId != "NAT" { + if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { t.Errorf("Minute bucked cloned incorrectly: %v!", mb) } } @@ -806,7 +814,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -815,7 +823,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { if ub.Type != UB_TYPE_POSTPAID || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 2 || - len(ub.BalanceMap[MINUTES]) != 2 || + len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true { t.Error("Reset counters action failed!", ub.UnitCounters) } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 647dbd84b..936c5e0b5 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -38,18 +38,21 @@ func populateDB() { minu := &UserBalance{ Id: "*out:vdf:minu", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}, MINUTES: BalanceChain{ - &Balance{Value: 200, DestinationId: "NAT", Weight: 10}, - &Balance{Value: 100, DestinationId: "RET", Weight: 20}, - }}, + BalanceMap: map[string]BalanceChain{ + CREDIT: BalanceChain{&Balance{Value: 0}}, + MINUTES + OUTBOUND: BalanceChain{ + &Balance{Value: 200, DestinationId: "NAT", Weight: 10}, + &Balance{Value: 100, DestinationId: "RET", Weight: 20}, + }}, } broker := &UserBalance{ Id: "*out:vdf:broker", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{ - &Balance{Value: 20, DestinationId: "NAT", Weight: 10, SpecialPrice: 1}, - &Balance{Value: 100, DestinationId: "RET", Weight: 20}, - }}, + BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{ + &Balance{Value: 20, DestinationId: "NAT", Weight: 10, SpecialPrice: 1}, + &Balance{Value: 100, DestinationId: "RET", Weight: 20}, + }}, } if storageGetter != nil { storageGetter.(Storage).Flush() diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 58bbc2faf..2668849d8 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -541,7 +541,7 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err } qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s','%s',%f,%f,%f)", tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Balance.Value, act.ExpirationString, - act.destinationTag, act.rateType, act.rateValue, act.minutesWeight, act.Weight) + act.DestinationTag, act.RateType, act.RateValue, act.MinutesWeight, act.Weight) i++ } } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 09bd764ad..56824abd4 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -317,12 +317,12 @@ func (self *TPCSVImporter) importActions(fn string) error { ActionType: actionType, BalanceId: balanceType, Direction: direction, - units: units, - expirationDate: expiryTime, - destinationTag: destTag, - rateType: rateType, - rateValue: rateValue, - minutesWeight: minutesWeight, + Units: units, + ExpirationDate: expiryTime, + DestinationTag: destTag, + RateType: rateType, + RateValue: rateValue, + MinutesWeight: minutesWeight, Weight: weight, } if err := self.StorDb.SetTPActions(self.TPid, map[string][]*Action{actId: []*Action{act}}); err != nil { diff --git a/engine/units_counter.go b/engine/units_counter.go index fed75af11..78d573b68 100644 --- a/engine/units_counter.go +++ b/engine/units_counter.go @@ -30,7 +30,7 @@ type UnitsCounter struct { MinuteBalances BalanceChain } -func (uc *UnitsCounter) initMinuteBuckets(ats []*ActionTrigger) { +func (uc *UnitsCounter) initMinuteBalances(ats []*ActionTrigger) { uc.MinuteBalances = make(BalanceChain, 0) for _, at := range ats { acs, err := storageGetter.GetActions(at.ActionsId) @@ -38,8 +38,10 @@ func (uc *UnitsCounter) initMinuteBuckets(ats []*ActionTrigger) { continue } for _, a := range acs { - if a.Balance != nil { - uc.MinuteBalances = append(uc.MinuteBalances, a.Balance.Clone()) + if a.BalanceId == MINUTES && a.Balance != nil { + b := a.Balance.Clone() + b.Value = 0 + uc.MinuteBalances = append(uc.MinuteBalances, b) } } } diff --git a/engine/userbalance.go b/engine/userbalance.go index bed8ac76a..3c534d110 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -72,11 +72,11 @@ Returns user's available minutes for the specified destination */ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, balances BalanceChain) { credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() - if len(ub.BalanceMap[MINUTES]) == 0 { + if len(ub.BalanceMap[MINUTES+OUTBOUND]) == 0 { // Logger.Debug("There are no minute buckets to check for user: ", ub.Id) return } - for _, b := range ub.BalanceMap[MINUTES] { + for _, b := range ub.BalanceMap[MINUTES+OUTBOUND] { if b.IsExpired() { continue } @@ -109,7 +109,7 @@ func (ub *UserBalance) debitMinuteBalance(newMb *Balance) error { ub.BalanceMap = make(map[string]BalanceChain, 0) } found := false - for _, mb := range ub.BalanceMap[MINUTES] { + for _, mb := range ub.BalanceMap[MINUTES+OUTBOUND] { if mb.IsExpired() { continue } @@ -123,7 +123,7 @@ func (ub *UserBalance) debitMinuteBalance(newMb *Balance) error { // then we add it to the list if !found && newMb.Value <= 0 { newMb.Value = -newMb.Value - ub.BalanceMap[MINUTES] = append(ub.BalanceMap[MINUTES], newMb) + ub.BalanceMap[MINUTES+OUTBOUND] = append(ub.BalanceMap[MINUTES+OUTBOUND], newMb) } return nil } @@ -259,8 +259,8 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { } } else { // BALANCE for _, b := range ub.BalanceMap[at.BalanceId] { - if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety - for _, mb := range ub.BalanceMap[MINUTES] { + if at.BalanceId == MINUTES { + for _, mb := range ub.BalanceMap[MINUTES+OUTBOUND] { if strings.Contains(at.ThresholdType, "*max") { if mb.DestinationId == at.DestinationId && mb.Value >= at.ThresholdValue { // run the actions @@ -347,7 +347,7 @@ func (ub *UserBalance) initMinuteCounters() { continue } for _, a := range acs { - if a.Balance != nil { + if a.BalanceId == MINUTES && a.Balance != nil { direction := at.Direction if direction == "" { direction = OUTBOUND @@ -359,7 +359,9 @@ func (ub *UserBalance) initMinuteCounters() { uc.MinuteBalances = BalanceChain{} ub.UnitCounters = append(ub.UnitCounters, uc) } - uc.MinuteBalances = append(uc.MinuteBalances, a.Balance.Clone()) + b := a.Balance.Clone() + b.Value = 0 + uc.MinuteBalances = append(uc.MinuteBalances, b) uc.MinuteBalances.Sort() } } @@ -377,9 +379,9 @@ func (ub *UserBalance) CleanExpiredBalancesAndBuckets() { } ub.BalanceMap[key] = bm } - for i := 0; i < len(ub.BalanceMap[MINUTES]); i++ { - if ub.BalanceMap[MINUTES][i].IsExpired() { - ub.BalanceMap[MINUTES] = append(ub.BalanceMap[MINUTES][:i], ub.BalanceMap[MINUTES][i+1:]...) + for i := 0; i < len(ub.BalanceMap[MINUTES+OUTBOUND]); i++ { + if ub.BalanceMap[MINUTES+OUTBOUND][i].IsExpired() { + ub.BalanceMap[MINUTES+OUTBOUND] = append(ub.BalanceMap[MINUTES+OUTBOUND][:i], ub.BalanceMap[MINUTES+OUTBOUND][i+1:]...) } } } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 421354a1a..38d1de85c 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -98,7 +98,7 @@ func TestBalanceChainStoreRestore(t *testing.T) { func TestUserBalanceStorageStoreRestore(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) ub1, err := storageGetter.GetUserBalance("other") if err != nil || !ub1.BalanceMap[CREDIT+OUTBOUND].Equal(rifsBalance.BalanceMap[CREDIT+OUTBOUND]) { @@ -110,7 +110,7 @@ func TestUserBalanceStorageStoreRestore(t *testing.T) { func TestGetSecondsForPrefix(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 110.0 if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { @@ -122,7 +122,7 @@ func TestGetSpecialPricedSeconds(t *testing.T) { b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 21.0 if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight { @@ -133,13 +133,13 @@ func TestGetSpecialPricedSeconds(t *testing.T) { func TestUserBalanceStorageStore(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) result, err := storageGetter.GetUserBalance(rifsBalance.Id) if err != nil || rifsBalance.Id != result.Id || - len(rifsBalance.BalanceMap[MINUTES]) < 2 || len(result.BalanceMap[MINUTES]) < 2 || - !(rifsBalance.BalanceMap[MINUTES][0].Equal(result.BalanceMap[MINUTES][0])) || - !(rifsBalance.BalanceMap[MINUTES][1].Equal(result.BalanceMap[MINUTES][1])) || + len(rifsBalance.BalanceMap[MINUTES+OUTBOUND]) < 2 || len(result.BalanceMap[MINUTES+OUTBOUND]) < 2 || + !(rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Equal(result.BalanceMap[MINUTES+OUTBOUND][0])) || + !(rifsBalance.BalanceMap[MINUTES+OUTBOUND][1].Equal(result.BalanceMap[MINUTES+OUTBOUND][1])) || !rifsBalance.BalanceMap[CREDIT+OUTBOUND].Equal(result.BalanceMap[CREDIT+OUTBOUND]) { t.Errorf("Expected %v was %v", rifsBalance, result) } @@ -148,7 +148,7 @@ func TestUserBalanceStorageStore(t *testing.T) { func TestDebitMoneyBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 6, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 15 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 15, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -158,7 +158,7 @@ func TestDebitMoneyBalance(t *testing.T) { func TestDebitAllMoneyBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} rifsBalance.debitBalance(CREDIT, 21, false) result := rifsBalance.debitBalance(CREDIT, 0, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { @@ -169,7 +169,7 @@ func TestDebitAllMoneyBalance(t *testing.T) { func TestDebitMoreMoneyBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 22, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -1 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", -1, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -179,7 +179,7 @@ func TestDebitMoreMoneyBalance(t *testing.T) { func TestDebitNegativeMoneyBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, -15, false) if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -189,7 +189,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { func TestDebitMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(6, "0723", false) if b2.Value != 94 || err != nil { t.Log(err) @@ -200,7 +200,7 @@ func TestDebitMinuteBalance(t *testing.T) { func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(105, "0723", false) if b2.Value != 0 || b1.Value != 5 || err != nil { t.Log(err) @@ -211,7 +211,7 @@ func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { func TestDebitAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(110, "0723", false) if b2.Value != 0 || b1.Value != 0 || err != nil { t.Errorf("Expected %v was %v", 0, b2.Value) @@ -221,7 +221,7 @@ func TestDebitAllMinuteBalance(t *testing.T) { func TestDebitMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(115, "0723", false) if b2.Value != 100 || b1.Value != 10 || err == nil { t.Errorf("Expected %v was %v", 1000, b2.Value) @@ -231,7 +231,7 @@ func TestDebitMoreMinuteBalance(t *testing.T) { func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(5, "0723", false) if b2.Value != 95 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -241,7 +241,7 @@ func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(21, "0723", false) if b2.Value != 79 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -251,7 +251,7 @@ func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) if b2.Value != 75 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { t.Log(b2.Value) @@ -264,7 +264,7 @@ func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) expected := 21.0 if b2.Value != 100 || b1.Value != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { @@ -278,7 +278,7 @@ func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { t.Log(b1, b2, err) @@ -289,7 +289,7 @@ func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { func TestDebitNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { t.Log(b1, b2, err) @@ -300,7 +300,7 @@ func TestDebitNegativeMinuteBalance(t *testing.T) { func TestDebitSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 12, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 88 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 88, rifsBalance.BalanceMap[SMS+OUTBOUND]) @@ -310,7 +310,7 @@ func TestDebitSMSBalance(t *testing.T) { func TestDebitAllSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 100, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[SMS+OUTBOUND]) @@ -320,7 +320,7 @@ func TestDebitAllSMSBalance(t *testing.T) { func TestDebitMoreSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 110, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != -10 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", -10, rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value) @@ -330,7 +330,7 @@ func TestDebitMoreSMSBalance(t *testing.T) { func TestDebitNegativeSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, -15, false) if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 115 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 115, rifsBalance.BalanceMap[SMS+OUTBOUND]) @@ -341,12 +341,12 @@ func TestUserBalancedebitBalance(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } newMb := &Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NEW"} ub.debitMinuteBalance(newMb) - if len(ub.BalanceMap[MINUTES]) != 3 || ub.BalanceMap[MINUTES][2] != newMb { - t.Error("Error adding minute bucket!", len(ub.BalanceMap[MINUTES]), ub.BalanceMap[MINUTES]) + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 3 || ub.BalanceMap[MINUTES+OUTBOUND][2] != newMb { + t.Error("Error adding minute bucket!", len(ub.BalanceMap[MINUTES+OUTBOUND]), ub.BalanceMap[MINUTES+OUTBOUND]) } } @@ -355,11 +355,11 @@ func TestUserBalancedebitBalanceExists(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES: BalanceChain{&Balance{Value: 15, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 15, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } newMb := &Balance{Value: -10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"} ub.debitMinuteBalance(newMb) - if len(ub.BalanceMap[MINUTES]) != 2 || ub.BalanceMap[MINUTES][0].Value != 25 { + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 25 { t.Error("Error adding minute bucket!") } } @@ -368,10 +368,10 @@ func TestUserBalanceAddMinuteNil(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } ub.debitMinuteBalance(nil) - if len(ub.BalanceMap[MINUTES]) != 2 { + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 { t.Error("Error adding minute bucket!") } } @@ -382,53 +382,53 @@ func TestUserBalanceAddMinutBucketEmpty(t *testing.T) { mb3 := &Balance{Value: -10, DestinationId: "OTHER"} ub := &UserBalance{} ub.debitMinuteBalance(mb1) - if len(ub.BalanceMap[MINUTES]) != 1 { - t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } ub.debitMinuteBalance(mb2) - if len(ub.BalanceMap[MINUTES]) != 1 || ub.BalanceMap[MINUTES][0].Value != 20 { - t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } ub.debitMinuteBalance(mb3) - if len(ub.BalanceMap[MINUTES]) != 2 { - t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES]) + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 { + t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } } func TestUserBalanceExecuteTriggeredActions(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Balance: &Balance{Value: 1}}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES+OUTBOUND][0].Value) } // are set to executed ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 1}}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES+OUTBOUND][0].Value) } // we can reset them ub.resetActionTriggers(nil) ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.BalanceMap[MINUTES][0].Value != 30 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 30 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES+OUTBOUND][0].Value) } } func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: TRIGGER_MIN_COUNTER, ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Balance: &Balance{Value: 1}}) - if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES][0].Value != 20 { - t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES][0].Value) + if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { + t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.BalanceMap[MINUTES+OUTBOUND][0].Value) } } @@ -451,7 +451,7 @@ func TestCleanExpired(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{ &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, - &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}, MINUTES: BalanceChain{ + &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}, MINUTES + OUTBOUND: BalanceChain{ &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, }}, @@ -460,7 +460,7 @@ func TestCleanExpired(t *testing.T) { if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 2 { t.Error("Error cleaning expired balances!") } - if len(ub.BalanceMap[MINUTES]) != 1 { + if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 { t.Error("Error cleaning expired minute buckets!") } } @@ -516,7 +516,7 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + ub1 := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} b.StartTimer() for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") @@ -526,7 +526,7 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { storageGetter.SetUserBalance(rifsBalance) storageGetter.GetUserBalance(rifsBalance.Id) @@ -536,7 +536,7 @@ func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { func BenchmarkGetSecondsForPrefix(b *testing.B) { b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") } From 372fbe0e108224deeb8572bcdef82f4f8f8e7e09 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 9 Sep 2013 20:45:46 +0300 Subject: [PATCH 12/39] started destination hash storage and colapsed debit actions --- engine/action.go | 6 +---- engine/actions_test.go | 25 +++++------------ engine/calldesc.go | 6 +++-- engine/destinations.go | 3 --- engine/destinations_test.go | 11 +++++--- engine/ratingprofile.go | 5 ---- engine/storage_interface.go | 2 +- engine/storage_map.go | 14 ++++++++++ engine/storage_redis.go | 28 +++++++++++++------ engine/userbalance.go | 54 +++++++++++++------------------------ engine/userbalance_test.go | 17 +++++++----- utils/coreutils.go | 20 ++++++++++++++ utils/utils_test.go | 14 ++++++++++ 13 files changed, 116 insertions(+), 89 deletions(-) diff --git a/engine/action.go b/engine/action.go index ed48e6aab..039264ca1 100644 --- a/engine/action.go +++ b/engine/action.go @@ -161,11 +161,7 @@ func genericDebit(ub *UserBalance, a *Action) (err error) { if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain) } - if a.BalanceId == MINUTES { - ub.debitMinuteBalance(a.Balance) - } else { - ub.debitBalanceAction(a) - } + ub.debitBalanceAction(a) return } diff --git a/engine/actions_test.go b/engine/actions_test.go index f9e121f52..85d52399c 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -515,17 +515,6 @@ func TestActionTriggerPriotityList(t *testing.T) { } } -/*func TestActionLog(t *testing.T) { - a := &Action{ - ActionType: "TEST", - BalanceId: "BALANCE", - Units: 10, - Weight: 11, - Balance: &Balance{}, - } - logAction(nil, a) -}*/ - func TestActionResetTriggres(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", @@ -667,7 +656,7 @@ func TestActionTopupResetMinutes(t *testing.T) { len(ub.UnitCounters) != 1 || len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue()) + t.Errorf("Topup reset minutes action failed: %v", ub.BalanceMap[MINUTES+OUTBOUND][0]) } } @@ -698,15 +687,15 @@ func TestActionTopupMinutes(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 15 || + ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue() != 15 || ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup minutes action failed!", ub.BalanceMap[MINUTES+OUTBOUND][0]) + t.Error("Topup minutes action failed!", ub.BalanceMap[MINUTES+OUTBOUND]) } } @@ -737,7 +726,7 @@ func TestActionDebitMinutes(t *testing.T) { UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 5 || @@ -797,8 +786,6 @@ func TestActionResetCounterMinutes(t *testing.T) { len(ub.UnitCounters[1].MinuteBalances) != 1 || len(ub.BalanceMap[MINUTES]) != 2 || ub.ActionTriggers[0].Executed != true { - t.Logf("%#v", ub.UnitCounters[1].MinuteBalances[0]) - t.Logf("%#v", ub.UnitCounters[1].MinuteBalances[1]) t.Error("Reset counters action failed!", ub.UnitCounters[1].MinuteBalances) } if len(ub.UnitCounters) < 2 || len(ub.UnitCounters[1].MinuteBalances) < 1 { @@ -841,7 +828,7 @@ func TestActionTriggerLogging(t *testing.T) { } as, err := storageGetter.GetActions(at.ActionsId) if err != nil { - t.Error("Error getting actions for the action timing: ", err) + t.Error("Error getting actions for the action timing: ", as, err) } storageLogger.LogActionTrigger("rif", RATER_SOURCE, at, as) //expected := "rif*some_uuid;MONETARY;OUT;NAT;TEST_ACTIONS;100;10;false*|TOPUP|MONETARY|OUT|10|0" diff --git a/engine/calldesc.go b/engine/calldesc.go index d72a97eb9..34016837f 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -39,8 +39,10 @@ func init() { //db_server := "127.0.0.1" //db_server := "192.168.0.17" m, _ := NewMapStorage() - //m, _ = NewMongoStorage(db_server, "27017", "cgrates_test", "", "") - //m, _ = NewRedisStorage(db_server+":6379", 11, "") + //m, _ := NewMongoStorage(db_server, "27017", "cgrates_test", "", "") + //m, _ := NewRedisStorage(db_server+":6379", 11, "") + //m, _ := NewRedigoStorage(db_server+":6379", 11, "") + //m, _ := NewRadixStorage(db_server+":6379", 11, "") storageGetter, _ = m.(DataStorage) storageLogger = storageGetter.(LogStorage) diff --git a/engine/destinations.go b/engine/destinations.go index 0d15f592e..6b638ae44 100644 --- a/engine/destinations.go +++ b/engine/destinations.go @@ -45,9 +45,6 @@ func GetDestination(dId string) (d *Destination, err error) { return } -/* -De-serializes the destination for the storage. Used for key-value storages. -*/ func (d *Destination) containsPrefix(prefix string) (precision int, ok bool) { if d == nil { return diff --git a/engine/destinations_test.go b/engine/destinations_test.go index 61b97e9f1..c5ccc3ca9 100644 --- a/engine/destinations_test.go +++ b/engine/destinations_test.go @@ -38,8 +38,11 @@ func TestDestinationStoreRestore(t *testing.T) { func TestDestinationStorageStore(t *testing.T) { nationale := &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} - storageGetter.SetDestination(nationale) - result, _ := storageGetter.GetDestination(nationale.Id) + err := storageGetter.SetDestination(nationale) + if err != nil { + t.Error("Error storing destination: ", err) + } + result, err := storageGetter.GetDestination(nationale.Id) if !reflect.DeepEqual(nationale, result) { t.Errorf("Expected %q was %q", nationale, result) } @@ -86,8 +89,8 @@ func TestDestinationGetNotExists(t *testing.T) { func TestDestinationGetNotExistsCache(t *testing.T) { GetDestination("not existing") - if _, err := cache2go.GetCached("not existing"); err == nil { - t.Error("Bad destination cached") + if d, err := cache2go.GetCached("not existing"); err == nil { + t.Error("Bad destination cached: ", d) } } diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 4199ddd8b..664f139f2 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -23,11 +23,6 @@ import ( "fmt" ) -const ( - // the minimum length for a destination prefix to be matched. - MIN_PREFIX_LENGTH = 2 -) - type RatingProfile struct { Id string FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject diff --git a/engine/storage_interface.go b/engine/storage_interface.go index c535b1842..2dd07f493 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -69,8 +69,8 @@ type DataStorage interface { GetRatingProfile(string) (*RatingProfile, error) SetRatingProfile(*RatingProfile) error GetDestination(string) (*Destination, error) + DestinationContainsPrefix(string, string) (int, error) SetDestination(*Destination) error - // End Apier functions GetActions(string) (Actions, error) SetActions(string, Actions) error GetUserBalance(string) (*UserBalance, error) diff --git a/engine/storage_map.go b/engine/storage_map.go index 1e03d70c8..d009632c5 100644 --- a/engine/storage_map.go +++ b/engine/storage_map.go @@ -70,6 +70,20 @@ func (ms *MapStorage) GetDestination(key string) (dest *Destination, err error) } return } + +func (ms *MapStorage) DestinationContainsPrefix(key string, prefix string) (precision int, err error) { + if d, err := ms.GetDestination(key); err != nil { + return 0, err + } else { + for _, p := range utils.SplitPrefix(prefix) { + if precision, ok := d.containsPrefix(p); ok { + return precision, nil + } + } + return precision, nil + } +} + func (ms *MapStorage) SetDestination(dest *Destination) (err error) { result, err := ms.ms.Marshal(dest) ms.dict[DESTINATION_PREFIX+dest.Id] = result diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 335ae6905..4addfb9c5 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -21,6 +21,7 @@ package engine import ( "fmt" "github.com/cgrates/cgrates/history" + "github.com/cgrates/cgrates/utils" "menteslibres.net/gosexy/redis" "strconv" "strings" @@ -87,20 +88,31 @@ func (rs *RedisStorage) SetRatingProfile(rp *RatingProfile) (err error) { } func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) { - var values string - if values, err = rs.db.Get(DESTINATION_PREFIX + key); err == nil { - dest = &Destination{Id: key} - err = rs.ms.Unmarshal([]byte(values), dest) + var values []string + if values, err = rs.db.HKeys(DESTINATION_PREFIX + key); len(values) > 0 && err == nil { + dest = &Destination{Id: key, Prefixes: values} + } + return +} + +func (rs *RedisStorage) DestinationContainsPrefix(key string, prefix string) (precision int, err error) { + var values []string + if values, err = rs.db.HMGet(DESTINATION_PREFIX+key, utils.SplitPrefix(prefix)...); err == nil { + for i, p := range values { + if p != "" { + return len(prefix) - i, nil + } + } } return } func (rs *RedisStorage) SetDestination(dest *Destination) (err error) { - var result []byte - if result, err = rs.ms.Marshal(dest); err != nil { - return + var newPrefixes []interface{} + for _, p := range dest.Prefixes { + newPrefixes = append(newPrefixes, p, "*") } - _, err = rs.db.Set(DESTINATION_PREFIX+dest.Id, result) + _, err = rs.db.HMSet(DESTINATION_PREFIX+dest.Id, newPrefixes...) if err == nil && historyScribe != nil { response := 0 historyScribe.Record(&history.Record{DESTINATION_PREFIX + dest.Id, dest}, &response) diff --git a/engine/userbalance.go b/engine/userbalance.go index 3c534d110..b387144f9 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -100,32 +100,37 @@ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float return } -// Debit seconds from specified minute bucket -func (ub *UserBalance) debitMinuteBalance(newMb *Balance) error { - if newMb == nil { - return errors.New("Nil minute bucket!") +// Debits some amount of user's specified balance adding the balance if it does not exists. +// Returns the remaining credit in user's balance. +func (ub *UserBalance) debitBalanceAction(a *Action) error { + if a == nil { + return errors.New("nil minute action!") + } + if a.Balance.Id == "" { + a.Balance.Id = utils.GenUUID() } if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain, 0) } found := false - for _, mb := range ub.BalanceMap[MINUTES+OUTBOUND] { - if mb.IsExpired() { - continue + id := a.BalanceId + a.Direction + for _, b := range ub.BalanceMap[id] { + if b.IsExpired() { + continue // we can clean expired balances balances here } - if mb.Equal(newMb) { - mb.Value -= newMb.Value + if b.Equal(a.Balance) { + b.Value -= a.Balance.Value found = true break } } // if it is not found and the Seconds are negative (topup) // then we add it to the list - if !found && newMb.Value <= 0 { - newMb.Value = -newMb.Value - ub.BalanceMap[MINUTES+OUTBOUND] = append(ub.BalanceMap[MINUTES+OUTBOUND], newMb) + if !found && a.Balance.Value <= 0 { + a.Balance.Value = -a.Balance.Value + ub.BalanceMap[id] = append(ub.BalanceMap[id], a.Balance) } - return nil + return nil //ub.BalanceMap[id].GetTotalValue() } /* @@ -179,29 +184,6 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count return nil } -// Debits some amount of user's specified balance adding the balance if it does not exists. -// Returns the remaining credit in user's balance. -func (ub *UserBalance) debitBalanceAction(a *Action) float64 { - newBalance := &Balance{Id: utils.GenUUID()} - if a.Balance != nil { - newBalance.ExpirationDate = a.Balance.ExpirationDate - newBalance.Weight = a.Balance.Weight - } - found := false - id := a.BalanceId + a.Direction - for _, b := range ub.BalanceMap[id] { - if b.Equal(newBalance) { - b.Value -= a.Balance.Value - found = true - } - } - if !found { - newBalance.Value -= a.Balance.Value - ub.BalanceMap[id] = append(ub.BalanceMap[id], newBalance) - } - return ub.BalanceMap[a.BalanceId+OUTBOUND].GetTotalValue() -} - /* Debits some amount of user's specified balance. Returns the remaining credit in user's balance. */ diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 38d1de85c..33e4a87a0 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -344,7 +344,8 @@ func TestUserBalancedebitBalance(t *testing.T) { BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } newMb := &Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NEW"} - ub.debitMinuteBalance(newMb) + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: newMb} + ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 3 || ub.BalanceMap[MINUTES+OUTBOUND][2] != newMb { t.Error("Error adding minute bucket!", len(ub.BalanceMap[MINUTES+OUTBOUND]), ub.BalanceMap[MINUTES+OUTBOUND]) } @@ -358,7 +359,8 @@ func TestUserBalancedebitBalanceExists(t *testing.T) { BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 15, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } newMb := &Balance{Value: -10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"} - ub.debitMinuteBalance(newMb) + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: newMb} + ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 25 { t.Error("Error adding minute bucket!") } @@ -370,7 +372,7 @@ func TestUserBalanceAddMinuteNil(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, } - ub.debitMinuteBalance(nil) + ub.debitBalanceAction(nil) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 { t.Error("Error adding minute bucket!") } @@ -381,15 +383,18 @@ func TestUserBalanceAddMinutBucketEmpty(t *testing.T) { mb2 := &Balance{Value: -10, DestinationId: "NAT"} mb3 := &Balance{Value: -10, DestinationId: "OTHER"} ub := &UserBalance{} - ub.debitMinuteBalance(mb1) + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: mb1} + ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 { t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } - ub.debitMinuteBalance(mb2) + a = &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: mb2} + ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 1 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 20 { t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } - ub.debitMinuteBalance(mb3) + a = &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: mb3} + ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 { t.Error("Error adding minute bucket: ", ub.BalanceMap[MINUTES+OUTBOUND]) } diff --git a/utils/coreutils.go b/utils/coreutils.go index c1217c32c..5cbe8c54d 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -121,9 +121,29 @@ func ParseDate(date string) (expDate time.Time, err error) { return expDate, err } +// returns a number equeal or larger than the peram that exactly +// is divisible to 60 func RoundToMinute(seconds float64) float64 { if math.Mod(seconds, 60) == 0 { return seconds } return (60 - math.Mod(seconds, 60)) + seconds } + +func SplitPrefix(prefix string) []string { + var subs []string + max := len(prefix) + for i := 0; i < len(prefix)-1; i++ { + subs = append(subs, prefix[:max-i]) + } + return subs +} + +func SplitPrefixInterface(prefix string) []interface{} { + var subs []interface{} + max := len(prefix) + for i := 0; i < len(prefix)-1; i++ { + subs = append(subs, prefix[:max-i]) + } + return subs +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 12f0cf557..89bb11742 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -227,3 +227,17 @@ func TestRoundToMinute(t *testing.T) { t.Errorf("Error rounding to minute5: expected %v was %v", expected, result) } } + +func TestSplitPrefix(t *testing.T) { + a := SplitPrefix("0123456789") + if len(a) != 9 { + t.Error("Error splitting prefix: ", a) + } +} + +func TestSplitPrefixEmpty(t *testing.T) { + a := SplitPrefix("") + if len(a) != 0 { + t.Error("Error splitting prefix: ", a) + } +} From 1ee31ca0044bf99499f2e070af179a0f2233d64a Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 9 Sep 2013 22:16:07 +0300 Subject: [PATCH 13/39] started rating unit implementation --- engine/callcost.go | 7 +++++++ sessionmanager/fssessionmanager.go | 12 ++++-------- sessionmanager/session.go | 4 ++-- sessionmanager/sessionmanager.go | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/engine/callcost.go b/engine/callcost.go index 367789cd4..499e1c09b 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -66,3 +66,10 @@ func (cc *CallCost) GetStartTime() time.Time { } return cc.Timespans[0].TimeStart } + +func (cc *CallCost) GetTotalDuration() (td time.Duration) { + for _, ts := range cc.Timespans { + td += ts.GetDuration() + } + return +} diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index fe989f0a9..130971b66 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -315,8 +315,8 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { } -func (sm *FSSessionManager) LoopAction(s *Session, cd *engine.CallDescriptor, index float64) { - cc := &engine.CallCost{} +func (sm *FSSessionManager) LoopAction(s *Session, cd *engine.CallDescriptor, index float64) (cc *engine.CallCost) { + cc = &engine.CallCost{} cd.LoopIndex = index cd.Amount = sm.debitPeriod.Seconds() cd.CallDuration += time.Duration(cd.Amount) * time.Second @@ -326,18 +326,14 @@ func (sm *FSSessionManager) LoopAction(s *Session, cd *engine.CallDescriptor, in // disconnect session s.sessionManager.DisconnectSession(s, SYSTEM_ERROR) } - nbts := len(cc.Timespans) - remainingSeconds := 0.0 engine.Logger.Debug(fmt.Sprintf("Result of MaxDebit call: %v", cc)) - if nbts > 0 { - remainingSeconds = cc.Timespans[nbts-1].TimeEnd.Sub(cc.Timespans[0].TimeStart).Seconds() - } - if remainingSeconds == 0 || err != nil { + if cc.GetTotalDuration() == 0 || err != nil { engine.Logger.Info(fmt.Sprintf("No credit left: Disconnect %v", s)) sm.DisconnectSession(s, INSUFFICIENT_FUNDS) return } s.CallCosts = append(s.CallCosts, cc) + return } func (sm *FSSessionManager) GetDebitPeriod() time.Duration { diff --git a/sessionmanager/session.go b/sessionmanager/session.go index efa703124..3e5cadb20 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -86,8 +86,8 @@ func (s *Session) startDebitLoop() { nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(s.sessionManager.GetDebitPeriod()) - s.sessionManager.LoopAction(s, &nextCd, index) - time.Sleep(s.sessionManager.GetDebitPeriod()) + cc := s.sessionManager.LoopAction(s, &nextCd, index) + time.Sleep(cc.GetTotalDuration()) index++ } } diff --git a/sessionmanager/sessionmanager.go b/sessionmanager/sessionmanager.go index ef7311e75..9eb2f68ae 100644 --- a/sessionmanager/sessionmanager.go +++ b/sessionmanager/sessionmanager.go @@ -28,7 +28,7 @@ type SessionManager interface { Connect(*config.CGRConfig) error DisconnectSession(*Session, string) RemoveSession(*Session) - LoopAction(*Session, *engine.CallDescriptor, float64) + LoopAction(*Session, *engine.CallDescriptor, float64) *engine.CallCost GetDebitPeriod() time.Duration GetDbLogger() engine.LogStorage Shutdown() error From a1612f55072afd5af537b8f20357799d877ac889 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 10 Sep 2013 14:45:55 +0300 Subject: [PATCH 14/39] implemented timespan expanding --- engine/callcost_test.go | 25 +++++++ engine/calldesc.go | 32 +++++++++ engine/interval_test.go | 5 ++ engine/timespans.go | 4 +- engine/timespans_test.go | 136 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 1 deletion(-) diff --git a/engine/callcost_test.go b/engine/callcost_test.go index 2aeb77415..57e13c043 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -125,3 +125,28 @@ func TestMultipleInputRightMerge(t *testing.T) { t.Errorf("Exdpected 150 was %v", cc1.Cost) } } + +func TestCallCostGetDurationZero(t *testing.T) { + cc := &CallCost{} + if cc.GetTotalDuration().Seconds() != 0 { + t.Error("Wrong call cost duration for zero timespans: ", cc.GetTotalDuration()) + } +} + +func TestCallCostGetDuration(t *testing.T) { + cc := &CallCost{ + Timespans: []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 13, 40, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 13, 41, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 13, 41, 30, 0, time.UTC), + }, + }, + } + if cc.GetTotalDuration().Seconds() != 90 { + t.Error("Wrong call cost duration: ", cc.GetTotalDuration()) + } +} diff --git a/engine/calldesc.go b/engine/calldesc.go index 34016837f..becd10a58 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -262,9 +262,41 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } } } + timespans = cd.expandTimeSpans(timespans) return } +// if the rate interval for any timespan has a RatingInterval larger than the timespan duration +// the timespan must expand potentially overlaping folowing timespans and may exceed call +// descriptor's initial duration +func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { + for i, ts := range timespans { + if ts.Interval != nil { + _, rateIncrement, _ := ts.Interval.GetPriceParameters(ts.GetGroupStart()) + if rateIncrement > ts.GetDuration() { + ts.TimeEnd = ts.TimeStart.Add(rateIncrement) + // overlap the rest of the timespans + for ; i < len(timespans); i++ { + if timespans[i].TimeEnd.Before(ts.TimeEnd) { + timespans[i].overlapped = true + } else if timespans[i].TimeStart.Before(ts.TimeEnd) { + timespans[i].TimeStart = ts.TimeEnd + } + } + break + } + } + } + // remove overlapped + for i, ts := range timespans { + if ts.overlapped { + timespans = timespans[:i] + break + } + } + return timespans +} + /* Creates a CallCost structure with the cost information calculated for the received CallDescriptor. */ diff --git a/engine/interval_test.go b/engine/interval_test.go index ec8da2893..6ea61ad73 100644 --- a/engine/interval_test.go +++ b/engine/interval_test.go @@ -182,6 +182,11 @@ func TestIntervalNotEqual(t *testing.T) { } } +func TestIntervalGetCost(t *testing.T) { +} + +/*********************************Benchmarks**************************************/ + func BenchmarkIntervalContainsDate(b *testing.B) { i := &Interval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} d := time.Date(2012, time.February, 1, 14, 30, 0, 0, time.UTC) diff --git a/engine/timespans.go b/engine/timespans.go index 3a9da6d6a..4feaa626d 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,6 +33,7 @@ type TimeSpan struct { Interval *Interval MinuteInfo *MinuteInfo CallDuration time.Duration // the call duration so far till TimeEnd + overlapped bool // mark a timespan as overlapped by an expanded one } // Holds the bonus minute information related to a specified timespan @@ -80,12 +81,13 @@ func (ts *TimeSpan) Contains(t time.Time) bool { } /* -Will set the interval as spans's interval if new Weight is greater then span's interval Weight +Will set the interval as spans's interval if new Weight is lower then span's interval Weight or if the Weights are equal and new price is lower then spans's interval price */ func (ts *TimeSpan) SetInterval(i *Interval) { if ts.Interval == nil || ts.Interval.Weight < i.Weight { ts.Interval = i + return } iPrice, _, _ := i.GetPriceParameters(ts.GetGroupStart()) tsPrice, _, _ := ts.Interval.GetPriceParameters(ts.GetGroupStart()) diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 6e1f54d8a..410609e3b 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -517,3 +517,139 @@ func TestTimespanSplitMultipleGroup(t *testing.T) { t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) } } + +func TestTimespanExpandingPastEnd(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + Interval: &Interval{Prices: PriceGroups{ + &Price{RateIncrement: 60 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 1 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 31, 0, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} + +func TestTimespanExpandingPastEndMultiple(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + Interval: &Interval{Prices: PriceGroups{ + &Price{RateIncrement: 60 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 40, 0, time.UTC), + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 40, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 50, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 1 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 31, 0, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} + +func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + Interval: &Interval{Prices: PriceGroups{ + &Price{RateIncrement: 60 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 40, 0, time.UTC), + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 40, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 31, 00, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 1 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 31, 0, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} + +func TestTimespanExpandingBeforeEnd(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + Interval: &Interval{Prices: PriceGroups{ + &Price{RateIncrement: 45 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 31, 0, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 2 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC)) || + !timespans[1].TimeStart.Equal(time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC)) || + !timespans[1].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 31, 0, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} + +func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + Interval: &Interval{Prices: PriceGroups{ + &Price{RateIncrement: 45 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 50, 0, time.UTC), + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 50, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 31, 00, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 3 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC)) || + !timespans[1].TimeStart.Equal(time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC)) || + !timespans[1].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 30, 50, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} From a35f5ed480b1185ad897cab3f1f4190ca9dccb05 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 12 Sep 2013 16:24:57 +0300 Subject: [PATCH 15/39] interval and activation profile refactoring --- apier/v1/tpactions.go | 14 +- apier/v1/tprates.go | 8 +- docs/ratinglogic.rst | 18 +- engine/action.go | 18 +- engine/action_timing.go | 2 +- engine/actions_test.go | 48 ++-- engine/callcost.go | 4 +- engine/callcost_test.go | 4 +- engine/calldesc.go | 73 +++--- engine/calldesc_test.go | 16 +- engine/history_test.go | 16 -- engine/loader_csv.go | 24 +- engine/loader_db.go | 32 +-- engine/loader_helpers.go | 14 +- engine/{interval.go => rateinterval.go} | 56 ++--- ...{interval_test.go => rateinterval_test.go} | 50 ++-- engine/{activationperiod.go => ratingplan.go} | 18 +- ...ationperiod_test.go => ratingplan_test.go} | 124 +++++----- engine/ratingprofile.go | 8 +- engine/ratingprofile_test.go | 12 +- engine/storage_interface.go | 4 +- engine/storage_sql.go | 10 +- engine/storage_test.go | 56 ++--- engine/timespans.go | 55 ++--- engine/timespans_test.go | 216 ++++++++++-------- engine/tpimporter_csv.go | 37 ++- sessionmanager/fssessionmanager.go | 2 +- utils/coreutils.go | 13 +- utils/utils_test.go | 25 +- 29 files changed, 487 insertions(+), 490 deletions(-) rename engine/{interval.go => rateinterval.go} (79%) rename engine/{interval_test.go => rateinterval_test.go} (75%) rename engine/{activationperiod.go => ratingplan.go} (75%) rename engine/{activationperiod_test.go => ratingplan_test.go} (61%) diff --git a/apier/v1/tpactions.go b/apier/v1/tpactions.go index 62849693f..b2cde95c2 100644 --- a/apier/v1/tpactions.go +++ b/apier/v1/tpactions.go @@ -50,13 +50,15 @@ func (self *ApierV1) SetTPActions(attrs utils.TPActions, reply *string) error { ActionType: act.Identifier, BalanceId: act.BalanceType, Direction: act.Direction, - Units: act.Units, ExpirationString: act.ExpiryTime, - DestinationTag: act.DestinationId, - RateType: act.RateType, - RateValue: act.Rate, - MinutesWeight: act.MinutesWeight, - Weight: act.Weight, + Balance: &engine.Balance{ + Value: act.Units, + DestinationId: act.DestinationId, + SpecialPriceType: act.RateType, + SpecialPrice: act.Rate, + Weight: act.MinutesWeight, + }, + Weight: act.Weight, } } if err := self.StorDb.SetTPActions(attrs.TPid, map[string][]*engine.Action{attrs.ActionsId: acts}); err != nil { diff --git a/apier/v1/tprates.go b/apier/v1/tprates.go index 1fd6cae71..641eb5872 100644 --- a/apier/v1/tprates.go +++ b/apier/v1/tprates.go @@ -23,9 +23,9 @@ package apier import ( "errors" "fmt" - "time" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" + "time" ) // Creates a new rate within a tariff plan @@ -38,7 +38,7 @@ func (self *ApierV1) SetTPRate(attrs utils.TPRate, reply *string) error { } else if exists { return errors.New(utils.ERR_DUPLICATE) } - rts := make([]*engine.Rate, len(attrs.RateSlots)) + rts := make([]*engine.LoadRate, len(attrs.RateSlots)) for idx, rtSlot := range attrs.RateSlots { var errParse error itrvlStrs := []string{rtSlot.RatedUnits, rtSlot.RateIncrements, rtSlot.GroupIntervalStart} @@ -48,10 +48,10 @@ func (self *ApierV1) SetTPRate(attrs utils.TPRate, reply *string) error { return fmt.Errorf("%s:Parsing interval failed:%s", utils.ERR_SERVER_ERROR, errParse.Error()) } } - rts[idx] = &engine.Rate{attrs.RateId, rtSlot.ConnectFee, rtSlot.Rate, itrvls[0], itrvls[1], itrvls[2], + rts[idx] = &engine.LoadRate{attrs.RateId, rtSlot.ConnectFee, rtSlot.Rate, itrvls[0], itrvls[1], itrvls[2], rtSlot.RoundingMethod, rtSlot.RoundingDecimals, rtSlot.Weight} } - if err := self.StorDb.SetTPRates(attrs.TPid, map[string][]*engine.Rate{attrs.RateId: rts}); err != nil { + if err := self.StorDb.SetTPRates(attrs.TPid, map[string][]*engine.LoadRate{attrs.RateId: rts}); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } *reply = "OK" diff --git a/docs/ratinglogic.rst b/docs/ratinglogic.rst index 9470b55d3..535d54a7d 100644 --- a/docs/ratinglogic.rst +++ b/docs/ratinglogic.rst @@ -15,7 +15,7 @@ The call information comes to CGRateS having the following vital information lik LoopIndex // indicates the position of this segment in a cost request loop CallDuration // the call duration so far (partial or final) FallbackSubject // the subject to check for destination if not found on primary subject - ActivationPeriods + RatingPlans } When the session manager receives a call start event it will first check if the call is prepaid or postpaid. If the call is postpaid than the cost will be determined only once at the end of the call but if the call is prepaid there will be a debit operation every X seconds (X is configurable). @@ -31,7 +31,7 @@ What are the activation periods? :: - type Interval struct { + type RateInterval struct { Years Months MonthDays @@ -51,9 +51,9 @@ What are the activation periods? } -An **Interval** specifies the Month, the MonthDay, the WeekDays, the StartTime and the EndTime when the Interval's price profile is in effect. +An **RateInterval** specifies the Month, the MonthDay, the WeekDays, the StartTime and the EndTime when the RateInterval's price profile is in effect. -:Example: The Interval {"Month": [1], "WeekDays":[1,2,3,4,5], "StartTime":"18:00:00"} specifies the *Price* for the first month of each year from Monday to Friday starting 18:00. Most structure elements are optional and they can be combined in any way it makes sense. If an element is omitted it means it is zero or any. +:Example: The RateInterval {"Month": [1], "WeekDays":[1,2,3,4,5], "StartTime":"18:00:00"} specifies the *Price* for the first month of each year from Monday to Friday starting 18:00. Most structure elements are optional and they can be combined in any way it makes sense. If an element is omitted it means it is zero or any. The *ConnectFee* specifies the connection price for the call if this interval is the first one of the call. @@ -65,7 +65,7 @@ The *RoundingMethod* and the *RoundingDecimals* will adjust the price using the The **Price** structure defines the start (*GroupIntervalStart*) of a section of a call with a specified rate *Value* per *RateUnit* diving and rounding the section in *RateIncrement* subsections. -So when there is a need to define new sets of prices just define new ActivationPeriods with the activation time set to the moment when it becomes active. +So when there is a need to define new sets of prices just define new RatingPlans with the activation time set to the moment when it becomes active. Let's get back to the engine. When a GetCost or Debit call comes to the engine it will try to match the best rating profile for the given *Direction*, *Tenant*, *TOR* and *Subject* using the longest *Subject* prefix method or using the *FallbackSubject* if not found. The rating profile contains the activation periods that might apply to the call in question. @@ -75,21 +75,21 @@ At this point in rating process the engine will start splitting the call into va 2. Activation periods: if there were not enough special price minutes available than the engine will check if the call spans over multiple activation periods (the call starts in initial rates period and continues in another). -3. Intervals: for each activation period that apply to the call the engine will select the best rate intervals that apply. +3. RateIntervals: for each activation period that apply to the call the engine will select the best rate intervals that apply. :: type TimeSpan struct { TimeStart, TimeEnd Cost - ActivationPeriod - Interval + RatingPlan + RateInterval MinuteInfo CallDuration // the call duration so far till TimeEnd } -The result of this splitting will be a list of *TimeSpan* structures each having attached the MinuteInfo or the Interval that gave the price for it. The *CallDuration* attribute will select the right *Price* from the *Interval* *Prices* list. The final cost for the call will be the sum of the prices of these times spans plus the *ConnectionFee* from the first time span of the call. +The result of this splitting will be a list of *TimeSpan* structures each having attached the MinuteInfo or the RateInterval that gave the price for it. The *CallDuration* attribute will select the right *Price* from the *RateInterval* *Prices* list. The final cost for the call will be the sum of the prices of these times spans plus the *ConnectionFee* from the first time span of the call. 6.2.1 User balances ------------------- diff --git a/engine/action.go b/engine/action.go index 039264ca1..23463914f 100644 --- a/engine/action.go +++ b/engine/action.go @@ -21,23 +21,19 @@ package engine import ( "fmt" "sort" - "time" ) /* Structure to be filled for each tariff plan with the bonus value for received calls minutes. */ type Action struct { - Id string - ActionType string - BalanceId string - Direction string - ExpirationString string - Weight float64 - Balance *Balance - DestinationTag, RateType string // From here for import/load purposes only - ExpirationDate time.Time - Units, RateValue, MinutesWeight float64 + Id string + ActionType string + BalanceId string + Direction string + ExpirationString string + Weight float64 + Balance *Balance } const ( diff --git a/engine/action_timing.go b/engine/action_timing.go index a608b9dcb..ae84f8835 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -37,7 +37,7 @@ type ActionTiming struct { Id string // uniquely identify the timing Tag string // informative purpose only UserBalanceIds []string - Timing *Interval + Timing *RateInterval Weight float64 ActionsId string actions Actions diff --git a/engine/actions_test.go b/engine/actions_test.go index 85d52399c..06d2a595f 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -34,7 +34,7 @@ func TestActionTimingNothing(t *testing.T) { } func TestActionTimingOnlyHour(t *testing.T) { - at := &ActionTiming{Timing: &Interval{StartTime: "10:01:00"}} + at := &ActionTiming{Timing: &RateInterval{StartTime: "10:01:00"}} st := at.GetNextStartTime() now := time.Now() y, m, d := now.Date() @@ -45,7 +45,7 @@ func TestActionTimingOnlyHour(t *testing.T) { } func TestActionTimingOnlyWeekdays(t *testing.T) { - at := &ActionTiming{Timing: &Interval{WeekDays: []time.Weekday{time.Monday}}} + at := &ActionTiming{Timing: &RateInterval{WeekDays: []time.Weekday{time.Monday}}} st := at.GetNextStartTime() now := time.Now() y, m, d := now.Date() @@ -64,7 +64,7 @@ func TestActionTimingOnlyWeekdays(t *testing.T) { } func TestActionTimingHourWeekdays(t *testing.T) { - at := &ActionTiming{Timing: &Interval{WeekDays: []time.Weekday{time.Monday}, StartTime: "10:01:00"}} + at := &ActionTiming{Timing: &RateInterval{WeekDays: []time.Weekday{time.Monday}, StartTime: "10:01:00"}} st := at.GetNextStartTime() now := time.Now() y, m, d := now.Date() @@ -85,7 +85,7 @@ func TestActionTimingOnlyMonthdays(t *testing.T) { now := time.Now() y, m, d := now.Date() tomorrow := time.Date(y, m, d, 0, 0, 0, 0, time.Local).AddDate(0, 0, 1) - at := &ActionTiming{Timing: &Interval{MonthDays: MonthDays{1, 25, 2, tomorrow.Day()}}} + at := &ActionTiming{Timing: &RateInterval{MonthDays: MonthDays{1, 25, 2, tomorrow.Day()}}} st := at.GetNextStartTime() expected := tomorrow if !st.Equal(expected) { @@ -102,7 +102,7 @@ func TestActionTimingHourMonthdays(t *testing.T) { if now.After(testTime) { day = tomorrow.Day() } - at := &ActionTiming{Timing: &Interval{MonthDays: MonthDays{now.Day(), tomorrow.Day()}, StartTime: "10:01:00"}} + at := &ActionTiming{Timing: &RateInterval{MonthDays: MonthDays{now.Day(), tomorrow.Day()}, StartTime: "10:01:00"}} st := at.GetNextStartTime() expected := time.Date(y, m, day, 10, 1, 0, 0, time.Local) if !st.Equal(expected) { @@ -114,7 +114,7 @@ func TestActionTimingOnlyMonths(t *testing.T) { now := time.Now() y, m, d := now.Date() nextMonth := time.Date(y, m, d, 0, 0, 0, 0, time.Local).AddDate(0, 1, 0) - at := &ActionTiming{Timing: &Interval{Months: Months{time.February, time.May, nextMonth.Month()}}} + at := &ActionTiming{Timing: &RateInterval{Months: Months{time.February, time.May, nextMonth.Month()}}} st := at.GetNextStartTime() expected := time.Date(y, nextMonth.Month(), 1, 0, 0, 0, 0, time.Local) if !st.Equal(expected) { @@ -131,7 +131,7 @@ func TestActionTimingHourMonths(t *testing.T) { if now.After(testTime) { month = nextMonth.Month() } - at := &ActionTiming{Timing: &Interval{Months: Months{now.Month(), nextMonth.Month()}, StartTime: "10:01:00"}} + at := &ActionTiming{Timing: &RateInterval{Months: Months{now.Month(), nextMonth.Month()}, StartTime: "10:01:00"}} st := at.GetNextStartTime() expected := time.Date(y, month, d, 10, 1, 0, 0, time.Local) if !st.Equal(expected) { @@ -156,7 +156,7 @@ func TestActionTimingHourMonthdaysMonths(t *testing.T) { month = nextMonth.Month() } } - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Months: Months{now.Month(), nextMonth.Month()}, MonthDays: MonthDays{now.Day(), tomorrow.Day()}, StartTime: "10:01:00", @@ -172,7 +172,7 @@ func TestActionTimingFirstOfTheMonth(t *testing.T) { now := time.Now() y, m, _ := now.Date() nextMonth := time.Date(y, m, 1, 0, 0, 0, 0, time.Local).AddDate(0, 1, 0) - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{1}, }} @@ -187,7 +187,7 @@ func TestActionTimingOnlyYears(t *testing.T) { now := time.Now() y, m, d := now.Date() nextYear := time.Date(y, m, d, 0, 0, 0, 0, time.Local).AddDate(1, 0, 0) - at := &ActionTiming{Timing: &Interval{Years: Years{now.Year(), nextYear.Year()}}} + at := &ActionTiming{Timing: &RateInterval{Years: Years{now.Year(), nextYear.Year()}}} st := at.GetNextStartTime() expected := time.Date(nextYear.Year(), 1, 1, 0, 0, 0, 0, time.Local) if !st.Equal(expected) { @@ -204,7 +204,7 @@ func TestActionTimingHourYears(t *testing.T) { if now.After(testTime) { year = nextYear.Year() } - at := &ActionTiming{Timing: &Interval{Years: Years{now.Year(), nextYear.Year()}, StartTime: "10:01:00"}} + at := &ActionTiming{Timing: &RateInterval{Years: Years{now.Year(), nextYear.Year()}, StartTime: "10:01:00"}} st := at.GetNextStartTime() expected := time.Date(year, m, d, 10, 1, 0, 0, time.Local) if !st.Equal(expected) { @@ -229,7 +229,7 @@ func TestActionTimingHourMonthdaysYear(t *testing.T) { year = nextYear.Year() } } - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Years: Years{now.Year(), nextYear.Year()}, MonthDays: MonthDays{now.Day(), tomorrow.Day()}, StartTime: "10:01:00", @@ -266,7 +266,7 @@ func TestActionTimingHourMonthdaysMonthYear(t *testing.T) { year = nextYear.Year() } } - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Years: Years{now.Year(), nextYear.Year()}, Months: Months{now.Month(), nextMonth.Month()}, MonthDays: MonthDays{now.Day(), tomorrow.Day()}, @@ -283,7 +283,7 @@ func TestActionTimingFirstOfTheYear(t *testing.T) { now := time.Now() y, _, _ := now.Date() nextYear := time.Date(y, 1, 1, 0, 0, 0, 0, time.Local).AddDate(1, 0, 0) - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Years: Years{nextYear.Year()}, Months: Months{time.January}, MonthDays: MonthDays{1}, @@ -300,7 +300,7 @@ func TestActionTimingFirstMonthOfTheYear(t *testing.T) { now := time.Now() y, _, _ := now.Date() nextYear := time.Date(y, 1, 1, 0, 0, 0, 0, time.Local).AddDate(1, 0, 0) - at := &ActionTiming{Timing: &Interval{ + at := &ActionTiming{Timing: &RateInterval{ Months: Months{time.January}, }} st := at.GetNextStartTime() @@ -311,14 +311,14 @@ func TestActionTimingFirstMonthOfTheYear(t *testing.T) { } func TestActionTimingCheckForASAP(t *testing.T) { - at := &ActionTiming{Timing: &Interval{StartTime: ASAP}} + at := &ActionTiming{Timing: &RateInterval{StartTime: ASAP}} if !at.CheckForASAP() { t.Errorf("%v should be asap!", at) } } func TestActionTimingIsOneTimeRun(t *testing.T) { - at := &ActionTiming{Timing: &Interval{StartTime: ASAP}} + at := &ActionTiming{Timing: &RateInterval{StartTime: ASAP}} if !at.CheckForASAP() { t.Errorf("%v should be asap!", at) } @@ -328,7 +328,7 @@ func TestActionTimingIsOneTimeRun(t *testing.T) { } func TestActionTimingOneTimeRun(t *testing.T) { - at := &ActionTiming{Timing: &Interval{StartTime: ASAP}} + at := &ActionTiming{Timing: &RateInterval{StartTime: ASAP}} at.CheckForASAP() nextRun := at.GetNextStartTime() if nextRun.IsZero() { @@ -352,14 +352,14 @@ func TestActionTimingLogFunction(t *testing.T) { } func TestActionTimingPriotityListSortByWeight(t *testing.T) { - at1 := &ActionTiming{Timing: &Interval{ + at1 := &ActionTiming{Timing: &RateInterval{ Years: Years{2100}, Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{1}, StartTime: "00:00:00", Weight: 20, }} - at2 := &ActionTiming{Timing: &Interval{ + at2 := &ActionTiming{Timing: &RateInterval{ Years: Years{2100}, Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{2}, @@ -376,7 +376,7 @@ func TestActionTimingPriotityListSortByWeight(t *testing.T) { func TestActionTimingPriotityListWeight(t *testing.T) { at1 := &ActionTiming{ - Timing: &Interval{ + Timing: &RateInterval{ Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{1}, StartTime: "00:00:00", @@ -384,7 +384,7 @@ func TestActionTimingPriotityListWeight(t *testing.T) { Weight: 10.0, } at2 := &ActionTiming{ - Timing: &Interval{ + Timing: &RateInterval{ Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{1}, StartTime: "00:00:00", @@ -848,7 +848,7 @@ func TestActionTriggerLogging(t *testing.T) { } func TestActionTimingLogging(t *testing.T) { - i := &Interval{ + i := &RateInterval{ Months: Months{time.January, time.February, time.March, time.April, time.May, time.June, time.July, time.August, time.September, time.October, time.November, time.December}, MonthDays: MonthDays{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, WeekDays: WeekDays{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, @@ -856,7 +856,7 @@ func TestActionTimingLogging(t *testing.T) { EndTime: "00:00:00", Weight: 10.0, ConnectFee: 0.0, - Prices: PriceGroups{&Price{0, 1.0, 1 * time.Second, 60 * time.Second}}, + Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 60 * time.Second}}, } at := &ActionTiming{ Id: "some uuid", diff --git a/engine/callcost.go b/engine/callcost.go index 499e1c09b..7c75f29ab 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -47,8 +47,8 @@ func (cc *CallCost) Merge(other *CallCost) { } ts := cc.Timespans[len(cc.Timespans)-1] otherTs := other.Timespans[0] - if reflect.DeepEqual(ts.ActivationPeriod, otherTs.ActivationPeriod) && - reflect.DeepEqual(ts.MinuteInfo, otherTs.MinuteInfo) && reflect.DeepEqual(ts.Interval, otherTs.Interval) { + if reflect.DeepEqual(ts.RatingPlan, otherTs.RatingPlan) && + reflect.DeepEqual(ts.MinuteInfo, otherTs.MinuteInfo) && reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { // extend the last timespan with ts.TimeEnd = ts.TimeEnd.Add(otherTs.GetDuration()) // add the rest of the timspans diff --git a/engine/callcost_test.go b/engine/callcost_test.go index 57e13c043..d1e9f364b 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -56,7 +56,7 @@ func TestMultipleResultMerge(t *testing.T) { if cc1.Cost != 60 { t.Errorf("expected 60 was %v", cc1.Cost) for _, ts := range cc1.Timespans { - t.Log(ts.Interval) + t.Log(ts.RateInterval) } } t1 = time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) @@ -66,7 +66,7 @@ func TestMultipleResultMerge(t *testing.T) { if cc2.Cost != 30 { t.Errorf("expected 30 was %v", cc2.Cost) for _, ts := range cc1.Timespans { - t.Log(ts.Interval) + t.Log(ts.RateInterval) } } cc1.Merge(cc2) diff --git a/engine/calldesc.go b/engine/calldesc.go index becd10a58..9211af136 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -107,13 +107,13 @@ type CallDescriptor struct { CallDuration time.Duration // the call duration so far (partial or final) Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject - ActivationPeriods []*ActivationPeriod + RatingPlans []*RatingPlan userBalance *UserBalance } // Adds an activation period that applyes to current call descriptor. -func (cd *CallDescriptor) AddActivationPeriod(aps ...*ActivationPeriod) { - cd.ActivationPeriods = append(cd.ActivationPeriods, aps...) +func (cd *CallDescriptor) AddRatingPlan(aps ...*RatingPlan) { + cd.RatingPlans = append(cd.RatingPlans, aps...) } // Returns the key used to retrive the user balance involved in this call @@ -136,28 +136,28 @@ func (cd *CallDescriptor) getUserBalance() (ub *UserBalance, err error) { /* Restores the activation periods for the specified prefix from storage. */ -func (cd *CallDescriptor) LoadActivationPeriods() (destPrefix string, err error) { +func (cd *CallDescriptor) LoadRatingPlans() (destPrefix string, err error) { if val, err := cache2go.GetXCached(cd.GetKey() + cd.Destination); err == nil { - xaps := val.(xCachedActivationPeriods) - cd.ActivationPeriods = xaps.aps + xaps := val.(xCachedRatingPlans) + cd.RatingPlans = xaps.aps return xaps.destPrefix, nil } - destPrefix, values, err := cd.getActivationPeriodsForPrefix(cd.GetKey(), 1) + destPrefix, values, err := cd.getRatingPlansForPrefix(cd.GetKey(), 1) if err != nil { fallbackKey := fmt.Sprintf("%s:%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, FALLBACK_SUBJECT) // use the default subject - destPrefix, values, err = cd.getActivationPeriodsForPrefix(fallbackKey, 1) + destPrefix, values, err = cd.getRatingPlansForPrefix(fallbackKey, 1) } //load the activation preriods if err == nil && len(values) > 0 { - xaps := xCachedActivationPeriods{destPrefix, values, new(cache2go.XEntry)} + xaps := xCachedRatingPlans{destPrefix, values, new(cache2go.XEntry)} xaps.XCache(cd.GetKey()+cd.Destination, debitPeriod+5*time.Second, xaps) - cd.ActivationPeriods = values + cd.RatingPlans = values } return } -func (cd *CallDescriptor) getActivationPeriodsForPrefix(key string, recursionDepth int) (foundPrefix string, aps []*ActivationPeriod, err error) { +func (cd *CallDescriptor) getRatingPlansForPrefix(key string, recursionDepth int) (foundPrefix string, aps []*RatingPlan, err error) { if recursionDepth > RECURSION_MAX_DEPTH { err = errors.New("Max fallback recursion depth reached!" + key) return @@ -166,12 +166,12 @@ func (cd *CallDescriptor) getActivationPeriodsForPrefix(key string, recursionDep if err != nil { return "", nil, err } - foundPrefix, aps, err = rp.GetActivationPeriodsForPrefix(cd.Destination) + foundPrefix, aps, err = rp.GetRatingPlansForPrefix(cd.Destination) if err != nil { if rp.FallbackKey != "" { recursionDepth++ for _, fbk := range strings.Split(rp.FallbackKey, FALLBACK_SEP) { - if destPrefix, values, err := cd.getActivationPeriodsForPrefix(fbk, recursionDepth); err == nil { + if destPrefix, values, err := cd.getRatingPlansForPrefix(fbk, recursionDepth); err == nil { return destPrefix, values, err } } @@ -217,23 +217,23 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti if firstSpan.MinuteInfo != nil { return // all the timespans are on minutes } - if len(cd.ActivationPeriods) == 0 { + if len(cd.RatingPlans) == 0 { return } - firstSpan.ActivationPeriod = cd.ActivationPeriods[0] + firstSpan.RatingPlan = cd.RatingPlans[0] // split on activation periods afterStart, afterEnd := false, false //optimization for multiple activation periods - for _, ap := range cd.ActivationPeriods { + for _, ap := range cd.RatingPlans { if !afterStart && !afterEnd && ap.ActivationTime.Before(cd.TimeStart) { - firstSpan.ActivationPeriod = ap + firstSpan.RatingPlan = ap } else { afterStart = true for i := 0; i < len(timespans); i++ { if timespans[i].MinuteInfo != nil { continue } - newTs := timespans[i].SplitByActivationPeriod(ap) + newTs := timespans[i].SplitByRatingPlan(ap) if newTs != nil { timespans = append(timespans, newTs) } else { @@ -248,16 +248,16 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti if timespans[i].MinuteInfo != nil { continue // cont try to split timespans payed with minutes } - ap := timespans[i].ActivationPeriod - //timespans[i].ActivationPeriod = nil - ap.Intervals.Sort() - for _, interval := range ap.Intervals { - if timespans[i].Interval != nil && timespans[i].Interval.Weight < interval.Weight { + ap := timespans[i].RatingPlan + //timespans[i].RatingPlan = nil + ap.RateIntervals.Sort() + for _, interval := range ap.RateIntervals { + if timespans[i].RateInterval != nil && timespans[i].RateInterval.Weight < interval.Weight { continue // if the timespan has an interval than it already has a heigher weight } - newTs := timespans[i].SplitByInterval(interval) + newTs := timespans[i].SplitByRateInterval(interval) if newTs != nil { - newTs.ActivationPeriod = ap + newTs.RatingPlan = ap timespans = append(timespans, newTs) } } @@ -266,15 +266,20 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti return } -// if the rate interval for any timespan has a RatingInterval larger than the timespan duration +// if the rate interval for any timespan has a RatingIncrement larger than the timespan duration // the timespan must expand potentially overlaping folowing timespans and may exceed call // descriptor's initial duration func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { for i, ts := range timespans { - if ts.Interval != nil { - _, rateIncrement, _ := ts.Interval.GetPriceParameters(ts.GetGroupStart()) + if ts.RateInterval != nil { + _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + // if the timespan duration is larger than the rate increment make sure it is a multiple of it + if rateIncrement < ts.GetDuration() { + rateIncrement = utils.RoundTo(rateIncrement, ts.GetDuration()) + } if rateIncrement > ts.GetDuration() { ts.TimeEnd = ts.TimeStart.Add(rateIncrement) + ts.SetNewCallDuration(ts) // set new call duration for this timespan // overlap the rest of the timespans for ; i < len(timespans); i++ { if timespans[i].TimeEnd.Before(ts.TimeEnd) { @@ -301,7 +306,7 @@ func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { Creates a CallCost structure with the cost information calculated for the received CallDescriptor. */ func (cd *CallDescriptor) GetCost() (*CallCost, error) { - destPrefix, err := cd.LoadActivationPeriods() + destPrefix, err := cd.LoadRatingPlans() if err != nil { Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err)) return &CallCost{Cost: -1}, err @@ -312,8 +317,8 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { for i, ts := range timespans { // only add connect fee if this is the first/only call cost request - if cd.LoopIndex == 0 && i == 0 && ts.MinuteInfo == nil && ts.Interval != nil { - connectionFee = ts.Interval.ConnectFee + if cd.LoopIndex == 0 && i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + connectionFee = ts.RateInterval.ConnectFee } cost += ts.getCost(cd) } @@ -339,7 +344,7 @@ If the user has no credit then it will return 0. If the user has postpayed plan it returns -1. */ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float64, err error) { - _, err = cd.LoadActivationPeriods() + _, err = cd.LoadRatingPlans() if err != nil { Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err)) return 0, err @@ -371,8 +376,8 @@ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float6 cost := 0.0 for i, ts := range timespans { - if i == 0 && ts.MinuteInfo == nil && ts.Interval != nil { - cost += ts.Interval.ConnectFee + if i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + cost += ts.RateInterval.ConnectFee } cost += ts.getCost(cd) } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 936c5e0b5..6871a8bca 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -68,10 +68,10 @@ func TestSplitSpans(t *testing.T) { t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cd.LoadActivationPeriods() + cd.LoadRatingPlans() timespans := cd.splitInTimeSpans(nil) if len(timespans) != 2 { - t.Log(cd.ActivationPeriods) + t.Log(cd.RatingPlans) t.Error("Wrong number of timespans: ", len(timespans)) } } @@ -116,7 +116,7 @@ func TestFullDestNotFound(t *testing.T) { result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { - t.Log(cd.ActivationPeriods) + t.Log(cd.RatingPlans) t.Errorf("Expected %v was %v", expected, result) } } @@ -128,12 +128,12 @@ func TestSubjectNotFound(t *testing.T) { result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { - t.Log(cd.ActivationPeriods) + t.Log(cd.RatingPlans) t.Errorf("Expected %v was %v", expected, result) } } -func TestMultipleActivationPeriods(t *testing.T) { +func TestMultipleRatingPlans(t *testing.T) { t1 := time.Date(2012, time.February, 8, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} @@ -145,7 +145,7 @@ func TestMultipleActivationPeriods(t *testing.T) { } } -func TestSpansMultipleActivationPeriods(t *testing.T) { +func TestSpansMultipleRatingPlans(t *testing.T) { t1 := time.Date(2012, time.February, 7, 23, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 0, 30, 0, 0, time.UTC) cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} @@ -242,7 +242,7 @@ func BenchmarkStorageRestoring(b *testing.B) { cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { - cd.LoadActivationPeriods() + cd.LoadRatingPlans() } } @@ -262,7 +262,7 @@ func BenchmarkSplitting(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} - cd.LoadActivationPeriods() + cd.LoadRatingPlans() b.StartTimer() for i := 0; i < b.N; i++ { cd.splitInTimeSpans(nil) diff --git a/engine/history_test.go b/engine/history_test.go index 976dba2c7..274c25289 100644 --- a/engine/history_test.go +++ b/engine/history_test.go @@ -36,19 +36,3 @@ func TestHistoryDestinations(t *testing.T) { t.Error("Error in destination history content:", scribe.DestBuf.String()) } } - -func TestHistoryratingProfiles(t *testing.T) { - scribe := historyScribe.(*history.MockScribe) - expected := `[{"Key":"*out:CUSTOMER_1:0:danb","Object":{"Id":"*out:CUSTOMER_1:0:danb","FallbackKey":"","DestinationMap":{"ALL":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:CUSTOMER_1:0:rif:from:tm","Object":{"Id":"*out:CUSTOMER_1:0:rif:from:tm","FallbackKey":"*out:CUSTOMER_1:0:danb","DestinationMap":{"ALL":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:CUSTOMER_2:0:danb:87.139.12.167","Object":{"Id":"*out:CUSTOMER_2:0:danb:87.139.12.167","FallbackKey":"*out:CUSTOMER_2:0:danb","DestinationMap":{"ALL":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:*any","Object":{"Id":"*out:vdf:0:*any","FallbackKey":"","DestinationMap":{"ALL":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:fall","Object":{"Id":"*out:vdf:0:fall","FallbackKey":"*out:vdf:0:rif","DestinationMap":{"ALL":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:inf","Object":{"Id":"*out:vdf:0:inf","FallbackKey":"*out:vdf:0:inf","DestinationMap":{"ALL":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:minu","Object":{"Id":"*out:vdf:0:minu","FallbackKey":"","DestinationMap":{"ALL":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:one","Object":{"Id":"*out:vdf:0:one","FallbackKey":"","DestinationMap":{"ALL":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.2,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.1,"RateIncrement":1000000000,"RateUnit":60000000000},{"GroupIntervalStart":0,"Value":0.05,"RateIncrement":1000000000,"RateUnit":60000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}} -{"Key":"*out:vdf:0:rif","Object":{"Id":"*out:vdf:0:rif","FallbackKey":"","DestinationMap":{"ALL":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_O2":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"GERMANY_PREMIUM":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"NAT":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}],"RET":[{"ActivationTime":"2012-01-01T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]},{"ActivationTime":"2012-02-28T00:00:00Z","Intervals":[{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":1,"Prices":[{"GroupIntervalStart":0,"Value":1,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[1,2,3,4,5],"StartTime":"18:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0},{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[6,0],"StartTime":"00:00:00","EndTime":"","Weight":10,"ConnectFee":0,"Prices":[{"GroupIntervalStart":0,"Value":0.5,"RateIncrement":1000000000,"RateUnit":1000000000}],"RoundingMethod":"","RoundingDecimals":0}]}]},"Tag":"","Tenant":"","TOR":"","Direction":"","Subject":"","DestRatesTimingTag":"","RatesFallbackSubject":"","ActivationTime":""}}]` - if scribe.RpBuf.String() != expected { - t.Error("Error in rating profiles history content:", scribe.RpBuf.String()) - } -} diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 12e9939f4..1413a8a64 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -39,9 +39,9 @@ type CSVReader struct { accountActions []*UserBalance destinations []*Destination timings map[string]*Timing - rates map[string]*Rate + rates map[string]*LoadRate destinationRates map[string][]*DestinationRate - activationPeriods map[string]*ActivationPeriod + activationPeriods map[string]*RatingPlan ratingProfiles map[string]*RatingProfile // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, @@ -55,10 +55,10 @@ func NewFileCSVReader(storage DataStorage, sep rune, destinationsFn, timingsFn, c.actions = make(map[string][]*Action) c.actionsTimings = make(map[string][]*ActionTiming) c.actionsTriggers = make(map[string][]*ActionTrigger) - c.rates = make(map[string]*Rate) + c.rates = make(map[string]*LoadRate) c.destinationRates = make(map[string][]*DestinationRate) c.timings = make(map[string]*Timing) - c.activationPeriods = make(map[string]*ActivationPeriod) + c.activationPeriods = make(map[string]*RatingPlan) c.ratingProfiles = make(map[string]*RatingProfile) c.readerFunc = openFileCSVReader c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, @@ -223,8 +223,8 @@ func (csvr *CSVReader) LoadRates() (err error) { } for record, err := csvReader.Read(); err == nil; record, err = csvReader.Read() { tag := record[0] - var r *Rate - r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8]) + var r *LoadRate + r, err = NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8]) if err != nil { return err } @@ -285,9 +285,9 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { } for _, dr := range drs { if _, exists := csvr.activationPeriods[tag]; !exists { - csvr.activationPeriods[tag] = &ActivationPeriod{} + csvr.activationPeriods[tag] = &RatingPlan{} } - csvr.activationPeriods[tag].AddInterval(rt.GetInterval(dr)) + csvr.activationPeriods[tag].AddRateInterval(rt.GetRateInterval(dr)) } } return @@ -320,10 +320,10 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not load ratinTiming for tag: %v", record[5])) } - newAP := &ActivationPeriod{ActivationTime: at} + newAP := &RatingPlan{ActivationTime: at} //copy(newAP.Intervals, ap.Intervals) - newAP.Intervals = append(newAP.Intervals, ap.Intervals...) - rp.AddActivationPeriodIfNotPresent(d.Id, newAP) + newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) + rp.AddRatingPlanIfNotPresent(d.Id, newAP) if fallbacksubject != "" { rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject) } @@ -411,7 +411,7 @@ func (csvr *CSVReader) LoadActionTimings() (err error) { Id: utils.GenUUID(), Tag: record[2], Weight: weight, - Timing: &Interval{ + Timing: &RateInterval{ Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, diff --git a/engine/loader_db.go b/engine/loader_db.go index 4c55a7030..2141e7c46 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -35,9 +35,9 @@ type DbReader struct { accountActions []*UserBalance destinations []*Destination timings map[string]*Timing - rates map[string]*Rate + rates map[string]*LoadRate destinationRates map[string][]*DestinationRate - activationPeriods map[string]*ActivationPeriod + activationPeriods map[string]*RatingPlan ratingProfiles map[string]*RatingProfile } @@ -46,7 +46,7 @@ func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader c.storDb = storDB c.dataDb = storage c.tpid = tpid - c.activationPeriods = make(map[string]*ActivationPeriod) + c.activationPeriods = make(map[string]*RatingPlan) c.actionsTimings = make(map[string][]*ActionTiming) return c } @@ -169,9 +169,9 @@ func (dbr *DbReader) LoadDestinationRateTimings() error { for _, dr := range drs { _, exists := dbr.activationPeriods[rt.Tag] if !exists { - dbr.activationPeriods[rt.Tag] = &ActivationPeriod{} + dbr.activationPeriods[rt.Tag] = &RatingPlan{} } - dbr.activationPeriods[rt.Tag].AddInterval(rt.GetInterval(dr)) + dbr.activationPeriods[rt.Tag].AddRateInterval(rt.GetRateInterval(dr)) } } return nil @@ -192,10 +192,10 @@ func (dbr *DbReader) LoadRatingProfiles() error { if !exists { return errors.New(fmt.Sprintf("Could not load rating timing for tag: %v", rp.DestRatesTimingTag)) } - newAP := &ActivationPeriod{ActivationTime: at} + newAP := &RatingPlan{ActivationTime: at} //copy(newAP.Intervals, ap.Intervals) - newAP.Intervals = append(newAP.Intervals, ap.Intervals...) - rp.AddActivationPeriodIfNotPresent(d.Id, newAP) + newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) + rp.AddRatingPlanIfNotPresent(d.Id, newAP) } } @@ -203,7 +203,7 @@ func (dbr *DbReader) LoadRatingProfiles() error { } func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { - activationPeriods := make(map[string]*ActivationPeriod) + activationPeriods := make(map[string]*RatingPlan) resultRatingProfile := &RatingProfile{} rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag) if err != nil || len(rpm) == 0 { @@ -242,9 +242,9 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { Logger.Debug(fmt.Sprintf("Rate: %v", rpm)) drate.Rate = rt[drate.RateTag] if _, exists := activationPeriods[destrateTiming.Tag]; !exists { - activationPeriods[destrateTiming.Tag] = &ActivationPeriod{} + activationPeriods[destrateTiming.Tag] = &RatingPlan{} } - activationPeriods[destrateTiming.Tag].AddInterval(destrateTiming.GetInterval(drate)) + activationPeriods[destrateTiming.Tag].AddRateInterval(destrateTiming.GetRateInterval(drate)) dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag) if err != nil || len(dm) == 0 { return fmt.Errorf("Could not get destination id %s: %v", drate.DestinationsTag, err) @@ -253,9 +253,9 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { for _, destination := range dm { Logger.Debug(fmt.Sprintf("Destination: %v", rpm)) ap := activationPeriods[ratingProfile.DestRatesTimingTag] - newAP := &ActivationPeriod{ActivationTime: at} - newAP.Intervals = append(newAP.Intervals, ap.Intervals...) - resultRatingProfile.AddActivationPeriodIfNotPresent(destination.Id, newAP) + newAP := &RatingPlan{ActivationTime: at} + newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) + resultRatingProfile.AddRatingPlanIfNotPresent(destination.Id, newAP) dbr.dataDb.SetDestination(destination) } } @@ -288,7 +288,7 @@ func (dbr *DbReader) LoadActionTimings() (err error) { Id: utils.GenUUID(), Tag: at.Tag, Weight: at.Weight, - Timing: &Interval{ + Timing: &RateInterval{ Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, @@ -387,7 +387,7 @@ func (dbr *DbReader) LoadAccountActionsByTag(tag string) error { Id: utils.GenUUID(), Tag: at.Tag, Weight: at.Weight, - Timing: &Interval{ + Timing: &RateInterval{ Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 03e97247d..393e04f86 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -46,7 +46,7 @@ type TPLoader interface { WriteToDatabase(bool, bool) error } -type Rate struct { +type LoadRate struct { Tag string ConnectFee, Price float64 RateUnit, RateIncrement, GroupIntervalStart time.Duration @@ -55,7 +55,7 @@ type Rate struct { Weight float64 } -func NewRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterval, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) { +func NewLoadRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterval, roundingMethod, roundingDecimals, weight string) (r *LoadRate, err error) { cf, err := strconv.ParseFloat(connectFee, 64) if err != nil { log.Printf("Error parsing connect fee from: %v", connectFee) @@ -92,7 +92,7 @@ func NewRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterval, return } - r = &Rate{ + r = &LoadRate{ Tag: tag, ConnectFee: cf, Price: p, @@ -110,7 +110,7 @@ type DestinationRate struct { Tag string DestinationsTag string RateTag string - Rate *Rate + Rate *LoadRate } type Timing struct { @@ -155,8 +155,8 @@ func NewDestinationRateTiming(destinationRatesTag string, timing *Timing, weight return } -func (rt *DestinationRateTiming) GetInterval(dr *DestinationRate) (i *Interval) { - i = &Interval{ +func (rt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { + i = &RateInterval{ Years: rt.timing.Years, Months: rt.timing.Months, MonthDays: rt.timing.MonthDays, @@ -164,7 +164,7 @@ func (rt *DestinationRateTiming) GetInterval(dr *DestinationRate) (i *Interval) StartTime: rt.timing.StartTime, Weight: rt.Weight, ConnectFee: dr.Rate.ConnectFee, - Prices: PriceGroups{&Price{ + Rates: RateGroups{&Rate{ GroupIntervalStart: dr.Rate.GroupIntervalStart, Value: dr.Rate.Price, RateIncrement: dr.Rate.RateIncrement, diff --git a/engine/interval.go b/engine/rateinterval.go similarity index 79% rename from engine/interval.go rename to engine/rateinterval.go index 1181a430b..db8c8b0fe 100644 --- a/engine/interval.go +++ b/engine/rateinterval.go @@ -32,48 +32,48 @@ import ( /* Defines a time interval for which a certain set of prices will apply */ -type Interval struct { +type RateInterval struct { Years Years Months Months MonthDays MonthDays WeekDays WeekDays StartTime, EndTime string // ##:##:## format Weight, ConnectFee float64 - Prices PriceGroups // GroupInterval (start time): Price + Rates RateGroups // GroupRateInterval (start time): Rate RoundingMethod string RoundingDecimals int } -type Price struct { +type Rate struct { GroupIntervalStart time.Duration Value float64 RateIncrement time.Duration RateUnit time.Duration } -func (p *Price) Equal(o *Price) bool { +func (p *Rate) Equal(o *Rate) bool { return p.GroupIntervalStart == o.GroupIntervalStart && p.Value == o.Value && p.RateIncrement == o.RateIncrement && p.RateUnit == o.RateUnit } -type PriceGroups []*Price +type RateGroups []*Rate -func (pg PriceGroups) Len() int { +func (pg RateGroups) Len() int { return len(pg) } -func (pg PriceGroups) Swap(i, j int) { +func (pg RateGroups) Swap(i, j int) { pg[i], pg[j] = pg[j], pg[i] } -func (pg PriceGroups) Less(i, j int) bool { +func (pg RateGroups) Less(i, j int) bool { return pg[i].GroupIntervalStart < pg[j].GroupIntervalStart } -func (pg PriceGroups) Sort() { +func (pg RateGroups) Sort() { sort.Sort(pg) } -func (pg PriceGroups) Equal(og PriceGroups) bool { +func (pg RateGroups) Equal(og RateGroups) bool { if len(pg) != len(og) { return false } @@ -85,7 +85,7 @@ func (pg PriceGroups) Equal(og PriceGroups) bool { return true } -func (pg *PriceGroups) AddPrice(ps ...*Price) { +func (pg *RateGroups) AddRate(ps ...*Rate) { for _, p := range ps { found := false for _, op := range *pg { @@ -103,7 +103,7 @@ func (pg *PriceGroups) AddPrice(ps ...*Price) { /* Returns true if the received time result inside the interval */ -func (i *Interval) Contains(t time.Time) bool { +func (i *RateInterval) Contains(t time.Time) bool { // check for years if len(i.Years) > 0 && !i.Years.Contains(t.Year()) { return false @@ -152,7 +152,7 @@ func (i *Interval) Contains(t time.Time) bool { /* Returns a time object that represents the end of the interval realtive to the received time */ -func (i *Interval) getRightMargin(t time.Time) (rigthtTime time.Time) { +func (i *RateInterval) getRightMargin(t time.Time) (rigthtTime time.Time) { year, month, day := t.Year(), t.Month(), t.Day() hour, min, sec, nsec := 23, 59, 59, 0 loc := t.Location() @@ -168,7 +168,7 @@ func (i *Interval) getRightMargin(t time.Time) (rigthtTime time.Time) { /* Returns a time object that represents the start of the interval realtive to the received time */ -func (i *Interval) getLeftMargin(t time.Time) (rigthtTime time.Time) { +func (i *RateInterval) getLeftMargin(t time.Time) (rigthtTime time.Time) { year, month, day := t.Year(), t.Month(), t.Day() hour, min, sec, nsec := 0, 0, 0, 0 loc := t.Location() @@ -181,11 +181,11 @@ func (i *Interval) getLeftMargin(t time.Time) (rigthtTime time.Time) { return time.Date(year, month, day, hour, min, sec, nsec, loc) } -func (i *Interval) String() string { +func (i *RateInterval) String() string { return fmt.Sprintf("%v %v %v %v %v %v", i.Years, i.Months, i.MonthDays, i.WeekDays, i.StartTime, i.EndTime) } -func (i *Interval) Equal(o *Interval) bool { +func (i *RateInterval) Equal(o *RateInterval) bool { return reflect.DeepEqual(i.Years, o.Years) && reflect.DeepEqual(i.Months, o.Months) && reflect.DeepEqual(i.MonthDays, o.MonthDays) && @@ -194,8 +194,8 @@ func (i *Interval) Equal(o *Interval) bool { i.EndTime == o.EndTime } -func (i *Interval) GetCost(duration, startSecond time.Duration) (cost float64) { - price, rateIncrement, rateUnit := i.GetPriceParameters(startSecond) +func (i *RateInterval) GetCost(duration, startSecond time.Duration) (cost float64) { + price, rateIncrement, rateUnit := i.GetRateParameters(startSecond) d := float64(duration.Seconds()) price /= rateUnit.Seconds() ri := rateIncrement.Seconds() @@ -204,11 +204,11 @@ func (i *Interval) GetCost(duration, startSecond time.Duration) (cost float64) { } // Gets the price for a the provided start second -func (i *Interval) GetPriceParameters(startSecond time.Duration) (price float64, rateIncrement, rateUnit time.Duration) { - i.Prices.Sort() - for index, price := range i.Prices { - if price.GroupIntervalStart <= startSecond && (index == len(i.Prices)-1 || - i.Prices[index+1].GroupIntervalStart > startSecond) { +func (i *RateInterval) GetRateParameters(startSecond time.Duration) (price float64, rateIncrement, rateUnit time.Duration) { + i.Rates.Sort() + for index, price := range i.Rates { + if price.GroupIntervalStart <= startSecond && (index == len(i.Rates)-1 || + i.Rates[index+1].GroupIntervalStart > startSecond) { if price.RateIncrement == 0 { price.RateIncrement = 1 * time.Second } @@ -222,20 +222,20 @@ func (i *Interval) GetPriceParameters(startSecond time.Duration) (price float64, } // Structure to store intervals according to weight -type IntervalList []*Interval +type RateIntervalList []*RateInterval -func (il IntervalList) Len() int { +func (il RateIntervalList) Len() int { return len(il) } -func (il IntervalList) Swap(i, j int) { +func (il RateIntervalList) Swap(i, j int) { il[i], il[j] = il[j], il[i] } -func (il IntervalList) Less(i, j int) bool { +func (il RateIntervalList) Less(i, j int) bool { return il[i].Weight < il[j].Weight } -func (il IntervalList) Sort() { +func (il RateIntervalList) Sort() { sort.Sort(il) } diff --git a/engine/interval_test.go b/engine/rateinterval_test.go similarity index 75% rename from engine/interval_test.go rename to engine/rateinterval_test.go index 6ea61ad73..e3daf02e3 100644 --- a/engine/interval_test.go +++ b/engine/rateinterval_test.go @@ -23,8 +23,8 @@ import ( "time" ) -func TestIntervalMonth(t *testing.T) { - i := &Interval{Months: Months{time.February}} +func TestRateIntervalMonth(t *testing.T) { + i := &RateInterval{Months: Months{time.February}} d := time.Date(2012, time.February, 10, 23, 0, 0, 0, time.UTC) d1 := time.Date(2012, time.January, 10, 23, 0, 0, 0, time.UTC) if !i.Contains(d) { @@ -35,8 +35,8 @@ func TestIntervalMonth(t *testing.T) { } } -func TestIntervalMonthDay(t *testing.T) { - i := &Interval{MonthDays: MonthDays{10}} +func TestRateIntervalMonthDay(t *testing.T) { + i := &RateInterval{MonthDays: MonthDays{10}} d := time.Date(2012, time.February, 10, 23, 0, 0, 0, time.UTC) d1 := time.Date(2012, time.February, 11, 23, 0, 0, 0, time.UTC) if !i.Contains(d) { @@ -47,8 +47,8 @@ func TestIntervalMonthDay(t *testing.T) { } } -func TestIntervalMonthAndMonthDay(t *testing.T) { - i := &Interval{Months: Months{time.February}, MonthDays: MonthDays{10}} +func TestRateIntervalMonthAndMonthDay(t *testing.T) { + i := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{10}} d := time.Date(2012, time.February, 10, 23, 0, 0, 0, time.UTC) d1 := time.Date(2012, time.February, 11, 23, 0, 0, 0, time.UTC) d2 := time.Date(2012, time.January, 10, 23, 0, 0, 0, time.UTC) @@ -63,9 +63,9 @@ func TestIntervalMonthAndMonthDay(t *testing.T) { } } -func TestIntervalWeekDays(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Wednesday}} - i2 := &Interval{WeekDays: []time.Weekday{time.Wednesday, time.Thursday}} +func TestRateIntervalWeekDays(t *testing.T) { + i := &RateInterval{WeekDays: []time.Weekday{time.Wednesday}} + i2 := &RateInterval{WeekDays: []time.Weekday{time.Wednesday, time.Thursday}} d := time.Date(2012, time.February, 1, 23, 0, 0, 0, time.UTC) d1 := time.Date(2012, time.February, 2, 23, 0, 0, 0, time.UTC) if !i.Contains(d) { @@ -82,9 +82,9 @@ func TestIntervalWeekDays(t *testing.T) { } } -func TestIntervalMonthAndMonthDayAndWeekDays(t *testing.T) { - i := &Interval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday}} - i2 := &Interval{Months: Months{time.February}, MonthDays: MonthDays{2}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}} +func TestRateIntervalMonthAndMonthDayAndWeekDays(t *testing.T) { + i := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday}} + i2 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{2}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}} d := time.Date(2012, time.February, 1, 23, 0, 0, 0, time.UTC) d1 := time.Date(2012, time.February, 2, 23, 0, 0, 0, time.UTC) if !i.Contains(d) { @@ -101,8 +101,8 @@ func TestIntervalMonthAndMonthDayAndWeekDays(t *testing.T) { } } -func TestIntervalHours(t *testing.T) { - i := &Interval{StartTime: "14:30:00", EndTime: "15:00:00"} +func TestRateIntervalHours(t *testing.T) { + i := &RateInterval{StartTime: "14:30:00", EndTime: "15:00:00"} d := time.Date(2012, time.February, 10, 14, 30, 1, 0, time.UTC) d1 := time.Date(2012, time.January, 10, 14, 29, 0, 0, time.UTC) d2 := time.Date(2012, time.January, 10, 14, 59, 0, 0, time.UTC) @@ -121,8 +121,8 @@ func TestIntervalHours(t *testing.T) { } } -func TestIntervalEverything(t *testing.T) { - i := &Interval{Months: Months{time.February}, +func TestRateIntervalEverything(t *testing.T) { + i := &RateInterval{Months: Months{time.February}, Years: Years{2012}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, @@ -150,13 +150,13 @@ func TestIntervalEverything(t *testing.T) { } } -func TestIntervalEqual(t *testing.T) { - i1 := &Interval{Months: Months{time.February}, +func TestRateIntervalEqual(t *testing.T) { + i1 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - i2 := &Interval{Months: Months{time.February}, + i2 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", @@ -166,13 +166,13 @@ func TestIntervalEqual(t *testing.T) { } } -func TestIntervalNotEqual(t *testing.T) { - i1 := &Interval{Months: Months{time.February}, +func TestRateIntervalNotEqual(t *testing.T) { + i1 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday}, StartTime: "14:30:00", EndTime: "15:00:00"} - i2 := &Interval{Months: Months{time.February}, + i2 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", @@ -182,13 +182,13 @@ func TestIntervalNotEqual(t *testing.T) { } } -func TestIntervalGetCost(t *testing.T) { +func TestRateIntervalGetCost(t *testing.T) { } /*********************************Benchmarks**************************************/ -func BenchmarkIntervalContainsDate(b *testing.B) { - i := &Interval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} +func BenchmarkRateIntervalContainsDate(b *testing.B) { + i := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} d := time.Date(2012, time.February, 1, 14, 30, 0, 0, time.UTC) for x := 0; x < b.N; x++ { i.Contains(d) diff --git a/engine/activationperiod.go b/engine/ratingplan.go similarity index 75% rename from engine/activationperiod.go rename to engine/ratingplan.go index deed2c518..5e14bf74f 100644 --- a/engine/activationperiod.go +++ b/engine/ratingplan.go @@ -26,36 +26,36 @@ import ( /* The struture that is saved to storage. */ -type ActivationPeriod struct { +type RatingPlan struct { ActivationTime time.Time - Intervals IntervalList + RateIntervals RateIntervalList } -type xCachedActivationPeriods struct { +type xCachedRatingPlans struct { destPrefix string - aps []*ActivationPeriod + aps []*RatingPlan *cache2go.XEntry } /* Adds one ore more intervals to the internal interval list only if it is not allready in the list. */ -func (ap *ActivationPeriod) AddInterval(is ...*Interval) { +func (ap *RatingPlan) AddRateInterval(is ...*RateInterval) { for _, i := range is { found := false - for _, ei := range ap.Intervals { + for _, ei := range ap.RateIntervals { if i.Equal(ei) { - (&ei.Prices).AddPrice(i.Prices...) + (&ei.Rates).AddRate(i.Rates...) found = true break } } if !found { - ap.Intervals = append(ap.Intervals, i) + ap.RateIntervals = append(ap.RateIntervals, i) } } } -func (ap *ActivationPeriod) Equal(o *ActivationPeriod) bool { +func (ap *RatingPlan) Equal(o *RatingPlan) bool { return ap.ActivationTime == o.ActivationTime } diff --git a/engine/activationperiod_test.go b/engine/ratingplan_test.go similarity index 61% rename from engine/activationperiod_test.go rename to engine/ratingplan_test.go index c1aff6236..ef007ba93 100644 --- a/engine/activationperiod_test.go +++ b/engine/ratingplan_test.go @@ -32,23 +32,23 @@ func TestApRestoreFromStorage(t *testing.T) { Tenant: "CUSTOMER_1", Subject: "rif:from:tm", Destination: "49"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 2 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 2 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestApStoreRestoreJson(t *testing.T) { d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) result, _ := json.Marshal(ap) - ap1 := &ActivationPeriod{} + ap1 := &RatingPlan{} json.Unmarshal(result, ap1) if !reflect.DeepEqual(ap, ap1) { t.Errorf("Expected %v was %v", ap, ap1) @@ -57,11 +57,11 @@ func TestApStoreRestoreJson(t *testing.T) { func TestApStoreRestoreBlank(t *testing.T) { d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + i := &RateInterval{} + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) result, _ := json.Marshal(ap) - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} json.Unmarshal(result, &ap1) if reflect.DeepEqual(ap, ap1) { t.Errorf("Expected %v was %v", ap, ap1) @@ -70,116 +70,116 @@ func TestApStoreRestoreBlank(t *testing.T) { func TestFallbackDirect(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 1 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestFallbackMultiple(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 1 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestFallbackWithBackTrace(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 1 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestFallbackDefault(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "one", Destination: "0723"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 1 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 1 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestFallbackNoInfiniteLoop(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "rif", Destination: "0721"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 0 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 0 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestFallbackNoInfiniteLoopSelf(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "inf", Destination: "0721"} - cd.LoadActivationPeriods() - if len(cd.ActivationPeriods) != 0 { - t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) + cd.LoadRatingPlans() + if len(cd.RatingPlans) != 0 { + t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) } } func TestApAddIntervalIfNotPresent(t *testing.T) { - i1 := &Interval{Months: Months{time.February}, + i1 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - i2 := &Interval{Months: Months{time.February}, + i2 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - i3 := &Interval{Months: Months{time.February}, + i3 := &RateInterval{Months: Months{time.February}, MonthDays: MonthDays{1}, WeekDays: []time.Weekday{time.Wednesday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{} - ap.AddInterval(i1) - ap.AddInterval(i2) - if len(ap.Intervals) != 1 { + ap := &RatingPlan{} + ap.AddRateInterval(i1) + ap.AddRateInterval(i2) + if len(ap.RateIntervals) != 1 { t.Error("Wronfully appended interval ;)") } - ap.AddInterval(i3) - if len(ap.Intervals) != 2 { + ap.AddRateInterval(i3) + if len(ap.RateIntervals) != 2 { t.Error("Wronfully not appended interval ;)") } } -func TestApAddIntervalGroups(t *testing.T) { - i1 := &Interval{ - Prices: PriceGroups{&Price{0, 1, 1 * time.Second, 1 * time.Second}}, +func TestApAddRateIntervalGroups(t *testing.T) { + i1 := &RateInterval{ + Rates: RateGroups{&Rate{0, 1, 1 * time.Second, 1 * time.Second}}, } - i2 := &Interval{ - Prices: PriceGroups{&Price{30 * time.Second, 2, 1 * time.Second, 1 * time.Second}}, + i2 := &RateInterval{ + Rates: RateGroups{&Rate{30 * time.Second, 2, 1 * time.Second, 1 * time.Second}}, } - i3 := &Interval{ - Prices: PriceGroups{&Price{30 * time.Second, 2, 1 * time.Second, 1 * time.Second}}, + i3 := &RateInterval{ + Rates: RateGroups{&Rate{30 * time.Second, 2, 1 * time.Second, 1 * time.Second}}, } - ap := &ActivationPeriod{} - ap.AddInterval(i1) - ap.AddInterval(i2) - ap.AddInterval(i3) - if len(ap.Intervals) != 1 { + ap := &RatingPlan{} + ap.AddRateInterval(i1) + ap.AddRateInterval(i2) + ap.AddRateInterval(i3) + if len(ap.RateIntervals) != 1 { t.Error("Wronfully appended interval ;)") } - if len(ap.Intervals[0].Prices) != 2 { - t.Error("Group prices not formed: ", ap.Intervals[0].Prices) + if len(ap.RateIntervals[0].Rates) != 2 { + t.Error("Group prices not formed: ", ap.RateIntervals[0].Rates) } } /**************************** Benchmarks *************************************/ -func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) { +func BenchmarkRatingPlanStoreRestoreJson(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} b.StartTimer() for i := 0; i < b.N; i++ { result, _ := json.Marshal(ap) @@ -187,18 +187,18 @@ func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) { } } -func BenchmarkActivationPeriodStoreRestore(b *testing.B) { +func BenchmarkRatingPlanStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) - ap1 := &ActivationPeriod{} + ap1 := &RatingPlan{} b.StartTimer() for i := 0; i < b.N; i++ { result, _ := marsh.Marshal(ap) diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 664f139f2..0c243de0f 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -26,14 +26,14 @@ import ( type RatingProfile struct { Id string FallbackKey string // FallbackKey is used as complete combination of Tenant:TOR:Direction:Subject - DestinationMap map[string][]*ActivationPeriod + DestinationMap map[string][]*RatingPlan Tag, Tenant, TOR, Direction, Subject, DestRatesTimingTag, RatesFallbackSubject, ActivationTime string // used only for loading } // Adds an activation period that applyes to current rating profile if not already present. -func (rp *RatingProfile) AddActivationPeriodIfNotPresent(destInfo string, aps ...*ActivationPeriod) { +func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, aps ...*RatingPlan) { if rp.DestinationMap == nil { - rp.DestinationMap = make(map[string][]*ActivationPeriod, 1) + rp.DestinationMap = make(map[string][]*RatingPlan, 1) } for _, ap := range aps { found := false @@ -49,7 +49,7 @@ func (rp *RatingProfile) AddActivationPeriodIfNotPresent(destInfo string, aps .. } } -func (rp *RatingProfile) GetActivationPeriodsForPrefix(destPrefix string) (foundPrefix string, aps []*ActivationPeriod, err error) { +func (rp *RatingProfile) GetRatingPlansForPrefix(destPrefix string) (foundPrefix string, aps []*RatingPlan, err error) { bestPrecision := 0 for k, v := range rp.DestinationMap { d, err := GetDestination(k) diff --git a/engine/ratingprofile_test.go b/engine/ratingprofile_test.go index 76e0f78e3..cd7afc6cc 100644 --- a/engine/ratingprofile_test.go +++ b/engine/ratingprofile_test.go @@ -24,16 +24,16 @@ import ( ) func TestRpAddAPIfNotPresent(t *testing.T) { - ap1 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} - ap2 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} - ap3 := &ActivationPeriod{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 1, time.UTC)} + ap1 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} + ap2 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 0, time.UTC)} + ap3 := &RatingPlan{ActivationTime: time.Date(2012, time.July, 2, 14, 24, 30, 1, time.UTC)} rp := &RatingProfile{} - rp.AddActivationPeriodIfNotPresent("test", ap1) - rp.AddActivationPeriodIfNotPresent("test", ap2) + rp.AddRatingPlanIfNotPresent("test", ap1) + rp.AddRatingPlanIfNotPresent("test", ap2) if len(rp.DestinationMap["test"]) != 1 { t.Error("Wronfully appended activation period ;)", len(rp.DestinationMap["test"])) } - rp.AddActivationPeriodIfNotPresent("test", ap3) + rp.AddRatingPlanIfNotPresent("test", ap3) if len(rp.DestinationMap["test"]) != 2 { t.Error("Wronfully not appended activation period ;)", len(rp.DestinationMap["test"])) } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 2dd07f493..161c5c930 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -110,7 +110,7 @@ type LoadStorage interface { GetTPDestination(string, string) (*Destination, error) GetTPDestinationIds(string) ([]string, error) ExistsTPRate(string, string) (bool, error) - SetTPRates(string, map[string][]*Rate) error + SetTPRates(string, map[string][]*LoadRate) error GetTPRate(string, string) (*utils.TPRate, error) GetTPRateIds(string) ([]string, error) ExistsTPDestinationRate(string, string) (bool, error) @@ -142,7 +142,7 @@ type LoadStorage interface { // loader functions GetTpDestinations(string, string) ([]*Destination, error) GetTpTimings(string, string) (map[string]*Timing, error) - GetTpRates(string, string) (map[string]*Rate, error) + GetTpRates(string, string) (map[string]*LoadRate, error) GetTpDestinationRates(string, string) (map[string][]*DestinationRate, error) GetTpDestinationRateTimings(string, string) ([]*DestinationRateTiming, error) GetTpRatingProfiles(string, string) (map[string]*RatingProfile, error) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 2668849d8..13d13f1a3 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -192,7 +192,7 @@ func (self *SQLStorage) ExistsTPRate(tpid, rtId string) (bool, error) { return exists, nil } -func (self *SQLStorage) SetTPRates(tpid string, rts map[string][]*Rate) error { +func (self *SQLStorage) SetTPRates(tpid string, rts map[string][]*LoadRate) error { if len(rts) == 0 { return nil //Nothing to set } @@ -541,7 +541,7 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err } qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s','%s',%f,%f,%f)", tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Balance.Value, act.ExpirationString, - act.DestinationTag, act.RateType, act.RateValue, act.MinutesWeight, act.Weight) + act.Balance.DestinationId, act.Balance.SpecialPriceType, act.Balance.SpecialPrice, act.Balance.Weight, act.Weight) i++ } } @@ -921,8 +921,8 @@ func (self *SQLStorage) GetTpDestinations(tpid, tag string) ([]*Destination, err return dests, nil } -func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { - rts := make(map[string]*Rate) +func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*LoadRate, error) { + rts := make(map[string]*LoadRate) q := fmt.Sprintf("SELECT tag, connect_fee, rate, rate_unit, rate_increment, group_interval_start, rounding_method, rounding_decimals, weight FROM %s WHERE tpid='%s' ", utils.TBL_TP_RATES, tpid) if tag != "" { q += fmt.Sprintf(" AND tag='%s'", tag) @@ -940,7 +940,7 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { if err := rows.Scan(&tag, &connect_fee, &rate, &rate_unit, &rate_increment, &group_interval_start, &roundingMethod, &roundingDecimals, &weight); err != nil { return nil, err } - r := &Rate{ + r := &LoadRate{ Tag: tag, ConnectFee: connect_fee, Price: rate, diff --git a/engine/storage_test.go b/engine/storage_test.go index e77d99bf5..4a297a4b4 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -104,16 +104,16 @@ func GetUB() *UserBalance { func BenchmarkMarshallerJSONStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := new(JSONMarshaler) @@ -128,16 +128,16 @@ func BenchmarkMarshallerJSONStoreRestore(b *testing.B) { func BenchmarkMarshallerBSONStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := new(BSONMarshaler) @@ -152,16 +152,16 @@ func BenchmarkMarshallerBSONStoreRestore(b *testing.B) { func BenchmarkMarshallerJSONBufStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := new(JSONBufMarshaler) @@ -176,16 +176,16 @@ func BenchmarkMarshallerJSONBufStoreRestore(b *testing.B) { func BenchmarkMarshallerGOBStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := new(GOBMarshaler) @@ -200,16 +200,16 @@ func BenchmarkMarshallerGOBStoreRestore(b *testing.B) { func BenchmarkMarshallerMsgpackStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := new(MsgpackMarshaler) @@ -225,16 +225,16 @@ func BenchmarkMarshallerMsgpackStoreRestore(b *testing.B) { func BenchmarkMarshallerCodecMsgpackStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := NewCodecMsgpackMarshaler() @@ -249,16 +249,16 @@ func BenchmarkMarshallerCodecMsgpackStoreRestore(b *testing.B) { func BenchmarkMarshallerBincStoreRestore(b *testing.B) { b.StopTimer() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Months: []time.Month{time.February}, + i := &RateInterval{Months: []time.Month{time.February}, MonthDays: []int{1}, WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, StartTime: "14:30:00", EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) + ap := &RatingPlan{ActivationTime: d} + ap.AddRateInterval(i) ub := GetUB() - ap1 := ActivationPeriod{} + ap1 := RatingPlan{} ub1 := &UserBalance{} b.StartTimer() ms := NewBincMarshaler() diff --git a/engine/timespans.go b/engine/timespans.go index 4feaa626d..b2abd493a 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -29,8 +29,8 @@ A unit in which a call will be split that has a specific price related interval type TimeSpan struct { TimeStart, TimeEnd time.Time Cost float64 - ActivationPeriod *ActivationPeriod - Interval *Interval + RatingPlan *RatingPlan + RateInterval *RateInterval MinuteInfo *MinuteInfo CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one @@ -43,9 +43,7 @@ type MinuteInfo struct { Price float64 } -/* -Returns the duration of the timespan -*/ +// Returns the duration of the timespan func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) } @@ -57,18 +55,11 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { if ts.MinuteInfo != nil { return ts.GetDuration().Seconds() * ts.MinuteInfo.Price } - if ts.Interval == nil { + if ts.RateInterval == nil { return 0 } - i := ts.Interval + i := ts.RateInterval cost = i.GetCost(ts.GetDuration(), ts.GetGroupStart()) - // if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { - // userBalance.mux.RLock() - // if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 { - // cost *= (100 - percentageDiscount) / 100 - // } - // userBalance.mux.RUnlock() - // } ts.Cost = cost return } @@ -84,15 +75,15 @@ func (ts *TimeSpan) Contains(t time.Time) bool { Will set the interval as spans's interval if new Weight is lower then span's interval Weight or if the Weights are equal and new price is lower then spans's interval price */ -func (ts *TimeSpan) SetInterval(i *Interval) { - if ts.Interval == nil || ts.Interval.Weight < i.Weight { - ts.Interval = i +func (ts *TimeSpan) SetRateInterval(i *RateInterval) { + if ts.RateInterval == nil || ts.RateInterval.Weight < i.Weight { + ts.RateInterval = i return } - iPrice, _, _ := i.GetPriceParameters(ts.GetGroupStart()) - tsPrice, _, _ := ts.Interval.GetPriceParameters(ts.GetGroupStart()) - if ts.Interval.Weight == i.Weight && iPrice < tsPrice { - ts.Interval = i + iPrice, _, _ := i.GetRateParameters(ts.GetGroupStart()) + tsPrice, _, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + if ts.RateInterval.Weight == i.Weight && iPrice < tsPrice { + ts.RateInterval = i } } @@ -102,7 +93,7 @@ It will modify the endtime of the received timespan and it will return a new timespan starting from the end of the received one. The interval will attach itself to the timespan that overlaps the interval. */ -func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { +func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { //Logger.Debug("here: ", ts, " +++ ", i) // if the span is not in interval return nil @@ -111,14 +102,14 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { return } // split by GroupStart - i.Prices.Sort() - for _, price := range i.Prices { + i.Rates.Sort() + for _, price := range i.Rates { if ts.GetGroupStart() < price.GroupIntervalStart && ts.GetGroupEnd() >= price.GroupIntervalStart { - ts.SetInterval(i) + ts.SetRateInterval(i) splitTime := ts.TimeStart.Add(price.GroupIntervalStart - ts.GetGroupStart()) nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} ts.TimeEnd = splitTime - nts.SetInterval(i) + nts.SetRateInterval(i) nts.CallDuration = ts.CallDuration ts.SetNewCallDuration(nts) @@ -129,14 +120,14 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { // if the span is enclosed in the interval try to set as new interval and return nil if i.Contains(ts.TimeStart) && i.Contains(ts.TimeEnd) { //Logger.Debug("All in interval") - ts.SetInterval(i) + ts.SetRateInterval(i) return } // if only the start time is in the interval split the interval to the right if i.Contains(ts.TimeStart) { //Logger.Debug("Start in interval") splitTime := i.getRightMargin(ts.TimeStart) - ts.SetInterval(i) + ts.SetRateInterval(i) if splitTime == ts.TimeStart { return } @@ -157,7 +148,7 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} ts.TimeEnd = splitTime - nts.SetInterval(i) + nts.SetRateInterval(i) nts.CallDuration = ts.CallDuration ts.SetNewCallDuration(nts) @@ -169,11 +160,11 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { /* Splits the given timespan on activation period's activation time. */ -func (ts *TimeSpan) SplitByActivationPeriod(ap *ActivationPeriod) (newTs *TimeSpan) { +func (ts *TimeSpan) SplitByRatingPlan(ap *RatingPlan) (newTs *TimeSpan) { if !ts.Contains(ap.ActivationTime) { return nil } - newTs = &TimeSpan{TimeStart: ap.ActivationTime, TimeEnd: ts.TimeEnd, ActivationPeriod: ap} + newTs = &TimeSpan{TimeStart: ap.ActivationTime, TimeEnd: ts.TimeEnd, RatingPlan: ap} newTs.CallDuration = ts.CallDuration ts.TimeEnd = ap.ActivationTime ts.SetNewCallDuration(newTs) @@ -217,6 +208,7 @@ func (ts *TimeSpan) SplitByMinuteBalance(mb *Balance) (newTs *TimeSpan) { return } +// Returns the starting time of this timespan func (ts *TimeSpan) GetGroupStart() time.Duration { s := ts.CallDuration - ts.GetDuration() if s < 0 { @@ -229,6 +221,7 @@ func (ts *TimeSpan) GetGroupEnd() time.Duration { return ts.CallDuration } +// sets the CallDuration attribute to reflect new timespan func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) { d := ts.CallDuration - nts.GetDuration() if d < 0 { diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 410609e3b..ff2ec3bad 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -24,24 +24,24 @@ import ( ) func TestRightMargin(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}} + i := &RateInterval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}} t1 := time.Date(2012, time.February, 3, 23, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 4, 0, 10, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 3, 23, 59, 59, 0, time.UTC) { t.Error("Incorrect first half", ts) } if nts.TimeStart != time.Date(2012, time.February, 3, 23, 59, 59, 0, time.UTC) || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 15*60-1 || nts.GetDuration().Seconds() != 10*60+1 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), ts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), ts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { @@ -50,24 +50,24 @@ func TestRightMargin(t *testing.T) { } func TestRightHourMargin(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, EndTime: "17:59:00"} + i := &RateInterval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, EndTime: "17:59:00"} t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 3, 17, 59, 00, 0, time.UTC) { t.Error("Incorrect first half", ts) } if nts.TimeStart != time.Date(2012, time.February, 3, 17, 59, 00, 0, time.UTC) || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 29*60 || nts.GetDuration().Seconds() != 1*60 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) @@ -75,23 +75,23 @@ func TestRightHourMargin(t *testing.T) { } func TestLeftMargin(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}} + i := &RateInterval{WeekDays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}} t1 := time.Date(2012, time.February, 5, 23, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 6, 0, 10, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 6, 0, 0, 0, 0, time.UTC) { t.Error("Incorrect first half", ts) } if nts.TimeStart != time.Date(2012, time.February, 6, 0, 0, 0, 0, time.UTC) || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if nts.Interval != i { - t.Error("Interval not attached correctly") + if nts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 15*60 || nts.GetDuration().Seconds() != 10*60 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) @@ -99,23 +99,23 @@ func TestLeftMargin(t *testing.T) { } func TestLeftHourMargin(t *testing.T) { - i := &Interval{Months: Months{time.December}, MonthDays: MonthDays{1}, StartTime: "09:00:00"} + i := &RateInterval{Months: Months{time.December}, MonthDays: MonthDays{1}, StartTime: "09:00:00"} t1 := time.Date(2012, time.December, 1, 8, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.December, 1, 9, 20, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.December, 1, 9, 0, 0, 0, time.UTC) { t.Error("Incorrect first half", ts) } if nts.TimeStart != time.Date(2012, time.December, 1, 9, 0, 0, 0, time.UTC) || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if nts.Interval != i { - t.Error("Interval not attached correctly") + if nts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 15*60 || nts.GetDuration().Seconds() != 20*60 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) @@ -123,27 +123,27 @@ func TestLeftHourMargin(t *testing.T) { } func TestEnclosingMargin(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Sunday}} + i := &RateInterval{WeekDays: []time.Weekday{time.Sunday}} t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 18, 10, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) if ts.TimeStart != t1 || ts.TimeEnd != t2 || nts != nil { t.Error("Incorrect enclosing", ts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } } func TestOutsideMargin(t *testing.T) { - i := &Interval{WeekDays: []time.Weekday{time.Monday}} + i := &RateInterval{WeekDays: []time.Weekday{time.Monday}} t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 18, 10, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} - result := ts.SplitByInterval(i) + result := ts.SplitByRateInterval(i) if result != nil { - t.Error("Interval not split correctly") + t.Error("RateInterval not split correctly") } } @@ -168,17 +168,17 @@ func TestSplitByActivationTime(t *testing.T) { t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) t3 := time.Date(2012, time.February, 5, 17, 50, 0, 0, time.UTC) ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - ap1 := &ActivationPeriod{ActivationTime: t1} - ap2 := &ActivationPeriod{ActivationTime: t2} - ap3 := &ActivationPeriod{ActivationTime: t3} + ap1 := &RatingPlan{ActivationTime: t1} + ap2 := &RatingPlan{ActivationTime: t2} + ap3 := &RatingPlan{ActivationTime: t3} - if ts.SplitByActivationPeriod(ap1) != nil { + if ts.SplitByRatingPlan(ap1) != nil { t.Error("Error spliting on left margin") } - if ts.SplitByActivationPeriod(ap2) != nil { + if ts.SplitByRatingPlan(ap2) != nil { t.Error("Error spliting on right margin") } - result := ts.SplitByActivationPeriod(ap3) + result := ts.SplitByRatingPlan(ap3) if result.TimeStart != t3 || result.TimeEnd != t2 { t.Error("Error spliting on interior") } @@ -192,27 +192,27 @@ func TestTimespanGetCost(t *testing.T) { if ts1.getCost(cd) != 0 { t.Error("No interval and still kicking") } - ts1.Interval = &Interval{Prices: PriceGroups{&Price{0, 1.0, 1 * time.Second, 1 * time.Second}}} + ts1.RateInterval = &RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}} if ts1.getCost(cd) != 600 { t.Error("Expected 10 got ", ts1.getCost(cd)) } - ts1.Interval.Prices[0].RateUnit = 60 * time.Second + ts1.RateInterval.Rates[0].RateUnit = 60 * time.Second if ts1.getCost(cd) != 10 { t.Error("Expected 6000 got ", ts1.getCost(cd)) } } -func TestSetInterval(t *testing.T) { - i1 := &Interval{Prices: PriceGroups{&Price{0, 1.0, 1 * time.Second, 1 * time.Second}}} - ts1 := TimeSpan{Interval: i1} - i2 := &Interval{Prices: PriceGroups{&Price{0, 2.0, 1 * time.Second, 1 * time.Second}}} - ts1.SetInterval(i2) - if ts1.Interval != i1 { +func TestSetRateInterval(t *testing.T) { + i1 := &RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}} + ts1 := TimeSpan{RateInterval: i1} + i2 := &RateInterval{Rates: RateGroups{&Rate{0, 2.0, 1 * time.Second, 1 * time.Second}}} + ts1.SetRateInterval(i2) + if ts1.RateInterval != i1 { t.Error("Smaller price interval should win") } i2.Weight = 1 - ts1.SetInterval(i2) - if ts1.Interval != i2 { + ts1.SetRateInterval(i2) + if ts1.RateInterval != i2 { t.Error("Bigger ponder interval should win") } } @@ -330,15 +330,15 @@ func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentScarceFirst(t *testi } func TestTimespanSplitGroupedRates(t *testing.T) { - i := &Interval{ + i := &RateInterval{ EndTime: "17:59:00", - Prices: PriceGroups{&Price{0, 2, 1 * time.Second, 1 * time.Second}, &Price{900 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, + Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{900 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 1800 * time.Second} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) splitTime := time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts.TimeStart, ts.TimeEnd) @@ -346,17 +346,17 @@ func TestTimespanSplitGroupedRates(t *testing.T) { if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } - c1 := ts.Interval.GetCost(ts.GetDuration(), ts.GetGroupStart()) - c2 := nts.Interval.GetCost(nts.GetDuration(), nts.GetGroupStart()) + c1 := ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) + c2 := nts.RateInterval.GetCost(nts.GetDuration(), nts.GetGroupStart()) if c1 != 1800 || c2 != 900 { t.Error("Wrong costs: ", c1, c2) } if ts.GetDuration().Seconds() != 15*60 || nts.GetDuration().Seconds() != 15*60 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) @@ -364,15 +364,15 @@ func TestTimespanSplitGroupedRates(t *testing.T) { } func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { - i := &Interval{ + i := &RateInterval{ EndTime: "17:59:00", - Prices: PriceGroups{&Price{0, 2, 1 * time.Second, 1 * time.Second}, &Price{30 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, + Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{30 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 31, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 60 * time.Second} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) splitTime := time.Date(2012, time.February, 3, 17, 30, 30, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts) @@ -380,17 +380,17 @@ func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } - c1 := ts.Interval.GetCost(ts.GetDuration(), ts.GetGroupStart()) - c2 := nts.Interval.GetCost(nts.GetDuration(), nts.GetGroupStart()) + c1 := ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) + c2 := nts.RateInterval.GetCost(nts.GetDuration(), nts.GetGroupStart()) if c1 != 60 || c2 != 60 { t.Error("Wrong costs: ", c1, c2) } if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 0.5*60 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) @@ -398,15 +398,15 @@ func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { } func TestTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { - i := &Interval{ + i := &RateInterval{ EndTime: "17:00:30", - Prices: PriceGroups{&Price{0, 2, 1 * time.Second, 1 * time.Second}, &Price{60 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, + Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{60 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, } t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 01, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) splitTime := time.Date(2012, time.February, 3, 17, 00, 30, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts) @@ -414,32 +414,32 @@ func TestTimespanSplitRightHourMarginBeforeGroup(t *testing.T) { if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 30 || nts.GetDuration().Seconds() != 30 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } - nnts := nts.SplitByInterval(i) + nnts := nts.SplitByRateInterval(i) if nnts != nil { t.Error("Bad new split", nnts) } } func TestTimespanSplitGroupSecondSplit(t *testing.T) { - i := &Interval{ + i := &RateInterval{ EndTime: "17:03:30", - Prices: PriceGroups{&Price{0, 2, 1 * time.Second, 1 * time.Second}, &Price{60 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, + Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{60 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, } t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 04, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 240 * time.Second} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) splitTime := time.Date(2012, time.February, 3, 17, 01, 00, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", nts) @@ -447,17 +447,17 @@ func TestTimespanSplitGroupSecondSplit(t *testing.T) { if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 60 || nts.GetDuration().Seconds() != 180 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } - nnts := nts.SplitByInterval(i) + nnts := nts.SplitByRateInterval(i) nsplitTime := time.Date(2012, time.February, 3, 17, 03, 30, 0, time.UTC) if nts.TimeStart != splitTime || nts.TimeEnd != nsplitTime { t.Error("Incorrect first half", nts) @@ -465,25 +465,25 @@ func TestTimespanSplitGroupSecondSplit(t *testing.T) { if nnts.TimeStart != nsplitTime || nnts.TimeEnd != t2 { t.Error("Incorrect second half", nnts) } - if nts.Interval != i { - t.Error("Interval not attached correctly") + if nts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if nts.GetDuration().Seconds() != 150 || nnts.GetDuration().Seconds() != 30 { - t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) } } func TestTimespanSplitMultipleGroup(t *testing.T) { - i := &Interval{ + i := &RateInterval{ EndTime: "17:05:00", - Prices: PriceGroups{&Price{0, 2, 1 * time.Second, 1 * time.Second}, &Price{60 * time.Second, 1, 1 * time.Second, 1 * time.Second}, &Price{180 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, + Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{60 * time.Second, 1, 1 * time.Second, 1 * time.Second}, &Rate{180 * time.Second, 1, 1 * time.Second, 1 * time.Second}}, } t1 := time.Date(2012, time.February, 3, 17, 00, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 04, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 240 * time.Second} oldDuration := ts.GetDuration() - nts := ts.SplitByInterval(i) + nts := ts.SplitByRateInterval(i) splitTime := time.Date(2012, time.February, 3, 17, 01, 00, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", nts) @@ -491,17 +491,17 @@ func TestTimespanSplitMultipleGroup(t *testing.T) { if nts.TimeStart != splitTime || nts.TimeEnd != t2 { t.Error("Incorrect second half", nts) } - if ts.Interval != i { - t.Error("Interval not attached correctly") + if ts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if ts.GetDuration().Seconds() != 60 || nts.GetDuration().Seconds() != 180 { - t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } - nnts := nts.SplitByInterval(i) + nnts := nts.SplitByRateInterval(i) nsplitTime := time.Date(2012, time.February, 3, 17, 03, 00, 0, time.UTC) if nts.TimeStart != splitTime || nts.TimeEnd != nsplitTime { t.Error("Incorrect first half", nts) @@ -509,12 +509,12 @@ func TestTimespanSplitMultipleGroup(t *testing.T) { if nnts.TimeStart != nsplitTime || nnts.TimeEnd != t2 { t.Error("Incorrect second half", nnts) } - if nts.Interval != i { - t.Error("Interval not attached correctly") + if nts.RateInterval != i { + t.Error("RateInterval not attached correctly") } if nts.GetDuration().Seconds() != 120 || nnts.GetDuration().Seconds() != 60 { - t.Error("Wrong durations.for Intervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) + t.Error("Wrong durations.for RateIntervals", nts.GetDuration().Seconds(), nnts.GetDuration().Seconds()) } } @@ -523,8 +523,8 @@ func TestTimespanExpandingPastEnd(t *testing.T) { &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), - Interval: &Interval{Prices: PriceGroups{ - &Price{RateIncrement: 60 * time.Second}, + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 60 * time.Second}, }}, }, &TimeSpan{ @@ -542,13 +542,37 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } +func TestTimespanExpandingRoundingPastEnd(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 20, 0, time.UTC), + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 15 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 20, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 40, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.expandTimeSpans(timespans) + if len(timespans) != 2 { + t.Error("Error removing overlaped intervals: ", timespans) + } + if !timespans[0].TimeEnd.Equal(time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC)) { + t.Error("Error expanding timespan: ", timespans[0]) + } +} + func TestTimespanExpandingPastEndMultiple(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), - Interval: &Interval{Prices: PriceGroups{ - &Price{RateIncrement: 60 * time.Second}, + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 60 * time.Second}, }}, }, &TimeSpan{ @@ -575,8 +599,8 @@ func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), - Interval: &Interval{Prices: PriceGroups{ - &Price{RateIncrement: 60 * time.Second}, + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 60 * time.Second}, }}, }, &TimeSpan{ @@ -603,8 +627,8 @@ func TestTimespanExpandingBeforeEnd(t *testing.T) { &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), - Interval: &Interval{Prices: PriceGroups{ - &Price{RateIncrement: 45 * time.Second}, + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 45 * time.Second}, }}, }, &TimeSpan{ @@ -629,8 +653,8 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), - Interval: &Interval{Prices: PriceGroups{ - &Price{RateIncrement: 45 * time.Second}, + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 45 * time.Second}, }}, }, &TimeSpan{ diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 56824abd4..324c122ba 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -24,7 +24,6 @@ import ( "io/ioutil" "log" "strconv" - "time" ) // Import tariff plan from csv into storDb @@ -139,11 +138,11 @@ func (self *TPCSVImporter) importRates(fn string) error { } continue } - rt, err := NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8]) + rt, err := NewLoadRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8]) if err != nil { return err } - if err := self.StorDb.SetTPRates(self.TPid, map[string][]*Rate{record[0]: []*Rate{rt}}); err != nil { + if err := self.StorDb.SetTPRates(self.TPid, map[string][]*LoadRate{record[0]: []*LoadRate{rt}}); err != nil { if self.Verbose { log.Printf("Ignoring line %d, storDb operational error: <%s> ", lineNr, err.Error()) } @@ -294,16 +293,6 @@ func (self *TPCSVImporter) importActions(fn string) error { } continue } - var expiryTime time.Time // Empty initialized time represents never expire - if record[5] != "*unlimited" { // ToDo: Expand here for other meta tags or go way of adding time for expiry - expiryTime, err = time.Parse(time.RFC3339, record[5]) - if err != nil { - if self.Verbose { - log.Printf("Ignoring line %d, warning: <%s> ", lineNr, err.Error()) - } - continue - } - } rateValue, _ := strconv.ParseFloat(record[8], 64) // Ignore errors since empty string is error, we can find out based on rateType if defined minutesWeight, _ := strconv.ParseFloat(record[9], 64) weight, err := strconv.ParseFloat(record[10], 64) @@ -314,16 +303,18 @@ func (self *TPCSVImporter) importActions(fn string) error { continue } act := &Action{ - ActionType: actionType, - BalanceId: balanceType, - Direction: direction, - Units: units, - ExpirationDate: expiryTime, - DestinationTag: destTag, - RateType: rateType, - RateValue: rateValue, - MinutesWeight: minutesWeight, - Weight: weight, + ActionType: actionType, + BalanceId: balanceType, + Direction: direction, + ExpirationString: record[5], + Balance: &Balance{ + Value: units, + DestinationId: destTag, + SpecialPriceType: rateType, + SpecialPrice: rateValue, + Weight: minutesWeight, + }, + Weight: weight, } if err := self.StorDb.SetTPActions(self.TPid, map[string][]*Action{actId: []*Action{act}}); err != nil { if self.Verbose { diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 130971b66..a466bb2bc 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -285,7 +285,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { Account: lastCC.Account, Destination: lastCC.Destination, Amount: -cost, - // FallbackSubject: lastCC.FallbackSubject, // ToDo: check how to best add it + // FallbackSubject: lastCC.FallbackSubject, // TODO: check how to best add it } var response float64 err := sm.connector.DebitCents(*cd, &response) diff --git a/utils/coreutils.go b/utils/coreutils.go index 5cbe8c54d..73b047373 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -121,13 +121,14 @@ func ParseDate(date string) (expDate time.Time, err error) { return expDate, err } -// returns a number equeal or larger than the peram that exactly -// is divisible to 60 -func RoundToMinute(seconds float64) float64 { - if math.Mod(seconds, 60) == 0 { - return seconds +// returns a number equeal or larger than the amount that exactly +// is divisible to whole +func RoundTo(whole, amount time.Duration) time.Duration { + a, w := float64(amount), float64(whole) + if math.Mod(a, w) == 0 { + return amount } - return (60 - math.Mod(seconds, 60)) + seconds + return time.Duration((w - math.Mod(a, w)) + a) } func SplitPrefix(prefix string) []string { diff --git a/utils/utils_test.go b/utils/utils_test.go index 89bb11742..af65bb6a7 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -195,33 +195,34 @@ func TestMissingStructFieldsIncorrect(t *testing.T) { } } -func TestRoundToMinute(t *testing.T) { - result := RoundToMinute(0) - expected := 0.0 +func TestRound(t *testing.T) { + minute := time.Minute + result := RoundTo(minute, 0*time.Second) + expected := 0 * time.Second if result != expected { t.Errorf("Error rounding to minute1: expected %v was %v", expected, result) } - result = RoundToMinute(1) - expected = 60.0 + result = RoundTo(minute, 1*time.Second) + expected = minute if result != expected { t.Errorf("Error rounding to minute2: expected %v was %v", expected, result) } - result = RoundToMinute(59) - expected = 60.0 + result = RoundTo(minute, 5*time.Second) + expected = minute if result != expected { t.Errorf("Error rounding to minute3: expected %v was %v", expected, result) } - result = RoundToMinute(60) - expected = 60.0 + result = RoundTo(minute, minute) + expected = minute if result != expected { t.Errorf("Error rounding to minute4: expected %v was %v", expected, result) } - result = RoundToMinute(90) - expected = 120.0 + result = RoundTo(minute, 90*time.Second) + expected = 120 * time.Second if result != expected { t.Errorf("Error rounding to minute5: expected %v was %v", expected, result) } - result = RoundToMinute(120) + result = RoundTo(60, 120) expected = 120.0 if result != expected { t.Errorf("Error rounding to minute5: expected %v was %v", expected, result) From e18420ea234c00028ca25026fd6b1a3816cf5dda Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 12 Sep 2013 21:09:42 +0300 Subject: [PATCH 16/39] rated seconds slice --- engine/balances.go | 1 + engine/callcost.go | 2 +- engine/callcost_test.go | 4 +- engine/calldesc.go | 66 +++------ engine/rateinterval.go | 14 +- engine/timespans.go | 92 +++++------- engine/timespans_test.go | 231 +++++++++++++---------------- sessionmanager/fssessionmanager.go | 27 +--- 8 files changed, 162 insertions(+), 275 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 20de21adb..1b0ebb0a2 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -34,6 +34,7 @@ type Balance struct { SpecialPriceType string SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) DestinationId string + RateSubject string precision int } diff --git a/engine/callcost.go b/engine/callcost.go index 7c75f29ab..3c3c371e4 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -48,7 +48,7 @@ func (cc *CallCost) Merge(other *CallCost) { ts := cc.Timespans[len(cc.Timespans)-1] otherTs := other.Timespans[0] if reflect.DeepEqual(ts.RatingPlan, otherTs.RatingPlan) && - reflect.DeepEqual(ts.MinuteInfo, otherTs.MinuteInfo) && reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { + reflect.DeepEqual(ts.RateInterval, otherTs.RateInterval) { // extend the last timespan with ts.TimeEnd = ts.TimeEnd.Add(otherTs.GetDuration()) // add the rest of the timspans diff --git a/engine/callcost_test.go b/engine/callcost_test.go index d1e9f364b..0c2d49328 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -86,7 +86,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { if cc1.Cost != 90 { t.Errorf("expected 90 was %v", cc1.Cost) } - t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) + /*t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC) cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() @@ -99,7 +99,7 @@ func TestMultipleInputLeftMerge(t *testing.T) { } if cc1.Cost != 120 { t.Errorf("Exdpected 120 was %v", cc1.Cost) - } + }*/ } func TestMultipleInputRightMerge(t *testing.T) { diff --git a/engine/calldesc.go b/engine/calldesc.go index 9211af136..ca1cd976d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -197,26 +197,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti firstSpan = &TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd, CallDuration: cd.CallDuration} } timespans = append(timespans, firstSpan) - // split on (free) minute buckets - if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { - _, _, minuteBalances := userBalance.getSecondsForPrefix(cd.Destination) - for _, b := range minuteBalances { - for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue - } - newTs := timespans[i].SplitByMinuteBalance(b) - if newTs != nil { - timespans = append(timespans, newTs) - firstSpan = newTs // we move the firstspan to the newly created one for further spliting - break - } - } - } - } - if firstSpan.MinuteInfo != nil { - return // all the timespans are on minutes - } if len(cd.RatingPlans) == 0 { return } @@ -230,9 +210,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } else { afterStart = true for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue - } newTs := timespans[i].SplitByRatingPlan(ap) if newTs != nil { timespans = append(timespans, newTs) @@ -245,9 +222,6 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } // split on price intervals for i := 0; i < len(timespans); i++ { - if timespans[i].MinuteInfo != nil { - continue // cont try to split timespans payed with minutes - } ap := timespans[i].RatingPlan //timespans[i].RatingPlan = nil ap.RateIntervals.Sort() @@ -262,27 +236,29 @@ func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*Ti } } } - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) return } // if the rate interval for any timespan has a RatingIncrement larger than the timespan duration // the timespan must expand potentially overlaping folowing timespans and may exceed call // descriptor's initial duration -func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { +func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*TimeSpan { for i, ts := range timespans { if ts.RateInterval != nil { _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) // if the timespan duration is larger than the rate increment make sure it is a multiple of it - if rateIncrement < ts.GetDuration() { + if rateIncrement != time.Second && rateIncrement < ts.GetDuration() { rateIncrement = utils.RoundTo(rateIncrement, ts.GetDuration()) } if rateIncrement > ts.GetDuration() { ts.TimeEnd = ts.TimeStart.Add(rateIncrement) - ts.SetNewCallDuration(ts) // set new call duration for this timespan + ts.CallDuration = ts.CallDuration + rateIncrement + // overlap the rest of the timespans + i += 1 for ; i < len(timespans); i++ { - if timespans[i].TimeEnd.Before(ts.TimeEnd) { + if timespans[i].TimeEnd.Before(ts.TimeEnd) || timespans[i].TimeEnd.Equal(ts.TimeEnd) { timespans[i].overlapped = true } else if timespans[i].TimeStart.Before(ts.TimeEnd) { timespans[i].TimeStart = ts.TimeEnd @@ -292,14 +268,15 @@ func (cd *CallDescriptor) expandTimeSpans(timespans []*TimeSpan) []*TimeSpan { } } } + var newTimespans []*TimeSpan // remove overlapped - for i, ts := range timespans { - if ts.overlapped { - timespans = timespans[:i] - break + for _, ts := range timespans { + if !ts.overlapped { + ts.createRatedSecondSlice() + newTimespans = append(newTimespans, ts) } } - return timespans + return newTimespans } /* @@ -317,10 +294,10 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { for i, ts := range timespans { // only add connect fee if this is the first/only call cost request - if cd.LoopIndex == 0 && i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + if cd.LoopIndex == 0 && i == 0 && ts.RateInterval != nil { connectionFee = ts.RateInterval.ConnectFee } - cost += ts.getCost(cd) + cost += ts.getCost() } cost = utils.Round(cost, roundingDecimals, roundingMethod) cc := &CallCost{ @@ -376,10 +353,10 @@ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float6 cost := 0.0 for i, ts := range timespans { - if i == 0 && ts.MinuteInfo == nil && ts.RateInterval != nil { + if i == 0 && ts.RateInterval != nil { cost += ts.RateInterval.ConnectFee } - cost += ts.getCost(cd) + cost += ts.Cost } //logger.Print(availableCredit, availableSeconds, cost) if cost < availableCredit { @@ -408,14 +385,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { Logger.Debug(fmt.Sprintf(" Attempting to debit from %v, value: %v", cd.GetUserBalanceKey(), cc.Cost+cc.ConnectFee)) defer storageGetter.SetUserBalance(userBalance) if cc.Cost != 0 || cc.ConnectFee != 0 { - userBalance.debitBalance(CREDIT, cc.Cost+cc.ConnectFee, true) - } - for _, ts := range cc.Timespans { - if ts.MinuteInfo != nil { - if err = userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination, true); err != nil { - return cc, err - } - } + userBalance.debitBalance(CREDIT+OUTBOUND, cc.Cost+cc.ConnectFee, true) } } return diff --git a/engine/rateinterval.go b/engine/rateinterval.go index db8c8b0fe..9857be0f1 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -21,7 +21,6 @@ package engine import ( "fmt" "github.com/cgrates/cgrates/utils" - "math" "reflect" "sort" "strconv" @@ -40,7 +39,7 @@ type RateInterval struct { StartTime, EndTime string // ##:##:## format Weight, ConnectFee float64 Rates RateGroups // GroupRateInterval (start time): Rate - RoundingMethod string + RoundingMethod string //ROUNDING_UP, ROUNDING_DOWN, ROUNDING_MIDDLE RoundingDecimals int } @@ -194,13 +193,12 @@ func (i *RateInterval) Equal(o *RateInterval) bool { i.EndTime == o.EndTime } -func (i *RateInterval) GetCost(duration, startSecond time.Duration) (cost float64) { - price, rateIncrement, rateUnit := i.GetRateParameters(startSecond) - d := float64(duration.Seconds()) +func (i *RateInterval) GetCost(duration, startSecond time.Duration) float64 { + price, _, rateUnit := i.GetRateParameters(startSecond) + d := duration.Seconds() price /= rateUnit.Seconds() - ri := rateIncrement.Seconds() - cost = math.Ceil(d/ri) * ri * price - return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod) + + return utils.Round(d*price, i.RoundingDecimals, i.RoundingMethod) } // Gets the price for a the provided start second diff --git a/engine/timespans.go b/engine/timespans.go index b2abd493a..65d64138b 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -19,7 +19,7 @@ along with this program. If not, see package engine import ( - "fmt" + "github.com/cgrates/cgrates/utils" "time" ) @@ -31,16 +31,14 @@ type TimeSpan struct { Cost float64 RatingPlan *RatingPlan RateInterval *RateInterval - MinuteInfo *MinuteInfo CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one + ratedSeconds []*rated_second } -// Holds the bonus minute information related to a specified timespan -type MinuteInfo struct { - DestinationId string - Quantity float64 - Price float64 +type rated_second struct { + index int + rate float64 } // Returns the duration of the timespan @@ -48,27 +46,20 @@ func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) } +// Returns true if the given time is inside timespan range. +func (ts *TimeSpan) Contains(t time.Time) bool { + return t.After(ts.TimeStart) && t.Before(ts.TimeEnd) +} + // Returns the cost of the timespan according to the relevant cost interval. // It also sets the Cost field of this timespan (used for refound on session // manager debit loop where the cost cannot be recalculated) -func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { - if ts.MinuteInfo != nil { - return ts.GetDuration().Seconds() * ts.MinuteInfo.Price - } +func (ts *TimeSpan) getCost() float64 { if ts.RateInterval == nil { return 0 } - i := ts.RateInterval - cost = i.GetCost(ts.GetDuration(), ts.GetGroupStart()) - ts.Cost = cost - return -} - -/* -Returns true if the given time is inside timespan range. -*/ -func (ts *TimeSpan) Contains(t time.Time) bool { - return t.After(ts.TimeStart) && t.Before(ts.TimeEnd) + ts.Cost = ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart()) + return ts.Cost } /* @@ -87,6 +78,26 @@ func (ts *TimeSpan) SetRateInterval(i *RateInterval) { } } +func (ts *TimeSpan) createRatedSecondSlice() { + if ts.RateInterval == nil { + return + } + // create rated seconds series + rate, _, rate_unit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + secondCost := rate / rate_unit.Seconds() + totalCost := 0.0 + for s := 0; s < int(ts.GetDuration().Seconds()); s++ { + ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{s, secondCost}) + totalCost += secondCost + } + cCost := ts.getCost() + // here there might be some subsecond duration left + if totalCost < cCost { + // add one extra second with the fractional cost + ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{len(ts.ratedSeconds), utils.Round(cCost-totalCost, ts.RateInterval.RoundingDecimals, ts.RateInterval.RoundingMethod)}) + } +} + /* Splits the given timespan according to how it relates to the interval. It will modify the endtime of the received timespan and it will return @@ -171,43 +182,6 @@ func (ts *TimeSpan) SplitByRatingPlan(ap *RatingPlan) (newTs *TimeSpan) { return } -/* -Splits the given timespan on minute bucket's duration. -*/ -func (ts *TimeSpan) SplitByMinuteBalance(mb *Balance) (newTs *TimeSpan) { - // if mb expired skip it - if !mb.ExpirationDate.IsZero() && (ts.TimeStart.Equal(mb.ExpirationDate) || ts.TimeStart.After(mb.ExpirationDate)) { - return nil - } - - // expiring before time spans end - - if !mb.ExpirationDate.IsZero() && ts.TimeEnd.After(mb.ExpirationDate) { - newTs = &TimeSpan{TimeStart: mb.ExpirationDate, TimeEnd: ts.TimeEnd} - newTs.CallDuration = ts.CallDuration - ts.TimeEnd = mb.ExpirationDate - ts.SetNewCallDuration(newTs) - } - - s := ts.GetDuration().Seconds() - ts.MinuteInfo = &MinuteInfo{mb.DestinationId, s, mb.SpecialPrice} - if s <= mb.Value { - mb.Value -= s - return newTs - } - secDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", mb.Value)) - - newTimeEnd := ts.TimeStart.Add(secDuration) - newTs = &TimeSpan{TimeStart: newTimeEnd, TimeEnd: ts.TimeEnd} - ts.TimeEnd = newTimeEnd - newTs.CallDuration = ts.CallDuration - ts.MinuteInfo.Quantity = mb.Value - ts.SetNewCallDuration(newTs) - mb.Value = 0 - - return -} - // Returns the starting time of this timespan func (ts *TimeSpan) GetGroupStart() time.Duration { s := ts.CallDuration - ts.GetDuration() diff --git a/engine/timespans_test.go b/engine/timespans_test.go index ff2ec3bad..01afd4650 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "github.com/cgrates/cgrates/utils" "testing" "time" ) @@ -188,17 +189,17 @@ func TestTimespanGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 5, 17, 45, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 5, 17, 55, 0, 0, time.UTC) ts1 := TimeSpan{TimeStart: t1, TimeEnd: t2} - cd := &CallDescriptor{Subject: "other"} - if ts1.getCost(cd) != 0 { + if ts1.getCost() != 0 { t.Error("No interval and still kicking") } - ts1.RateInterval = &RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}} - if ts1.getCost(cd) != 600 { - t.Error("Expected 10 got ", ts1.getCost(cd)) + ts1.SetRateInterval(&RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 1 * time.Second}}}) + if ts1.getCost() != 600 { + t.Error("Expected 10 got ", ts1.Cost) } - ts1.RateInterval.Rates[0].RateUnit = 60 * time.Second - if ts1.getCost(cd) != 10 { - t.Error("Expected 6000 got ", ts1.getCost(cd)) + ts1.RateInterval = nil + ts1.SetRateInterval(&RateInterval{Rates: RateGroups{&Rate{0, 1.0, 1 * time.Second, 60 * time.Second}}}) + if ts1.getCost() != 10 { + t.Error("Expected 6000 got ", ts1.Cost) } } @@ -217,118 +218,6 @@ func TestSetRateInterval(t *testing.T) { } } -func TestTimespanSplitByMinuteBucketPlenty(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Bad extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarce(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 60} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpired(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 39, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo != nil { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Bad extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpiring(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalancePlentyExpiringEnd(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 180, ExpirationDate: time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 120 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringSame(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 120, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 0, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 60 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentExpFirst(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 140, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 1, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { - t.Error("Not enough minutes on minute bucket split: ", ts.MinuteInfo.Quantity) - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - -func TestTimespanSplitByMinuteBalanceScarceExpiringDifferentScarceFirst(t *testing.T) { - t1 := time.Date(2013, time.July, 15, 10, 40, 0, 0, time.UTC) - t2 := time.Date(2013, time.July, 15, 10, 42, 0, 0, time.UTC) - mb := &Balance{Value: 61, ExpirationDate: time.Date(2013, time.July, 15, 10, 41, 30, 0, time.UTC)} - ts := TimeSpan{TimeStart: t1, TimeEnd: t2} - newTs := ts.SplitByMinuteBalance(mb) - if ts.MinuteInfo == nil || ts.MinuteInfo.Quantity != 61 { - t.Error("Not enough minutes on minute bucket split") - } - if newTs == nil || newTs.MinuteInfo != nil { - t.Error("Missing extra timespan on minute bucket split") - } -} - func TestTimespanSplitGroupedRates(t *testing.T) { i := &RateInterval{ EndTime: "17:59:00", @@ -366,19 +255,38 @@ func TestTimespanSplitGroupedRates(t *testing.T) { func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { i := &RateInterval{ EndTime: "17:59:00", - Rates: RateGroups{&Rate{0, 2, 1 * time.Second, 1 * time.Second}, &Rate{30 * time.Second, 1, 60 * time.Second, 1 * time.Second}}, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 2, + RateIncrement: time.Second, + RateUnit: time.Second}, + &Rate{ + GroupIntervalStart: 30 * time.Second, + Value: 1, + RateIncrement: time.Minute, + RateUnit: time.Second, + }}, } t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 3, 17, 31, 0, 0, time.UTC) ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 60 * time.Second} oldDuration := ts.GetDuration() nts := ts.SplitByRateInterval(i) + cd := &CallDescriptor{} + timespans := cd.roundTimeSpansToIncrement([]*TimeSpan{ts, nts}) + if len(timespans) != 2 { + t.Error("Error rounding timespans: ", timespans) + } + ts = timespans[0] + nts = timespans[1] splitTime := time.Date(2012, time.February, 3, 17, 30, 30, 0, time.UTC) if ts.TimeStart != t1 || ts.TimeEnd != splitTime { t.Error("Incorrect first half", ts) } - if nts.TimeStart != splitTime || nts.TimeEnd != t2 { - t.Error("Incorrect second half", nts) + t3 := time.Date(2012, time.February, 3, 17, 31, 30, 0, time.UTC) + if nts.TimeStart != splitTime || nts.TimeEnd != t3 { + t.Error("Incorrect second half", nts.TimeStart, nts.TimeEnd) } if ts.RateInterval != i { t.Error("RateInterval not attached correctly") @@ -389,10 +297,10 @@ func TestTimespanSplitGroupedRatesIncrements(t *testing.T) { t.Error("Wrong costs: ", c1, c2) } - if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 0.5*60 { + if ts.GetDuration().Seconds() != 0.5*60 || nts.GetDuration().Seconds() != 1*60 { t.Error("Wrong durations.for RateIntervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) } - if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + if ts.GetDuration()+nts.GetDuration() != oldDuration+30*time.Second { t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) } } @@ -533,7 +441,7 @@ func TestTimespanExpandingPastEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -542,6 +450,28 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } +func TestTimespanExpandingCallDuration(t *testing.T) { + timespans := []*TimeSpan{ + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{RateIncrement: 60 * time.Second}, + }}, + }, + &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 45, 0, time.UTC), + }, + } + cd := &CallDescriptor{} + timespans = cd.roundTimeSpansToIncrement(timespans) + + if timespans[0].CallDuration != time.Minute { + t.Error("Error setting call duration: ", timespans[0]) + } +} + func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -557,7 +487,7 @@ func TestTimespanExpandingRoundingPastEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 2 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -585,7 +515,7 @@ func TestTimespanExpandingPastEndMultiple(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -613,7 +543,7 @@ func TestTimespanExpandingPastEndMultipleEqual(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 1 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -637,7 +567,7 @@ func TestTimespanExpandingBeforeEnd(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 2 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -667,7 +597,7 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { }, } cd := &CallDescriptor{} - timespans = cd.expandTimeSpans(timespans) + timespans = cd.roundTimeSpansToIncrement(timespans) if len(timespans) != 3 { t.Error("Error removing overlaped intervals: ", timespans) } @@ -677,3 +607,42 @@ func TestTimespanExpandingBeforeEndMultiple(t *testing.T) { t.Error("Error expanding timespan: ", timespans[0]) } } + +func TestTimespanCreateSecondsSlice(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 0, time.UTC), + RateInterval: &RateInterval{Rates: RateGroups{ + &Rate{Value: 2.0}, + }}, + } + ts.createRatedSecondSlice() + if len(ts.ratedSeconds) != 30 { + t.Error("Error creating second slice: ", ts.ratedSeconds) + } + if ts.ratedSeconds[0].rate != 2.0 { + t.Error("Wrong second slice: ", ts.ratedSeconds[0]) + } +} + +func TestTimespanCreateSecondsFract(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 100000000, time.UTC), + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{Value: 2.0}, + }, + }, + } + ts.createRatedSecondSlice() + if len(ts.ratedSeconds) != 31 { + t.Error("Error creating second slice: ", ts.ratedSeconds) + } + t.Log(ts.getCost()) + if ts.ratedSeconds[30].rate != 0.2 { + t.Error("Wrong second slice: ", ts.ratedSeconds[30]) + } +} diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index a466bb2bc..1e22acfd1 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -247,7 +247,6 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refoundDuration := end.Sub(hangupTime).Seconds() cost := 0.0 - seconds := 0.0 engine.Logger.Info(fmt.Sprintf("Refund duration: %v", refoundDuration)) for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] @@ -258,18 +257,11 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { tmpCost := (procentage * ts.Cost) / 100 ts.Cost -= tmpCost cost += tmpCost - if ts.MinuteInfo != nil { - // DestinationPrefix and Price take from lastCC and above caclulus - seconds += (procentage * ts.MinuteInfo.Quantity) / 100 - } // set the end time to now ts.TimeEnd = hangupTime break // do not go to other timespans } else { cost += ts.Cost - if ts.MinuteInfo != nil { - seconds += ts.MinuteInfo.Quantity - } // remove the timestamp entirely lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refound @@ -293,25 +285,8 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { engine.Logger.Err(fmt.Sprintf("Debit cents failed: %v", err)) } } - if seconds > 0 { - cd := &engine.CallDescriptor{ - Direction: lastCC.Direction, - TOR: lastCC.TOR, - Tenant: lastCC.Tenant, - Subject: lastCC.Subject, - Account: lastCC.Account, - Destination: lastCC.Destination, - Amount: -seconds, - // FallbackSubject: lastCC.FallbackSubject, // ToDo: check how to best add it - } - var response float64 - err := sm.connector.DebitSeconds(*cd, &response) - if err != nil { - engine.Logger.Err(fmt.Sprintf("Debit seconds failed: %v", err)) - } - } lastCC.Cost -= cost - engine.Logger.Info(fmt.Sprintf("Rambursed %v cents, %v seconds", cost, seconds)) + engine.Logger.Info(fmt.Sprintf("Rambursed %v cents", cost)) } From bb22d37fc64e100bf330169a9df0a0e51c086024 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 14 Sep 2013 15:23:52 +0300 Subject: [PATCH 17/39] working on csv load testing --- engine/loader_csv_test.go | 453 +++++++++++++++++++++++++++++++++++++- engine/loader_helpers.go | 16 +- engine/rateinterval.go | 5 +- 3 files changed, 465 insertions(+), 9 deletions(-) diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index d2e7ada2f..28c7e884b 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -19,8 +19,10 @@ along with this program. If not, see package engine import ( - //"log" + "github.com/cgrates/cgrates/utils" + "reflect" "testing" + "time" ) var ( @@ -120,38 +122,487 @@ func TestLoadDestinations(t *testing.T) { if len(csvr.destinations) != 6 { t.Error("Failed to load destinations: ", csvr.destinations) } + for _, d := range csvr.destinations { + switch d.Id { + case "NAT": + if !reflect.DeepEqual(d.Prefixes, []string{`0256`, `0257`, `0723`}) { + t.Error("Faild to load destinations", d) + } + case "ALL": + if !reflect.DeepEqual(d.Prefixes, []string{`49`, `41`, `43`}) { + t.Error("Faild to load destinations", d) + } + case "RET": + if !reflect.DeepEqual(d.Prefixes, []string{`0723`, `0724`}) { + t.Error("Faild to load destinations", d) + } + case "GERMANY": + if !reflect.DeepEqual(d.Prefixes, []string{`49`}) { + t.Error("Faild to load destinations", d) + } + case "GERMANY_O2": + if !reflect.DeepEqual(d.Prefixes, []string{`41`}) { + t.Error("Faild to load destinations", d) + } + case "GERMANY_PREMIUM": + if !reflect.DeepEqual(d.Prefixes, []string{`43`}) { + t.Error("Faild to load destinations", d) + } + default: + t.Error("Unknown destination tag!") + } + } } func TestLoadTimimgs(t *testing.T) { if len(csvr.timings) != 4 { t.Error("Failed to load timings: ", csvr.timings) } + timing := csvr.timings["WORKDAYS_00"] + if !reflect.DeepEqual(timing, &Timing{ + Id: "WORKDAYS_00", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00", + }) { + t.Error("Error loading timing: ", timing) + } + timing = csvr.timings["WORKDAYS_18"] + if !reflect.DeepEqual(timing, &Timing{ + Id: "WORKDAYS_18", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "18:00:00", + }) { + t.Error("Error loading timing: ", timing) + } + timing = csvr.timings["WEEKENDS"] + if !reflect.DeepEqual(timing, &Timing{ + Id: "WEEKENDS", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{time.Saturday, time.Sunday}, + StartTime: "00:00:00", + }) { + t.Error("Error loading timing: ", timing) + } + timing = csvr.timings["ONE_TIME_RUN"] + if !reflect.DeepEqual(timing, &Timing{ + Id: "ONE_TIME_RUN", + Years: Years{2012}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{}, + StartTime: "*asap", + }) { + t.Error("Error loading timing: ", timing) + } } func TestLoadRates(t *testing.T) { if len(csvr.rates) != 5 { t.Error("Failed to load rates: ", csvr.rates) } + rate := csvr.rates["R1"] + if !reflect.DeepEqual(rate, &LoadRate{ + Tag: "R1", + ConnectFee: 0, + Price: 0.2, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Weight: 10, + }) { + t.Error("Error loading rate: ", csvr.rates) + } + rate = csvr.rates["R2"] + if !reflect.DeepEqual(rate, &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Weight: 10, + }) { + t.Error("Error loading rate: ", csvr.rates) + } + rate = csvr.rates["R3"] + if !reflect.DeepEqual(rate, &LoadRate{ + Tag: "R3", + ConnectFee: 0, + Price: 0.05, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Weight: 10, + }) { + t.Error("Error loading rate: ", csvr.rates) + } + rate = csvr.rates["R4"] + if !reflect.DeepEqual(rate, &LoadRate{ + Tag: "R4", + ConnectFee: 1, + Price: 1.0, + RateUnit: time.Second, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: utils.ROUNDING_UP, + RoundingDecimals: 2, + Weight: 10, + }) { + t.Error("Error loading rate: ", csvr.rates) + } + rate = csvr.rates["R5"] + if !reflect.DeepEqual(rate, &LoadRate{ + Tag: "R5", + ConnectFee: 0, + Price: 0.5, + RateUnit: time.Second, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: utils.ROUNDING_DOWN, + RoundingDecimals: 2, + Weight: 10, + }) { + t.Error("Error loading rate: ", csvr.rates) + } + } func TestLoadDestinationRates(t *testing.T) { if len(csvr.destinationRates) != 5 { t.Error("Failed to load rates: ", csvr.rates) } + drs := csvr.destinationRates["RT_STANDARD"] + if !reflect.DeepEqual(drs, []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY", + Rate: csvr.rates["R1"], + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_O2", + Rate: csvr.rates["R2"], + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_PREMIUM", + Rate: csvr.rates["R2"], + }, + }) { + t.Error("Error loading destination rate: ", drs) + } + drs = csvr.destinationRates["RT_DEFAULT"] + if !reflect.DeepEqual(drs, []*DestinationRate{ + &DestinationRate{ + Tag: "RT_DEFAULT", + DestinationsTag: "ALL", + Rate: csvr.rates["R2"], + }, + }) { + t.Error("Error loading destination rate: ", drs) + } + drs = csvr.destinationRates["RT_STD_WEEKEND"] + if !reflect.DeepEqual(drs, []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY", + Rate: csvr.rates["R2"], + }, + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY_O2", + Rate: csvr.rates["R3"], + }, + }) { + t.Error("Error loading destination rate: ", drs) + } + drs = csvr.destinationRates["P1"] + if !reflect.DeepEqual(drs, []*DestinationRate{ + &DestinationRate{ + Tag: "P1", + DestinationsTag: "NAT", + Rate: csvr.rates["R4"], + }, + }) { + t.Error("Error loading destination rate: ", drs) + } + drs = csvr.destinationRates["P2"] + if !reflect.DeepEqual(drs, []*DestinationRate{ + &DestinationRate{ + Tag: "P2", + DestinationsTag: "NAT", + Rate: csvr.rates["R5"], + }, + }) { + t.Error("Error loading destination rate: ", drs) + } } func TestLoadDestinationRateTimings(t *testing.T) { if len(csvr.activationPeriods) != 4 { t.Error("Failed to load rate timings: ", csvr.activationPeriods) } + rplan := csvr.activationPeriods["STANDARD"] + expected := &RatingPlan{ + ActivationTime: time.Time{}, + RateIntervals: RateIntervalList{ + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.2, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + &Rate{ + GroupIntervalStart: 0, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + }, + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "18:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + &Rate{ + GroupIntervalStart: 0, + Value: 0.05, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + }, + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{6, 0}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + &Rate{ + GroupIntervalStart: 0, + Value: 0.05, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + }, + }, + } + if !reflect.DeepEqual(rplan, expected) { + t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["STANDARD"]) + } + rplan = csvr.activationPeriods["PREMIUM"] + expected = &RatingPlan{ + ActivationTime: time.Time{}, + RateIntervals: RateIntervalList{ + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{6, 0}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + &Rate{ + GroupIntervalStart: 0, + Value: 0.05, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + }, + }, + } + if !reflect.DeepEqual(rplan, expected) { + t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["PREMIUM"]) + } + rplan = csvr.activationPeriods["DEFAULT"] + expected = &RatingPlan{ + ActivationTime: time.Time{}, + RateIntervals: RateIntervalList{ + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.1, + RateIncrement: time.Second, + RateUnit: time.Minute, + }, + }, + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + }, + }, + } + if !reflect.DeepEqual(rplan, expected) { + t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["DEFAULT"]) + } + rplan = csvr.activationPeriods["EVENING"] + expected = &RatingPlan{ + ActivationTime: time.Time{}, + RateIntervals: RateIntervalList{ + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 1, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 1, + RateIncrement: time.Second, + RateUnit: time.Second, + }, + }, + RoundingMethod: utils.ROUNDING_UP, + RoundingDecimals: 2, + }, + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "18:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.5, + RateIncrement: time.Second, + RateUnit: time.Second, + }, + }, + RoundingMethod: utils.ROUNDING_DOWN, + RoundingDecimals: 2, + }, + &RateInterval{ + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{6, 0}, + StartTime: "00:00:00", + EndTime: "", + Weight: 10, + ConnectFee: 0, + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0.5, + RateIncrement: time.Second, + RateUnit: time.Second, + }, + }, + RoundingMethod: utils.ROUNDING_DOWN, + RoundingDecimals: 2, + }, + }, + } + if !reflect.DeepEqual(rplan, expected) { + t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["EVENING"]) + } } func TestLoadRatingProfiles(t *testing.T) { if len(csvr.ratingProfiles) != 9 { t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles) } + rp := csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] + expected := &RatingProfile{} + if reflect.DeepEqual(rp, expected) { + t.Error("Error loading rating profile: ", csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"]) + } } +/* +CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb +CUSTOMER_1,0,*out,rif:from:tm,2012-02-28T00:00:00Z,STANDARD,danb +CUSTOMER_2,0,*out,danb:87.139.12.167,2012-01-01T00:00:00Z,STANDARD,danb +CUSTOMER_1,0,*out,danb,2012-01-01T00:00:00Z,PREMIUM, +vdf,0,*out,rif,2012-01-01T00:00:00Z,EVENING, +vdf,0,*out,rif,2012-02-28T00:00:00Z,EVENING, +vdf,0,*out,minu,2012-01-01T00:00:00Z,EVENING, +vdf,0,*out,*any,2012-02-28T00:00:00Z,EVENING, +vdf,0,*out,one,2012-02-28T00:00:00Z,STANDARD, +vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf +vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif +*/ + func TestLoadActions(t *testing.T) { if len(csvr.actions) != 1 { t.Error("Failed to load actions: ", csvr.actions) diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 393e04f86..8d9291f88 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -157,13 +157,15 @@ func NewDestinationRateTiming(destinationRatesTag string, timing *Timing, weight func (rt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { i = &RateInterval{ - Years: rt.timing.Years, - Months: rt.timing.Months, - MonthDays: rt.timing.MonthDays, - WeekDays: rt.timing.WeekDays, - StartTime: rt.timing.StartTime, - Weight: rt.Weight, - ConnectFee: dr.Rate.ConnectFee, + Years: rt.timing.Years, + Months: rt.timing.Months, + MonthDays: rt.timing.MonthDays, + WeekDays: rt.timing.WeekDays, + StartTime: rt.timing.StartTime, + Weight: rt.Weight, + ConnectFee: dr.Rate.ConnectFee, + RoundingMethod: dr.Rate.RoundingMethod, + RoundingDecimals: dr.Rate.RoundingDecimals, Rates: RateGroups{&Rate{ GroupIntervalStart: dr.Rate.GroupIntervalStart, Value: dr.Rate.Price, diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 9857be0f1..60b2293e7 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -51,7 +51,10 @@ type Rate struct { } func (p *Rate) Equal(o *Rate) bool { - return p.GroupIntervalStart == o.GroupIntervalStart && p.Value == o.Value && p.RateIncrement == o.RateIncrement && p.RateUnit == o.RateUnit + return p.GroupIntervalStart == o.GroupIntervalStart && + p.Value == o.Value && + p.RateIncrement == o.RateIncrement && + p.RateUnit == o.RateUnit } type RateGroups []*Rate From fa788103cf408d3d301d5b4a7831bbdc7f21c879 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 14 Sep 2013 17:58:52 +0300 Subject: [PATCH 18/39] added test for all csv loaders --- engine/action.go | 1 + engine/action_timing.go | 2 +- engine/loader_csv.go | 2 + engine/loader_csv_test.go | 87 +++++++++++++++++++++++++++++++++++++-- engine/rateinterval.go | 2 +- engine/userbalance.go | 7 ++-- 6 files changed, 92 insertions(+), 9 deletions(-) diff --git a/engine/action.go b/engine/action.go index 23463914f..9c4810da1 100644 --- a/engine/action.go +++ b/engine/action.go @@ -48,6 +48,7 @@ const ( DEBIT = "*debit" RESET_COUNTER = "*reset_counter" RESET_COUNTERS = "*reset_counters" + UNLIMITED = "*unlimited" ) type actionTypeFunc func(*UserBalance, *Action) error diff --git a/engine/action_timing.go b/engine/action_timing.go index ae84f8835..9262bd71d 100644 --- a/engine/action_timing.go +++ b/engine/action_timing.go @@ -287,6 +287,6 @@ func (atpl ActionTimingPriotityList) Sort() { sort.Sort(atpl) } -func (at *ActionTiming) String() string { +func (at *ActionTiming) String_DISABLED() string { return at.Tag + " " + at.GetNextStartTime().String() + ",w: " + strconv.FormatFloat(at.Weight, 'f', -1, 64) } diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 1413a8a64..c98a1b849 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -368,6 +368,7 @@ func (csvr *CSVReader) LoadActions() (err error) { Weight: weight, ExpirationString: record[5], Balance: &Balance{ + Id: utils.GenUUID(), Value: units, Weight: minutesWeight, SpecialPrice: value, @@ -412,6 +413,7 @@ func (csvr *CSVReader) LoadActionTimings() (err error) { Tag: record[2], Weight: weight, Timing: &RateInterval{ + Years: t.Years, Months: t.Months, MonthDays: t.MonthDays, WeekDays: t.WeekDays, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 28c7e884b..90ee0ac1c 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -87,14 +87,14 @@ vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif ` actions = ` -MINI,TOPUP,MINUTES,*out,100,*unlimited,NAT,*absolute,0,10,10 +MINI,*topup,*minutes,*out,100,*unlimited,NAT,*absolute,0,10,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 ` actionTriggers = ` -STANDARD_TRIGGER,MINUTES,*out,*min_counter,10,GERMANY_O2,SOME_1,10 -STANDARD_TRIGGER,MINUTES,*out,*max_balance,200,GERMANY,SOME_2,10 +STANDARD_TRIGGER,*minutes,*out,*min_counter,10,GERMANY_O2,SOME_1,10 +STANDARD_TRIGGER,*minutes,*out,*max_balance,200,GERMANY,SOME_2,10 ` accountActions = ` vdf,minitsboy,*out,MORE_MINUTES,STANDARD_TRIGGER @@ -607,22 +607,103 @@ func TestLoadActions(t *testing.T) { if len(csvr.actions) != 1 { t.Error("Failed to load actions: ", csvr.actions) } + a := csvr.actions["MINI"][0] + expected := &Action{ + Id: a.Id, + ActionType: TOPUP, + BalanceId: MINUTES, + Direction: OUTBOUND, + ExpirationString: UNLIMITED, + Weight: 10, + Balance: &Balance{ + Id: a.Balance.Id, + Value: 100, + Weight: 10, + SpecialPriceType: PRICE_ABSOLUTE, + SpecialPrice: 0, + DestinationId: "NAT", + }, + } + if !reflect.DeepEqual(a, expected) { + t.Error("Error loading action: ", a) + } } func TestLoadActionTimings(t *testing.T) { if len(csvr.actionsTimings) != 1 { t.Error("Failed to load action timings: ", csvr.actionsTimings) } + atm := csvr.actionsTimings["MORE_MINUTES"][0] + expected := &ActionTiming{ + Id: atm.Id, + Tag: "ONE_TIME_RUN", + UserBalanceIds: []string{"*out:vdf:minitsboy"}, + Timing: &RateInterval{ + Years: Years{2012}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{}, + StartTime: ASAP, + }, + Weight: 10, + ActionsId: "MINI", + } + if !reflect.DeepEqual(atm, expected) { + t.Error("Error loading action timing: ", atm) + } } func TestLoadActionTriggers(t *testing.T) { if len(csvr.actionsTriggers) != 1 { t.Error("Failed to load action triggers: ", csvr.actionsTriggers) } + atr := csvr.actionsTriggers["STANDARD_TRIGGER"][0] + expected := &ActionTrigger{ + Id: atr.Id, + BalanceId: MINUTES, + Direction: OUTBOUND, + ThresholdType: TRIGGER_MIN_COUNTER, + ThresholdValue: 10, + DestinationId: "GERMANY_O2", + Weight: 10, + ActionsId: "SOME_1", + Executed: false, + } + if !reflect.DeepEqual(atr, expected) { + t.Error("Error loading action trigger: ", atr) + } + atr = csvr.actionsTriggers["STANDARD_TRIGGER"][1] + expected = &ActionTrigger{ + Id: atr.Id, + BalanceId: MINUTES, + Direction: OUTBOUND, + ThresholdType: TRIGGER_MAX_BALANCE, + ThresholdValue: 200, + DestinationId: "GERMANY", + Weight: 10, + ActionsId: "SOME_2", + Executed: false, + } + if !reflect.DeepEqual(atr, expected) { + t.Error("Error loading action trigger: ", atr) + } } func TestLoadAccountActions(t *testing.T) { if len(csvr.accountActions) != 1 { t.Error("Failed to load account actions: ", csvr.accountActions) } + aa := csvr.accountActions[0] + expected := &UserBalance{ + Id: "*out:vdf:minitsboy", + Type: UB_TYPE_PREPAID, + ActionTriggers: csvr.actionsTriggers["STANDARD_TRIGGER"], + } + if !reflect.DeepEqual(aa, expected) { + t.Error("Error loading account action: ", aa) + } } + +/* +vdf,minitsboy,*out,MORE_MINUTES,STANDARD_TRIGGER +*/ diff --git a/engine/rateinterval.go b/engine/rateinterval.go index 60b2293e7..c6da5ed9f 100644 --- a/engine/rateinterval.go +++ b/engine/rateinterval.go @@ -183,7 +183,7 @@ func (i *RateInterval) getLeftMargin(t time.Time) (rigthtTime time.Time) { return time.Date(year, month, day, hour, min, sec, nsec, loc) } -func (i *RateInterval) String() string { +func (i *RateInterval) String_DISABLED() string { return fmt.Sprintf("%v %v %v %v %v %v", i.Years, i.Months, i.MonthDays, i.WeekDays, i.StartTime, i.EndTime) } diff --git a/engine/userbalance.go b/engine/userbalance.go index b387144f9..81f254cf2 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -55,10 +55,9 @@ Structure containing information about user's credit (minutes, cents, sms...).' This can represent a user or a shared group. */ type UserBalance struct { - Id string - Type string // prepaid-postpaid - BalanceMap map[string]BalanceChain - //MinuteBuckets []*MinuteBucket + Id string + Type string // prepaid-postpaid + BalanceMap map[string]BalanceChain UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList From 8cda25cadbebfdfdd5674ab0ff63a5dcc184ee50 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 14 Sep 2013 18:10:00 +0300 Subject: [PATCH 19/39] one more action --- engine/loader_csv_test.go | 48 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 90ee0ac1c..5849e28d7 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -87,6 +87,7 @@ vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif ` actions = ` +MINI,*topup_reset,*monetary,*out,10,*unlimited,,,0,10,10 MINI,*topup,*minutes,*out,100,*unlimited,NAT,*absolute,0,10,10 ` actionTimings = ` @@ -607,25 +608,40 @@ func TestLoadActions(t *testing.T) { if len(csvr.actions) != 1 { t.Error("Failed to load actions: ", csvr.actions) } - a := csvr.actions["MINI"][0] - expected := &Action{ - Id: a.Id, - ActionType: TOPUP, - BalanceId: MINUTES, - Direction: OUTBOUND, - ExpirationString: UNLIMITED, - Weight: 10, - Balance: &Balance{ - Id: a.Balance.Id, - Value: 100, + as := csvr.actions["MINI"] + expected := []*Action{ + &Action{ + Id: as[0].Id, + ActionType: TOPUP_RESET, + BalanceId: CREDIT, + Direction: OUTBOUND, + ExpirationString: UNLIMITED, Weight: 10, - SpecialPriceType: PRICE_ABSOLUTE, - SpecialPrice: 0, - DestinationId: "NAT", + Balance: &Balance{ + Id: as[0].Balance.Id, + Value: 10, + Weight: 10, + }, + }, + &Action{ + Id: as[1].Id, + ActionType: TOPUP, + BalanceId: MINUTES, + Direction: OUTBOUND, + ExpirationString: UNLIMITED, + Weight: 10, + Balance: &Balance{ + Id: as[1].Balance.Id, + Value: 100, + Weight: 10, + SpecialPriceType: PRICE_ABSOLUTE, + SpecialPrice: 0, + DestinationId: "NAT", + }, }, } - if !reflect.DeepEqual(a, expected) { - t.Error("Error loading action: ", a) + if !reflect.DeepEqual(as, expected) { + t.Error("Error loading action: ", as) } } From 1ff176cf47e027cbfe57df5e415d05cd5fd350f6 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sat, 14 Sep 2013 20:09:59 +0300 Subject: [PATCH 20/39] more name refactorings --- engine/loader_csv.go | 36 ++++++++++++++++++------------------ engine/loader_csv_test.go | 20 ++++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/engine/loader_csv.go b/engine/loader_csv.go index c98a1b849..dd7f0c4c5 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -30,19 +30,19 @@ import ( ) type CSVReader struct { - sep rune - storage DataStorage - readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) - actions map[string][]*Action - actionsTimings map[string][]*ActionTiming - actionsTriggers map[string][]*ActionTrigger - accountActions []*UserBalance - destinations []*Destination - timings map[string]*Timing - rates map[string]*LoadRate - destinationRates map[string][]*DestinationRate - activationPeriods map[string]*RatingPlan - ratingProfiles map[string]*RatingProfile + sep rune + storage DataStorage + readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) + actions map[string][]*Action + actionsTimings map[string][]*ActionTiming + actionsTriggers map[string][]*ActionTrigger + accountActions []*UserBalance + destinations []*Destination + timings map[string]*Timing + rates map[string]*LoadRate + destinationRates map[string][]*DestinationRate + ratingPlans map[string]*RatingPlan + ratingProfiles map[string]*RatingProfile // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string @@ -58,7 +58,7 @@ func NewFileCSVReader(storage DataStorage, sep rune, destinationsFn, timingsFn, c.rates = make(map[string]*LoadRate) c.destinationRates = make(map[string][]*DestinationRate) c.timings = make(map[string]*Timing) - c.activationPeriods = make(map[string]*RatingPlan) + c.ratingPlans = make(map[string]*RatingPlan) c.ratingProfiles = make(map[string]*RatingProfile) c.readerFunc = openFileCSVReader c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, @@ -284,10 +284,10 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1])) } for _, dr := range drs { - if _, exists := csvr.activationPeriods[tag]; !exists { - csvr.activationPeriods[tag] = &RatingPlan{} + if _, exists := csvr.ratingPlans[tag]; !exists { + csvr.ratingPlans[tag] = &RatingPlan{} } - csvr.activationPeriods[tag].AddRateInterval(rt.GetRateInterval(dr)) + csvr.ratingPlans[tag].AddRateInterval(rt.GetRateInterval(dr)) } } return @@ -316,7 +316,7 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { csvr.ratingProfiles[key] = rp } for _, d := range csvr.destinations { - ap, exists := csvr.activationPeriods[record[5]] + ap, exists := csvr.ratingPlans[record[5]] if !exists { return errors.New(fmt.Sprintf("Could not load ratinTiming for tag: %v", record[5])) } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 5849e28d7..5c0ff31bf 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -354,10 +354,10 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.activationPeriods) != 4 { - t.Error("Failed to load rate timings: ", csvr.activationPeriods) + if len(csvr.ratingPlans) != 4 { + t.Error("Failed to load rate timings: ", csvr.ratingPlans) } - rplan := csvr.activationPeriods["STANDARD"] + rplan := csvr.ratingPlans["STANDARD"] expected := &RatingPlan{ ActivationTime: time.Time{}, RateIntervals: RateIntervalList{ @@ -442,9 +442,9 @@ func TestLoadDestinationRateTimings(t *testing.T) { }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["STANDARD"]) + t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["STANDARD"]) } - rplan = csvr.activationPeriods["PREMIUM"] + rplan = csvr.ratingPlans["PREMIUM"] expected = &RatingPlan{ ActivationTime: time.Time{}, RateIntervals: RateIntervalList{ @@ -477,9 +477,9 @@ func TestLoadDestinationRateTimings(t *testing.T) { }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["PREMIUM"]) + t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["PREMIUM"]) } - rplan = csvr.activationPeriods["DEFAULT"] + rplan = csvr.ratingPlans["DEFAULT"] expected = &RatingPlan{ ActivationTime: time.Time{}, RateIntervals: RateIntervalList{ @@ -506,9 +506,9 @@ func TestLoadDestinationRateTimings(t *testing.T) { }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["DEFAULT"]) + t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["DEFAULT"]) } - rplan = csvr.activationPeriods["EVENING"] + rplan = csvr.ratingPlans["EVENING"] expected = &RatingPlan{ ActivationTime: time.Time{}, RateIntervals: RateIntervalList{ @@ -575,7 +575,7 @@ func TestLoadDestinationRateTimings(t *testing.T) { }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.activationPeriods["EVENING"]) + t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["EVENING"]) } } From f90b9e7aa3a36e7bc9038e5fb2d639d2edd53d7b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 18 Sep 2013 17:55:07 +0300 Subject: [PATCH 21/39] destinations are now stored in sets --- engine/calldesc.go | 6 +++--- engine/destinations_test.go | 13 ++++++++++--- engine/storage_interface.go | 1 + engine/storage_redis.go | 22 ++++++++++++++-------- engine/storage_test.go | 21 +++++++++++++++++++++ utils/coreutils.go | 11 +++++++++++ 6 files changed, 60 insertions(+), 14 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index ca1cd976d..35fbe3161 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -36,11 +36,11 @@ func init() { Logger = new(utils.StdLogger) Logger.Err(fmt.Sprintf("Could not connect to syslog: %v", err)) } - //db_server := "127.0.0.1" + db_server := "127.0.0.1" //db_server := "192.168.0.17" - m, _ := NewMapStorage() + //m, _ := NewMapStorage() //m, _ := NewMongoStorage(db_server, "27017", "cgrates_test", "", "") - //m, _ := NewRedisStorage(db_server+":6379", 11, "") + m, _ := NewRedisStorage(db_server+":6379", 11, "") //m, _ := NewRedigoStorage(db_server+":6379", 11, "") //m, _ := NewRadixStorage(db_server+":6379", 11, "") storageGetter, _ = m.(DataStorage) diff --git a/engine/destinations_test.go b/engine/destinations_test.go index c5ccc3ca9..56e875d49 100644 --- a/engine/destinations_test.go +++ b/engine/destinations_test.go @@ -21,7 +21,7 @@ package engine import ( "encoding/json" "github.com/cgrates/cgrates/cache2go" - "reflect" + "github.com/cgrates/cgrates/utils" "testing" ) @@ -43,7 +43,8 @@ func TestDestinationStorageStore(t *testing.T) { t.Error("Error storing destination: ", err) } result, err := storageGetter.GetDestination(nationale.Id) - if !reflect.DeepEqual(nationale, result) { + var ss utils.StringSlice = result.Prefixes + if !ss.Contains("0257") || !ss.Contains("0256") || !ss.Contains("0723") { t.Errorf("Expected %q was %q", nationale, result) } } @@ -54,7 +55,6 @@ func TestDestinationContainsPrefix(t *testing.T) { if !ok || precision != len("0256") { t.Error("Should contain prefix: ", nationale) } - } func TestDestinationContainsPrefixLong(t *testing.T) { @@ -63,7 +63,14 @@ func TestDestinationContainsPrefixLong(t *testing.T) { if !ok || precision != len("0256") { t.Error("Should contain prefix: ", nationale) } +} +func TestDestinationContainsPrefixWrong(t *testing.T) { + nationale := &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}} + precision, ok := nationale.containsPrefix("01234567") + if ok || precision != 0 { + t.Error("Should not contain prefix: ", nationale) + } } func TestDestinationGetExists(t *testing.T) { diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 161c5c930..0fe0b567a 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -36,6 +36,7 @@ const ( ACTION_PREFIX = "act_" USER_BALANCE_PREFIX = "ubl_" DESTINATION_PREFIX = "dst_" + TEMP_DESTINATION_PREFIX = "tmp_" LOG_CALL_COST_PREFIX = "cco_" LOG_ACTION_TIMMING_PREFIX = "ltm_" LOG_ACTION_TRIGGER_PREFIX = "ltr_" diff --git a/engine/storage_redis.go b/engine/storage_redis.go index 4addfb9c5..d0d0ac95c 100644 --- a/engine/storage_redis.go +++ b/engine/storage_redis.go @@ -89,30 +89,36 @@ func (rs *RedisStorage) SetRatingProfile(rp *RatingProfile) (err error) { func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) { var values []string - if values, err = rs.db.HKeys(DESTINATION_PREFIX + key); len(values) > 0 && err == nil { + if values, err = rs.db.SMembers(DESTINATION_PREFIX + key); len(values) > 0 && err == nil { dest = &Destination{Id: key, Prefixes: values} } return } func (rs *RedisStorage) DestinationContainsPrefix(key string, prefix string) (precision int, err error) { + if _, err := rs.db.SAdd(TEMP_DESTINATION_PREFIX+prefix, utils.SplitPrefixInterface(prefix)...); err != nil { + return 0, err + } var values []string - if values, err = rs.db.HMGet(DESTINATION_PREFIX+key, utils.SplitPrefix(prefix)...); err == nil { - for i, p := range values { - if p != "" { - return len(prefix) - i, nil + if values, err = rs.db.SInter(DESTINATION_PREFIX+key, TEMP_DESTINATION_PREFIX+prefix); err == nil { + for _, p := range values { + if len(p) > precision { + precision = len(p) } } } + if _, err := rs.db.Del(TEMP_DESTINATION_PREFIX + prefix); err != nil { + Logger.Err("Error removing temp ") + } return } func (rs *RedisStorage) SetDestination(dest *Destination) (err error) { - var newPrefixes []interface{} + var prefixes []interface{} for _, p := range dest.Prefixes { - newPrefixes = append(newPrefixes, p, "*") + prefixes = append(prefixes, p) } - _, err = rs.db.HMSet(DESTINATION_PREFIX+dest.Id, newPrefixes...) + _, err = rs.db.SAdd(DESTINATION_PREFIX+dest.Id, prefixes...) if err == nil && historyScribe != nil { response := 0 historyScribe.Record(&history.Record{DESTINATION_PREFIX + dest.Id, dest}, &response) diff --git a/engine/storage_test.go b/engine/storage_test.go index 4a297a4b4..7b450c21c 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -71,6 +71,27 @@ func TestMsgpackTime(t *testing.T) { } } +func TestStorageDestinationContainsPrefixShort(t *testing.T) { + precision, err := storageGetter.DestinationContainsPrefix("NAT", "0723") + if err != nil || precision != 4 { + t.Error("Error finding prefix: ", err, precision) + } +} + +func TestStorageDestinationContainsPrefixLong(t *testing.T) { + precision, err := storageGetter.DestinationContainsPrefix("NAT", "0723045326") + if err != nil || precision != 4 { + t.Error("Error finding prefix: ", err, precision) + } +} + +func TestStorageDestinationContainsPrefixNotExisting(t *testing.T) { + precision, err := storageGetter.DestinationContainsPrefix("NAT", "072") + if err != nil || precision != 0 { + t.Error("Error finding prefix: ", err, precision) + } +} + /************************** Benchmarks *****************************/ func GetUB() *UserBalance { diff --git a/utils/coreutils.go b/utils/coreutils.go index 73b047373..68e26a294 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -131,6 +131,17 @@ func RoundTo(whole, amount time.Duration) time.Duration { return time.Duration((w - math.Mod(a, w)) + a) } +type StringSlice []string + +func (ss StringSlice) Contains(needle string) bool { + for _, hay := range ss { + if hay == needle { + return true + } + } + return false +} + func SplitPrefix(prefix string) []string { var subs []string max := len(prefix) From 8c1ad3de869dbf7f6b5e1a50084499b0082b04ba Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 18 Sep 2013 18:09:56 +0300 Subject: [PATCH 22/39] reverted to map for tests --- engine/calldesc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 35fbe3161..ca1cd976d 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -36,11 +36,11 @@ func init() { Logger = new(utils.StdLogger) Logger.Err(fmt.Sprintf("Could not connect to syslog: %v", err)) } - db_server := "127.0.0.1" + //db_server := "127.0.0.1" //db_server := "192.168.0.17" - //m, _ := NewMapStorage() + m, _ := NewMapStorage() //m, _ := NewMongoStorage(db_server, "27017", "cgrates_test", "", "") - m, _ := NewRedisStorage(db_server+":6379", 11, "") + //m, _ := NewRedisStorage(db_server+":6379", 11, "") //m, _ := NewRedigoStorage(db_server+":6379", 11, "") //m, _ := NewRadixStorage(db_server+":6379", 11, "") storageGetter, _ = m.(DataStorage) From cd98141fbb1d599db22e55c14064518b5cd419a1 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 23 Sep 2013 19:26:40 +0300 Subject: [PATCH 23/39] still working on balance subject debit --- engine/actions_test.go | 6 +- engine/balances.go | 17 +++-- engine/balances_test.go | 8 +-- engine/callcost.go | 12 ++++ engine/calldesc.go | 23 ++++-- engine/timespans.go | 60 ++++++++++------ engine/timespans_test.go | 44 ++++++++---- engine/userbalance.go | 133 +++++++++++++++++++++++------------ engine/userbalance_test.go | 23 +++--- hard_update_external_libs.py | 49 +++++++++++++ utils/utils_test.go | 5 ++ 11 files changed, 270 insertions(+), 110 deletions(-) create mode 100755 hard_update_external_libs.py diff --git a/engine/actions_test.go b/engine/actions_test.go index 06d2a595f..6eba519dc 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -763,8 +763,8 @@ func TestActionResetAllCounters(t *testing.T) { t.FailNow() } mb := ub.UnitCounters[0].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { - t.Errorf("Balanxce cloned incorrectly: %v!", mb) + if mb.Weight != 20 || mb.Value != 0 || mb.DestinationId != "NAT" { + t.Errorf("Balance cloned incorrectly: %v!", mb) } } @@ -792,7 +792,7 @@ func TestActionResetCounterMinutes(t *testing.T) { t.FailNow() } mb := ub.UnitCounters[1].MinuteBalances[0] - if mb.Weight != 20 || mb.SpecialPrice != 1 || mb.Value != 0 || mb.DestinationId != "NAT" { + if mb.Weight != 20 || mb.Value != 0 || mb.DestinationId != "NAT" { t.Errorf("Minute bucked cloned incorrectly: %v!", mb) } } diff --git a/engine/balances.go b/engine/balances.go index 1b0ebb0a2..4065de810 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -41,8 +41,8 @@ type Balance struct { func (b *Balance) Equal(o *Balance) bool { return b.ExpirationDate.Equal(o.ExpirationDate) && b.Weight == o.Weight && - b.SpecialPrice == o.SpecialPrice && - b.DestinationId == o.DestinationId + b.DestinationId == o.DestinationId && + b.RateSubject == o.RateSubject } func (b *Balance) IsExpired() bool { @@ -51,13 +51,12 @@ func (b *Balance) IsExpired() bool { func (b *Balance) Clone() *Balance { return &Balance{ - Id: b.Id, - Value: b.Value, - SpecialPrice: b.SpecialPrice, - SpecialPriceType: b.SpecialPriceType, - DestinationId: b.DestinationId, - ExpirationDate: b.ExpirationDate, - Weight: b.Weight, + Id: b.Id, + Value: b.Value, + DestinationId: b.DestinationId, + ExpirationDate: b.ExpirationDate, + Weight: b.Weight, + RateSubject: b.RateSubject, } } diff --git a/engine/balances_test.go b/engine/balances_test.go index 24db6b1a4..20a3179af 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -57,16 +57,16 @@ func TestBalanceSortSpecialPrice(t *testing.T) { } func TestBalanceEqual(t *testing.T) { - mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} - mb3 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: ""} + mb1 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} + mb2 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} + mb3 := &Balance{Weight: 1, precision: 1, RateSubject: "2", DestinationId: ""} if !mb1.Equal(mb2) || mb2.Equal(mb3) { t.Error("Equal failure!", mb1 == mb2, mb3) } } func TestBalanceClone(t *testing.T) { - mb1 := &Balance{Value: 1, Weight: 2, SpecialPrice: 3, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "5"} + mb1 := &Balance{Value: 1, Weight: 2, RateSubject: "test", DestinationId: "5"} mb2 := mb1.Clone() if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) { t.Errorf("Cloning failure: \n%v\n%v", mb1, mb2) diff --git a/engine/callcost.go b/engine/callcost.go index 3c3c371e4..778b54e53 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -73,3 +73,15 @@ func (cc *CallCost) GetTotalDuration() (td time.Duration) { } return } + +// Creates a CallDescriptor structure copying related data from CallCost +func (cc *CallCost) CreateCallDescriptor() *CallDescriptor { + return &CallDescriptor{ + Direction: cc.Direction, + TOR: cc.TOR, + Tenant: cc.Tenant, + Subject: cc.Subject, + Account: cc.Account, + Destination: cc.Destination, + } +} diff --git a/engine/calldesc.go b/engine/calldesc.go index ca1cd976d..d82a69c7b 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -104,7 +104,7 @@ type CallDescriptor struct { Tenant, Subject, Account, Destination string TimeStart, TimeEnd time.Time LoopIndex float64 // indicates the position of this segment in a cost request loop - CallDuration time.Duration // the call duration so far (partial or final) + CallDuration time.Duration // the call duration so far (till TimeEnd) Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject RatingPlans []*RatingPlan @@ -248,12 +248,13 @@ func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*Ti if ts.RateInterval != nil { _, rateIncrement, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) // if the timespan duration is larger than the rate increment make sure it is a multiple of it - if rateIncrement != time.Second && rateIncrement < ts.GetDuration() { + if rateIncrement < ts.GetDuration() { rateIncrement = utils.RoundTo(rateIncrement, ts.GetDuration()) } if rateIncrement > ts.GetDuration() { + initialDuration := ts.GetDuration() ts.TimeEnd = ts.TimeStart.Add(rateIncrement) - ts.CallDuration = ts.CallDuration + rateIncrement + ts.CallDuration = ts.CallDuration + (rateIncrement - initialDuration) // overlap the rest of the timespans i += 1 @@ -272,7 +273,6 @@ func (cd *CallDescriptor) roundTimeSpansToIncrement(timespans []*TimeSpan) []*Ti // remove overlapped for _, ts := range timespans { if !ts.overlapped { - ts.createRatedSecondSlice() newTimespans = append(newTimespans, ts) } } @@ -299,6 +299,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { } cost += ts.getCost() } + // global rounding cost = utils.Round(cost, roundingDecimals, roundingMethod) cc := &CallCost{ Direction: cd.Direction, @@ -438,7 +439,7 @@ The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) DebitSeconds() (err error) { if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { defer storageGetter.SetUserBalance(userBalance) - return userBalance.debitMinutesBalance(cd.Amount, cd.Destination, true) + return userBalance.debitCreditBalance(cd.CreateCallCost(), true) } return err } @@ -468,3 +469,15 @@ func (cd *CallDescriptor) FlushCache() (err error) { return nil } + +// Creates a CallCost structure copying related data from CallDescriptor +func (cd *CallDescriptor) CreateCallCost() *CallCost { + return &CallCost{ + Direction: cd.Direction, + TOR: cd.TOR, + Tenant: cd.Tenant, + Subject: cd.Subject, + Account: cd.Account, + Destination: cd.Destination, + } +} diff --git a/engine/timespans.go b/engine/timespans.go index 65d64138b..f31054517 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,12 +33,14 @@ type TimeSpan struct { RateInterval *RateInterval CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one - ratedSeconds []*rated_second + Increments []*Increment } -type rated_second struct { - index int - rate float64 +type Increment struct { + Duration time.Duration + Cost float64 + BalanceId string + BalanceType string } // Returns the duration of the timespan @@ -78,23 +80,17 @@ func (ts *TimeSpan) SetRateInterval(i *RateInterval) { } } -func (ts *TimeSpan) createRatedSecondSlice() { +func (ts *TimeSpan) createIncrementsSlice() { if ts.RateInterval == nil { return } - // create rated seconds series - rate, _, rate_unit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) - secondCost := rate / rate_unit.Seconds() + // create rated units series + rate, rateIncrement, rateUnit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) + incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds() totalCost := 0.0 - for s := 0; s < int(ts.GetDuration().Seconds()); s++ { - ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{s, secondCost}) - totalCost += secondCost - } - cCost := ts.getCost() - // here there might be some subsecond duration left - if totalCost < cCost { - // add one extra second with the fractional cost - ts.ratedSeconds = append(ts.ratedSeconds, &rated_second{len(ts.ratedSeconds), utils.Round(cCost-totalCost, ts.RateInterval.RoundingDecimals, ts.RateInterval.RoundingMethod)}) + for s := 0; s < int(ts.GetDuration()/rateIncrement); s++ { + ts.Increments = append(ts.Increments, &Increment{Duration: rateIncrement, Cost: incrementCost}) + totalCost += incrementCost } } @@ -168,9 +164,17 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { return } -/* -Splits the given timespan on activation period's activation time. -*/ +// Split the interval at the given increment start +func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan { + timeStart := ts.GetTimeStartForIncrement(index, increment) + newTs := &TimeSpan{TimeStart: timeStart, TimeEnd: ts.TimeEnd} + newTs.CallDuration = ts.CallDuration + ts.TimeEnd = timeStart + ts.SetNewCallDuration(newTs) + return newTs +} + +// Splits the given timespan on activation period's activation time. func (ts *TimeSpan) SplitByRatingPlan(ap *RatingPlan) (newTs *TimeSpan) { if !ts.Contains(ap.ActivationTime) { return nil @@ -203,3 +207,19 @@ func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) { } ts.CallDuration = d } + +// returns a time for the specified second in the time span +func (ts *TimeSpan) GetTimeStartForIncrement(index int, increment *Increment) time.Time { + return ts.TimeStart.Add(time.Duration(int64(index) * increment.Duration.Nanoseconds())) +} + +func (ts *TimeSpan) RoundToDuration(duration time.Duration) { + if duration < ts.GetDuration() { + duration = utils.RoundTo(duration, ts.GetDuration()) + } + if duration > ts.GetDuration() { + initialDuration := ts.GetDuration() + ts.TimeEnd = ts.TimeStart.Add(duration) + ts.CallDuration = ts.CallDuration + (duration - initialDuration) + } +} diff --git a/engine/timespans_test.go b/engine/timespans_test.go index 01afd4650..fbeaab1d2 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -19,7 +19,7 @@ along with this program. If not, see package engine import ( - "github.com/cgrates/cgrates/utils" + //"github.com/cgrates/cgrates/utils" "testing" "time" ) @@ -450,6 +450,7 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } +/* func TestTimespanExpandingCallDuration(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -471,7 +472,7 @@ func TestTimespanExpandingCallDuration(t *testing.T) { t.Error("Error setting call duration: ", timespans[0]) } } - +*/ func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -616,15 +617,16 @@ func TestTimespanCreateSecondsSlice(t *testing.T) { &Rate{Value: 2.0}, }}, } - ts.createRatedSecondSlice() - if len(ts.ratedSeconds) != 30 { - t.Error("Error creating second slice: ", ts.ratedSeconds) + ts.createIncrementsSlice() + if len(ts.Increments) != 30 { + t.Error("Error creating second slice: ", ts.Increments) } - if ts.ratedSeconds[0].rate != 2.0 { - t.Error("Wrong second slice: ", ts.ratedSeconds[0]) + if ts.Increments[0].Cost != 2.0 { + t.Error("Wrong second slice: ", ts.Increments[0]) } } +/* func TestTimespanCreateSecondsFract(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), @@ -637,12 +639,28 @@ func TestTimespanCreateSecondsFract(t *testing.T) { }, }, } - ts.createRatedSecondSlice() - if len(ts.ratedSeconds) != 31 { - t.Error("Error creating second slice: ", ts.ratedSeconds) + ts.createIncrementsSlice() + if len(ts.Increments) != 31 { + t.Error("Error creating second slice: ", ts.Increments) } - t.Log(ts.getCost()) - if ts.ratedSeconds[30].rate != 0.2 { - t.Error("Wrong second slice: ", ts.ratedSeconds[30]) + if len(ts.Increments) < 31 || ts.Increments[30].Cost != 0.2 { + t.Error("Wrong second slice: ", ts.Increments) } } + +func TestTimespanSplitByIncrement(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 30, 30, 0, time.UTC), + CallDuration: 50 * time.Second, + } + i := &Increment{Duration: time.Second} + newTs := ts.SplitByIncrement(5, i) + if ts.GetDuration() != 5*time.Second || newTs.GetDuration() != 25*time.Second { + t.Error("Error spliting by second: ", ts.GetDuration(), newTs.GetDuration()) + } + if ts.CallDuration != 25*time.Second || newTs.CallDuration != 50*time.Second { + t.Error("Error spliting by second at setting call duration: ", ts.GetDuration(), newTs.GetDuration()) + } +} +*/ diff --git a/engine/userbalance.go b/engine/userbalance.go index 81f254cf2..8616db284 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -20,6 +20,7 @@ package engine import ( "errors" + "fmt" "github.com/cgrates/cgrates/utils" "strings" ) @@ -44,6 +45,9 @@ const ( TRIGGER_MAX_COUNTER = "*max_counter" TRIGGER_MIN_BALANCE = "*min_balance" TRIGGER_MAX_BALANCE = "*max_balance" + // minute subjects + ZEROSECOND = "*zerosecond" + ZEROMINUTE = "*zerominute" ) var ( @@ -66,9 +70,7 @@ type UserBalance struct { UserIds []string // group info about users } -/* -Returns user's available minutes for the specified destination -*/ +// Returns user's available minutes for the specified destination func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, balances BalanceChain) { credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() if len(ub.BalanceMap[MINUTES+OUTBOUND]) == 0 { @@ -132,54 +134,95 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { return nil //ub.BalanceMap[id].GetTotalValue() } -/* -Debits the received amount of seconds from user's minute buckets. -All the appropriate buckets will be debited until all amount of minutes is consumed. -If the amount is bigger than the sum of all seconds in the minute buckets than nothing will be -debited and an error will be returned. -*/ -func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count bool) error { - if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: amount, DestinationId: prefix}}) - } - avaliableNbSeconds, _, bucketList := ub.getSecondsForPrefix(prefix) - if avaliableNbSeconds < amount { - return AMOUNT_TOO_BIG - } - var credit BalanceChain - if bc, exists := ub.BalanceMap[CREDIT+OUTBOUND]; exists { - credit = bc.Clone() - } - for _, mb := range bucketList { - if mb.Value < amount { - if mb.SpecialPrice > 0 { // debit the money if the bucket has price - credit.Debit(mb.Value * mb.SpecialPrice) - } - } else { - if mb.SpecialPrice > 0 { // debit the money if the bucket has price - credit.Debit(amount * mb.SpecialPrice) - } - break +func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) BalanceChain { + var usefulBalances BalanceChain + for _, b := range balances { + if b.IsExpired() { + continue } - if ub.Type == UB_TYPE_PREPAID && credit.GetTotalValue() < 0 { - break + if b.DestinationId != "" { + precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, prefix) + if err != nil { + continue + } + if precision > 0 { + b.precision = precision + if b.Value > 0 { + balances = append(balances, b) + } + } } } - // need to check again because there are two break above - if ub.Type == UB_TYPE_PREPAID && credit.GetTotalValue() < 0 { - return AMOUNT_TOO_BIG - } - ub.BalanceMap[CREDIT+OUTBOUND] = credit // credit is > 0 + // resort by precision + usefulBalances.Sort() + return usefulBalances +} - for _, mb := range bucketList { - if mb.Value < amount { - amount -= mb.Value - mb.Value = 0 - } else { - mb.Value -= amount - break +/* +This method is the core of userbalance debiting: don't panic just follow the branches +*/ +func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { + // debit minutes first + minuteBalances := ub.BalanceMap[MINUTES+cc.Direction] + + usefulBalances := ub.getBalanceForPrefix(cc.Destination, minuteBalances) + + for _, ts := range cc.Timespans { + ts.createIncrementsSlice() + for incrementIndex, increment := range ts.Increments { + for _, b := range usefulBalances { + if b.Value == 0 { + continue + } + + // check standard subject tags + if b.RateSubject == ZEROSECOND || b.RateSubject == "" { + if b.Value >= increment.Duration.Seconds() { + b.Value -= increment.Duration.Seconds() + increment.BalanceId = b.Id + break + } + } + if b.RateSubject == ZEROMINUTE { + if b.Value >= 60 { + // TODO: round to minute (consume the rest of the timespans) + nts := ts.SplitByIncrement(incrementIndex, increment) + nts.RoundToDuration(increment.Duration) + + b.Value -= 60 + increment.BalanceId = b.Id + break + } + } + // nts.SplitByIncrement() + // get the new rate + cd := cc.CreateCallDescriptor() + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd + cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration + newCC, err := cd.GetCost() + if err != nil { + Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) + continue + } + //debit new callcost + for _, nts := range newCC.Timespans { + for _, nIncrement := range nts.Increments { + // debit minutes and money + _ = nIncrement + } + } + } + if increment.BalanceId == "" { + // no balance was attached to this increment: cut the rest of increments/timespans + + } } } + + if count { + ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: cc.Cost + cc.ConnectFee, DestinationId: cc.Destination}}) + } return nil } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 33e4a87a0..b88b6381b 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -186,11 +186,12 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } } +/* func TestDebitMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(6, "0723", false) + err := rifsBalance.debitCreditBalance(6, "0723", false) if b2.Value != 94 || err != nil { t.Log(err) t.Errorf("Expected %v was %v", 94, b2.Value) @@ -201,7 +202,7 @@ func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(105, "0723", false) + err := rifsBalance.debitCreditBalance(105, "0723", false) if b2.Value != 0 || b1.Value != 5 || err != nil { t.Log(err) t.Errorf("Expected %v was %v", 0, b2.Value) @@ -212,7 +213,7 @@ func TestDebitAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(110, "0723", false) + err := rifsBalance.debitCreditBalance(110, "0723", false) if b2.Value != 0 || b1.Value != 0 || err != nil { t.Errorf("Expected %v was %v", 0, b2.Value) } @@ -222,7 +223,7 @@ func TestDebitMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(115, "0723", false) + err := rifsBalance.debitCreditBalance(115, "0723", false) if b2.Value != 100 || b1.Value != 10 || err == nil { t.Errorf("Expected %v was %v", 1000, b2.Value) } @@ -232,7 +233,7 @@ func TestDebitSpecialPriceMinuteBalance0(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(5, "0723", false) + err := rifsBalance.debitCreditBalance(5, "0723", false) if b2.Value != 95 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } @@ -242,7 +243,7 @@ func TestDebitSpecialPriceAllMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(21, "0723", false) + err := rifsBalance.debitCreditBalance(21, "0723", false) if b2.Value != 79 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } @@ -252,7 +253,7 @@ func TestDebitSpecialPriceMoreMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(25, "0723", false) + err := rifsBalance.debitCreditBalance(25, "0723", false) if b2.Value != 75 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { t.Log(b2.Value) t.Log(b1.Value) @@ -265,7 +266,7 @@ func TestDebitSpecialPriceMoreMinuteBalancePrepay(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(25, "0723", false) + err := rifsBalance.debitCreditBalance(25, "0723", false) expected := 21.0 if b2.Value != 100 || b1.Value != 10 || err != AMOUNT_TOO_BIG || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != expected { t.Log(b2.Value) @@ -279,7 +280,7 @@ func TestDebitSpecialPriceNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 1.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(-15, "0723", false) + err := rifsBalance.debitCreditBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) @@ -290,13 +291,13 @@ func TestDebitNegativeMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - err := rifsBalance.debitMinutesBalance(-15, "0723", false) + err := rifsBalance.debitCreditBalance(-15, "0723", false) if b2.Value != 115 || b1.Value != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { t.Log(b1, b2, err) t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } - +*/ func TestDebitSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} diff --git a/hard_update_external_libs.py b/hard_update_external_libs.py new file mode 100755 index 000000000..680522c1d --- /dev/null +++ b/hard_update_external_libs.py @@ -0,0 +1,49 @@ +#! /usr/bin/env python + +import os +import os.path +from subprocess import call + +libs = ('github.com/fzzy/radix/redis', + 'code.google.com/p/goconf/conf', + 'github.com/bmizerany/pq', + 'github.com/vmihailenco/msgpack', + 'github.com/ugorji/go/codec', + 'labix.org/v2/mgo', + 'github.com/cgrates/fsock', + 'github.com/go-sql-driver/mysql', + 'github.com/garyburd/redigo/redis', + 'menteslibres.net/gosexy/redis', + 'github.com/howeyc/fsnotify', +) + +if __name__ == "__main__": + go_path = os.path.join(os.environ['GOPATH'], 'src') + for lib in libs: + app_dir = os.path.abspath(os.path.join(go_path,lib)) + + if os.path.islink(app_dir): continue + git_path = os.path.join(app_dir, '.git') + bzr_path = os.path.join(app_dir, '.bzr') + hg_path = os.path.join(app_dir, '.hg') + svn_path = os.path.join(app_dir, '.svn') + if os.path.lexists(svn_path): + print("Updating svn %s" % app_dir) + os.chdir(app_dir) + call(['svn', 'update']) + elif os.path.lexists(git_path): + print("Updating git %s" % app_dir) + os.chdir(app_dir) + call(['git', 'checkout', 'master']) + call(['git', 'pull']) + elif os.path.lexists(bzr_path): + print("Updating bzr %s" % app_dir) + os.chdir(app_dir) + call(['bzr', 'pull']) + elif os.path.lexists(hg_path): + print("Updating hg %s" % app_dir) + os.chdir(app_dir) + call(['hg', 'pull', '-uv']) + else: + continue + call(['go', 'install']) diff --git a/utils/utils_test.go b/utils/utils_test.go index af65bb6a7..d642bac42 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -202,6 +202,11 @@ func TestRound(t *testing.T) { if result != expected { t.Errorf("Error rounding to minute1: expected %v was %v", expected, result) } + result = RoundTo(time.Second, 1*time.Second+500*time.Millisecond) + expected = 2 * time.Second + if result != expected { + t.Errorf("Error rounding to minute1: expected %v was %v", expected, result) + } result = RoundTo(minute, 1*time.Second) expected = minute if result != expected { From 17b982bdeb94ead27e06497db51bd73628860fcf Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 24 Sep 2013 18:50:44 +0300 Subject: [PATCH 24/39] new debit shaping up --- engine/timespans.go | 2 + engine/userbalance.go | 147 ++++++++++++++--- engine/userbalance_test.go | 328 +++++++++++++++++++++++++++++++++++++ 3 files changed, 453 insertions(+), 24 deletions(-) diff --git a/engine/timespans.go b/engine/timespans.go index f31054517..c59f391d3 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -84,6 +84,7 @@ func (ts *TimeSpan) createIncrementsSlice() { if ts.RateInterval == nil { return } + ts.Increments = make([]*Increment, 0) // create rated units series rate, rateIncrement, rateUnit := ts.RateInterval.GetRateParameters(ts.GetGroupStart()) incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds() @@ -170,6 +171,7 @@ func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan newTs := &TimeSpan{TimeStart: timeStart, TimeEnd: ts.TimeEnd} newTs.CallDuration = ts.CallDuration ts.TimeEnd = timeStart + ts.Increments = ts.Increments[0:index] ts.SetNewCallDuration(newTs) return newTs } diff --git a/engine/userbalance.go b/engine/userbalance.go index 8616db284..1826c5b29 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -23,6 +23,7 @@ import ( "fmt" "github.com/cgrates/cgrates/utils" "strings" + "time" ) const ( @@ -137,7 +138,7 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) BalanceChain { var usefulBalances BalanceChain for _, b := range balances { - if b.IsExpired() { + if b.IsExpired() || (ub.Type == UB_TYPE_PREPAID && b.Value <= 0) { continue } if b.DestinationId != "" { @@ -147,10 +148,10 @@ func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) } if precision > 0 { b.precision = precision - if b.Value > 0 { - balances = append(balances, b) - } + usefulBalances = append(usefulBalances, b) } + } else { + usefulBalances = append(usefulBalances, b) } } // resort by precision @@ -162,39 +163,109 @@ func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) This method is the core of userbalance debiting: don't panic just follow the branches */ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { - // debit minutes first minuteBalances := ub.BalanceMap[MINUTES+cc.Direction] + moneyBalances := ub.BalanceMap[CREDIT+cc.Direction] + usefulMinuteBalances := ub.getBalanceForPrefix(cc.Destination, minuteBalances) + usefulMoneyBalances := ub.getBalanceForPrefix(cc.Destination, moneyBalances) + // debit connect fee + if cc.ConnectFee > 0 { + amount := cc.ConnectFee + paid := false + for _, b := range usefulMoneyBalances { + if b.Value >= amount { + b.Value -= amount + // the conect fee is not refoundable! + if count { + ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) + } + paid = true + break + } + } + if !paid { + // there are no money for the connect fee; abort mission + cc.Timespans = make([]*TimeSpan, 0) + return nil + } + } + // debit minutes + for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ { + ts := cc.Timespans[tsIndex] - usefulBalances := ub.getBalanceForPrefix(cc.Destination, minuteBalances) - - for _, ts := range cc.Timespans { ts.createIncrementsSlice() for incrementIndex, increment := range ts.Increments { - for _, b := range usefulBalances { + paid := false + for _, b := range usefulMinuteBalances { if b.Value == 0 { continue } - // check standard subject tags if b.RateSubject == ZEROSECOND || b.RateSubject == "" { - if b.Value >= increment.Duration.Seconds() { - b.Value -= increment.Duration.Seconds() + amount := increment.Duration.Seconds() + if b.Value >= amount { + b.Value -= amount increment.BalanceId = b.Id + paid = true + if count { + ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) + } break } } if b.RateSubject == ZEROMINUTE { - if b.Value >= 60 { - // TODO: round to minute (consume the rest of the timespans) - nts := ts.SplitByIncrement(incrementIndex, increment) - nts.RoundToDuration(increment.Duration) + 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, increment) + } + newTs.RoundToDuration(time.Minute) + newTs.RateInterval = &RateInterval{ + Rates: RateGroups{ + &Rate{ + GroupIntervalStart: 0, + Value: 0, + RateIncrement: time.Minute, + RateUnit: time.Minute, + }, + }, + } + newTs.createIncrementsSlice() + // overlap the rest of the timespans + for i := tsIndex + 1; i < len(cc.Timespans); i++ { + if cc.Timespans[i].TimeEnd.Before(newTs.TimeEnd) || cc.Timespans[i].TimeEnd.Equal(newTs.TimeEnd) { + cc.Timespans[i].overlapped = true + } else if cc.Timespans[i].TimeStart.Before(newTs.TimeEnd) { + cc.Timespans[i].TimeStart = ts.TimeEnd + } + } + // 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 + } - b.Value -= 60 - increment.BalanceId = b.Id + var newTimespans []*TimeSpan + // remove overlapped + for _, ots := range cc.Timespans { + if !ots.overlapped { + newTimespans = append(newTimespans, ots) + } + } + cc.Timespans = newTimespans + b.Value -= amount + newTs.Increments[0].BalanceId = b.Id + paid = true + if count { + ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) + } break } } - // nts.SplitByIncrement() + // newTs.SplitByIncrement() // get the new rate cd := cc.CreateCallDescriptor() cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) @@ -213,16 +284,44 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } } } - if increment.BalanceId == "" { + if paid { + continue + } + // debit monetary + for _, b := range usefulMoneyBalances { + if b.Value == 0 { + continue + } + // check standard subject tags + if b.RateSubject == "" { + amount := increment.Cost + if b.Value >= amount { + b.Value -= amount + increment.BalanceId = b.Id + paid = true + if count { + ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) + } + break + } + } else { + // get the new rate + } + } + if !paid { // no balance was attached to this increment: cut the rest of increments/timespans - + ts.SplitByIncrement(incrementIndex, increment) + if len(ts.Increments) == 0 { + // if there are no increments left in the ts leav it out + cc.Timespans = cc.Timespans[:tsIndex] + } else { + cc.Timespans = cc.Timespans[:tsIndex+1] + } + return nil } } } - if count { - ub.countUnits(&Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: cc.Cost + cc.ConnectFee, DestinationId: cc.Destination}}) - } return nil } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index b88b6381b..97c6ae6cb 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -186,6 +186,334 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { } } +func TestDebitCreditZeroSecond(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { + t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[MINUTES+OUTBOUND][0]) + } +} + +func TestDebitCreditZeroMinute(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != time.Minute { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0]) + } +} +func TestDebitCreditZeroMixedMinute(t *testing.T) { + b1 := &Balance{Id: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Id: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + cc := &CallCost{ + 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, 20, 0, time.UTC), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1, b2}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "tests" || + cc.Timespans[1].Increments[0].BalanceId != "testm" { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0], cc.Timespans[1].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][1].Value != 0 || + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { + t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[MINUTES+OUTBOUND]) + } +} + +func TestDebitCreditNoCredit(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != time.Minute { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0]) + } + if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != time.Minute { + t.Error("Error truncating extra timespans: ", cc.Timespans) + } +} + +func TestDebitCreditHasCredit(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != time.Minute { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 30 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0], rifsBalance.BalanceMap[CREDIT+OUTBOUND][0]) + } + if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != time.Minute || cc.Timespans[1].GetDuration() != 20*time.Second { + t.Error("Error truncating extra timespans: ", cc.Timespans) + } +} + +func TestDebitCreditMoreTimespans(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != time.Minute { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 30 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0]) + } +} + +func TestDebitCreditMoreTimespansMixed(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Id: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1, b2}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != time.Minute { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 || + rifsBalance.BalanceMap[MINUTES+OUTBOUND][1].Value != 130 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][1], cc.Timespans[1]) + } +} + +func TestDebitCreditNoConectFeeCredit(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + cc := &CallCost{ + Direction: OUTBOUND, + Destination: "0723045326", + ConnectFee: 10.0, + 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), + CallDuration: 0, + RateInterval: &RateInterval{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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + + if len(cc.Timespans) != 0 || cc.GetTotalDuration() != 0 { + t.Error("Error cutting at no connect fee: ", cc.Timespans) + } +} + +func TestDebitCreditMoneyOnly(t *testing.T) { + cc := &CallCost{ + 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), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, 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), + CallDuration: 10 * time.Second, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "money", Value: 50}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + + if cc.Timespans[0].Increments[0].BalanceId != "money" || + cc.Timespans[0].Increments[0].Duration != 10*time.Second { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { + t.Error("Error extracting minutes from balance: ", + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0]) + } + if len(cc.Timespans) != 2 || + cc.Timespans[0].GetDuration() != 10*time.Second || + cc.Timespans[1].GetDuration() != 40*time.Second { + t.Error("Error truncating extra timespans: ", cc.Timespans[1].Increments[0]) + } +} + /* func TestDebitMinuteBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} From 6c35f6e3407878b52f59dbad94e03e9d34241cc1 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 25 Sep 2013 21:52:06 +0300 Subject: [PATCH 25/39] improved csv loading --- apier/v1/tpdestinationrates.go | 5 +- apier/v1/tpdestratetimings.go | 2 +- engine/loader_csv.go | 87 ++++--- engine/loader_csv_test.go | 402 ++++++++++++++------------------- engine/loader_db.go | 18 +- engine/loader_helpers.go | 67 ++++-- engine/ratingplan.go | 11 +- engine/ratingplan_test.go | 8 +- engine/ratingprofile.go | 11 +- engine/storage_interface.go | 2 +- engine/storage_sql.go | 17 +- engine/tpimporter_csv.go | 2 +- engine/userbalance.go | 1 + 13 files changed, 304 insertions(+), 329 deletions(-) diff --git a/apier/v1/tpdestinationrates.go b/apier/v1/tpdestinationrates.go index 9b4603457..4d3219a8a 100644 --- a/apier/v1/tpdestinationrates.go +++ b/apier/v1/tpdestinationrates.go @@ -39,7 +39,10 @@ func (self *ApierV1) SetTPDestinationRate(attrs utils.TPDestinationRate, reply * } drs := make([]*engine.DestinationRate, len(attrs.DestinationRates)) for idx, dr := range attrs.DestinationRates { - drs[idx] = &engine.DestinationRate{attrs.DestinationRateId, dr.DestinationId, dr.RateId, nil} + drs[idx] = &engine.DestinationRate{ + Tag: attrs.DestinationRateId, + DestinationsTag: dr.DestinationId, + RateTag: dr.RateId} } if err := self.StorDb.SetTPDestinationRates(attrs.TPid, map[string][]*engine.DestinationRate{attrs.DestinationRateId: drs}); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) diff --git a/apier/v1/tpdestratetimings.go b/apier/v1/tpdestratetimings.go index 64683c38d..91dee75ff 100644 --- a/apier/v1/tpdestratetimings.go +++ b/apier/v1/tpdestratetimings.go @@ -42,7 +42,7 @@ func (self *ApierV1) SetTPDestRateTiming(attrs utils.TPDestRateTiming, reply *st drts[idx] = &engine.DestinationRateTiming{Tag: attrs.DestRateTimingId, DestinationRatesTag: drt.DestRatesId, Weight: drt.Weight, - TimingsTag: drt.TimingId, + TimingTag: drt.TimingId, } } if err := self.StorDb.SetTPDestRateTimings(attrs.TPid, map[string][]*engine.DestinationRateTiming{attrs.DestRateTimingId: drts}); err != nil { diff --git a/engine/loader_csv.go b/engine/loader_csv.go index dd7f0c4c5..b38bd96a4 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -30,19 +30,19 @@ import ( ) type CSVReader struct { - sep rune - storage DataStorage - readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) - actions map[string][]*Action - actionsTimings map[string][]*ActionTiming - actionsTriggers map[string][]*ActionTrigger - accountActions []*UserBalance - destinations []*Destination - timings map[string]*Timing - rates map[string]*LoadRate - destinationRates map[string][]*DestinationRate - ratingPlans map[string]*RatingPlan - ratingProfiles map[string]*RatingProfile + sep rune + storage DataStorage + readerFunc func(string, rune, int) (*csv.Reader, *os.File, error) + actions map[string][]*Action + actionsTimings map[string][]*ActionTiming + actionsTriggers map[string][]*ActionTrigger + accountActions []*UserBalance + destinations []*Destination + timings map[string]*Timing + rates map[string][]*LoadRate + destinationRates map[string][]*DestinationRate + destinationRateTimings map[string][]*DestinationRateTiming + ratingProfiles map[string]*RatingProfile // file names destinationsFn, ratesFn, destinationratesFn, timingsFn, destinationratetimingsFn, ratingprofilesFn, actionsFn, actiontimingsFn, actiontriggersFn, accountactionsFn string @@ -55,10 +55,10 @@ func NewFileCSVReader(storage DataStorage, sep rune, destinationsFn, timingsFn, c.actions = make(map[string][]*Action) c.actionsTimings = make(map[string][]*ActionTiming) c.actionsTriggers = make(map[string][]*ActionTrigger) - c.rates = make(map[string]*LoadRate) + c.rates = make(map[string][]*LoadRate) c.destinationRates = make(map[string][]*DestinationRate) c.timings = make(map[string]*Timing) - c.ratingPlans = make(map[string]*RatingPlan) + c.destinationRateTimings = make(map[string][]*DestinationRateTiming) c.ratingProfiles = make(map[string]*RatingProfile) c.readerFunc = openFileCSVReader c.destinationsFn, c.timingsFn, c.ratesFn, c.destinationratesFn, c.destinationratetimingsFn, c.ratingprofilesFn, @@ -228,7 +228,14 @@ func (csvr *CSVReader) LoadRates() (err error) { if err != nil { return err } - csvr.rates[tag] = r + // same tag only to create rate groups + existingRates, exists := csvr.rates[tag] + if exists { + if err := existingRates[len(existingRates)-1].ValidNextGroup(r); err != nil { + return err + } + } + csvr.rates[tag] = append(csvr.rates[tag], r) } return } @@ -249,11 +256,20 @@ func (csvr *CSVReader) LoadDestinationRates() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not get rates for tag %v", record[2])) } - //ToDo: Not checking presence of destinations? + destinationExists := false + for _, d := range csvr.destinations { + if d.Id == record[1] { + destinationExists = true + break + } + } + if !destinationExists { + return errors.New(fmt.Sprintf("Could not get destination for tag %v", record[1])) + } dr := &DestinationRate{ Tag: tag, DestinationsTag: record[1], - Rate: r, + rates: r, } csvr.destinationRates[tag] = append(csvr.destinationRates[tag], dr) @@ -277,18 +293,12 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { if !exists { return errors.New(fmt.Sprintf("Could not get timing for tag %v", record[2])) } - - rt := NewDestinationRateTiming(record[1], t, record[3]) drs, exists := csvr.destinationRates[record[1]] if !exists { return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", record[1])) } - for _, dr := range drs { - if _, exists := csvr.ratingPlans[tag]; !exists { - csvr.ratingPlans[tag] = &RatingPlan{} - } - csvr.ratingPlans[tag].AddRateInterval(rt.GetRateInterval(dr)) - } + drt := NewDestinationRateTiming(drs, t, record[3]) + csvr.destinationRateTimings[tag] = append(csvr.destinationRateTimings[tag], drt) } return } @@ -315,19 +325,22 @@ func (csvr *CSVReader) LoadRatingProfiles() (err error) { rp = &RatingProfile{Id: key} csvr.ratingProfiles[key] = rp } - for _, d := range csvr.destinations { - ap, exists := csvr.ratingPlans[record[5]] - if !exists { - return errors.New(fmt.Sprintf("Could not load ratinTiming for tag: %v", record[5])) - } - newAP := &RatingPlan{ActivationTime: at} - //copy(newAP.Intervals, ap.Intervals) - newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) - rp.AddRatingPlanIfNotPresent(d.Id, newAP) - if fallbacksubject != "" { - rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject) + drts, exists := csvr.destinationRateTimings[record[5]] + if !exists { + return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", record[5])) + } + + for _, drt := range drts { + plan := &RatingPlan{ActivationTime: at} + for _, dr := range drt.destinationRates { + plan.AddRateInterval(drt.GetRateInterval(dr)) + rp.AddRatingPlanIfNotPresent(dr.DestinationsTag, plan) } } + + if fallbacksubject != "" { + rp.FallbackKey = fmt.Sprintf("%s:%s:%s:%s", direction, tenant, tor, fallbacksubject) + } } return } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 5c0ff31bf..9fa64ae8a 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -209,7 +209,7 @@ func TestLoadRates(t *testing.T) { if len(csvr.rates) != 5 { t.Error("Failed to load rates: ", csvr.rates) } - rate := csvr.rates["R1"] + rate := csvr.rates["R1"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R1", ConnectFee: 0, @@ -221,9 +221,9 @@ func TestLoadRates(t *testing.T) { RoundingDecimals: 2, Weight: 10, }) { - t.Error("Error loading rate: ", csvr.rates) + t.Error("Error loading rate: ", csvr.rates["R1"][0]) } - rate = csvr.rates["R2"] + rate = csvr.rates["R2"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R2", ConnectFee: 0, @@ -237,7 +237,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R3"] + rate = csvr.rates["R3"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R3", ConnectFee: 0, @@ -251,7 +251,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R4"] + rate = csvr.rates["R4"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R4", ConnectFee: 1, @@ -265,7 +265,7 @@ func TestLoadRates(t *testing.T) { }) { t.Error("Error loading rate: ", csvr.rates) } - rate = csvr.rates["R5"] + rate = csvr.rates["R5"][0] if !reflect.DeepEqual(rate, &LoadRate{ Tag: "R5", ConnectFee: 0, @@ -284,24 +284,24 @@ func TestLoadRates(t *testing.T) { func TestLoadDestinationRates(t *testing.T) { if len(csvr.destinationRates) != 5 { - t.Error("Failed to load rates: ", csvr.rates) + t.Error("Failed to load destinationrates: ", csvr.destinationRates) } drs := csvr.destinationRates["RT_STANDARD"] if !reflect.DeepEqual(drs, []*DestinationRate{ &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY", - Rate: csvr.rates["R1"], + rates: csvr.rates["R1"], }, &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY_O2", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, &DestinationRate{ Tag: "RT_STANDARD", DestinationsTag: "GERMANY_PREMIUM", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -311,7 +311,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "RT_DEFAULT", DestinationsTag: "ALL", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -321,12 +321,12 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "RT_STD_WEEKEND", DestinationsTag: "GERMANY", - Rate: csvr.rates["R2"], + rates: csvr.rates["R2"], }, &DestinationRate{ Tag: "RT_STD_WEEKEND", DestinationsTag: "GERMANY_O2", - Rate: csvr.rates["R3"], + rates: csvr.rates["R3"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -336,7 +336,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "P1", DestinationsTag: "NAT", - Rate: csvr.rates["R4"], + rates: csvr.rates["R4"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -346,7 +346,7 @@ func TestLoadDestinationRates(t *testing.T) { &DestinationRate{ Tag: "P2", DestinationsTag: "NAT", - Rate: csvr.rates["R5"], + rates: csvr.rates["R5"], }, }) { t.Error("Error loading destination rate: ", drs) @@ -354,228 +354,171 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadDestinationRateTimings(t *testing.T) { - if len(csvr.ratingPlans) != 4 { - t.Error("Failed to load rate timings: ", csvr.ratingPlans) + if len(csvr.destinationRateTimings) != 4 { + t.Error("Failed to load rate timings: ", csvr.destinationRateTimings) } - rplan := csvr.ratingPlans["STANDARD"] - expected := &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.2, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, + rplan := csvr.destinationRateTimings["STANDARD"] + expected := []*DestinationRateTiming{ + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R1", + ConnectFee: 0, + Price: 0.2, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STANDARD", + DestinationsTag: "GERMANY_PREMIUM", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "18:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, + Weight: 10, + timing: &Timing{ + Id: "WORKDAYS_00", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "00:00:00"}, + }, + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R3", + ConnectFee: 0, + Price: 0.05, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, + Weight: 10, + timing: &Timing{ + Id: "WORKDAYS_18", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{1, 2, 3, 4, 5}, + StartTime: "18:00:00", + }, + }, + &DestinationRateTiming{ + destinationRates: []*DestinationRate{ + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R2", + ConnectFee: 0, + Price: 0.1, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, }, }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, + &DestinationRate{ + Tag: "RT_STD_WEEKEND", + DestinationsTag: "GERMANY_O2", + rates: []*LoadRate{ + &LoadRate{ + Tag: "R3", + ConnectFee: 0, + Price: 0.05, + RateUnit: time.Minute, + RateIncrement: time.Second, + GroupIntervalStart: 0, + RoundingMethod: "*middle", + RoundingDecimals: 2, + Weight: 10, + }, + }, + }, + }, + Weight: 10, + timing: &Timing{ + Id: "WEEKENDS", + Years: Years{}, + Months: Months{}, + MonthDays: MonthDays{}, + WeekDays: WeekDays{6, 0}, + StartTime: "00:00:00", }, }, } if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["STANDARD"]) - } - rplan = csvr.ratingPlans["PREMIUM"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - &Rate{ - GroupIntervalStart: 0, - Value: 0.05, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["PREMIUM"]) - } - rplan = csvr.ratingPlans["DEFAULT"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.1, - RateIncrement: time.Second, - RateUnit: time.Minute, - }, - }, - RoundingMethod: utils.ROUNDING_MIDDLE, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["DEFAULT"]) - } - rplan = csvr.ratingPlans["EVENING"] - expected = &RatingPlan{ - ActivationTime: time.Time{}, - RateIntervals: RateIntervalList{ - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 1, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 1, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_UP, - RoundingDecimals: 2, - }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{1, 2, 3, 4, 5}, - StartTime: "18:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.5, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_DOWN, - RoundingDecimals: 2, - }, - &RateInterval{ - Years: Years{}, - Months: Months{}, - MonthDays: MonthDays{}, - WeekDays: WeekDays{6, 0}, - StartTime: "00:00:00", - EndTime: "", - Weight: 10, - ConnectFee: 0, - Rates: RateGroups{ - &Rate{ - GroupIntervalStart: 0, - Value: 0.5, - RateIncrement: time.Second, - RateUnit: time.Second, - }, - }, - RoundingMethod: utils.ROUNDING_DOWN, - RoundingDecimals: 2, - }, - }, - } - if !reflect.DeepEqual(rplan, expected) { - t.Errorf("Error loading rating plan: %#v", csvr.ratingPlans["EVENING"]) + t.Errorf("Error loading destination rate timing: %#v", rplan) } } @@ -586,22 +529,13 @@ func TestLoadRatingProfiles(t *testing.T) { rp := csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] expected := &RatingProfile{} if reflect.DeepEqual(rp, expected) { - t.Error("Error loading rating profile: ", csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"]) + t.Errorf("Error loading rating profile: %#v", rp) } } /* CUSTOMER_1,0,*out,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb CUSTOMER_1,0,*out,rif:from:tm,2012-02-28T00:00:00Z,STANDARD,danb -CUSTOMER_2,0,*out,danb:87.139.12.167,2012-01-01T00:00:00Z,STANDARD,danb -CUSTOMER_1,0,*out,danb,2012-01-01T00:00:00Z,PREMIUM, -vdf,0,*out,rif,2012-01-01T00:00:00Z,EVENING, -vdf,0,*out,rif,2012-02-28T00:00:00Z,EVENING, -vdf,0,*out,minu,2012-01-01T00:00:00Z,EVENING, -vdf,0,*out,*any,2012-02-28T00:00:00Z,EVENING, -vdf,0,*out,one,2012-02-28T00:00:00Z,STANDARD, -vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf -vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif */ func TestLoadActions(t *testing.T) { diff --git a/engine/loader_db.go b/engine/loader_db.go index 2141e7c46..fd996c84b 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -35,7 +35,7 @@ type DbReader struct { accountActions []*UserBalance destinations []*Destination timings map[string]*Timing - rates map[string]*LoadRate + rates map[string][]*LoadRate destinationRates map[string][]*DestinationRate activationPeriods map[string]*RatingPlan ratingProfiles map[string]*RatingProfile @@ -141,11 +141,11 @@ func (dbr *DbReader) LoadDestinationRates() (err error) { } for _, drs := range dbr.destinationRates { for _, dr := range drs { - rate, exists := dbr.rates[dr.RateTag] + rates, exists := dbr.rates[dr.RateTag] if !exists { return errors.New(fmt.Sprintf("Could not find rate for tag %v", dr.RateTag)) } - dr.Rate = rate + dr.rates = rates } } return nil @@ -157,9 +157,9 @@ func (dbr *DbReader) LoadDestinationRateTimings() error { return err } for _, rt := range rts { - t, exists := dbr.timings[rt.TimingsTag] + t, exists := dbr.timings[rt.TimingTag] if !exists { - return errors.New(fmt.Sprintf("Could not get timing for tag %v", rt.TimingsTag)) + return errors.New(fmt.Sprintf("Could not get timing for tag %v", rt.TimingTag)) } rt.timing = t drs, exists := dbr.destinationRates[rt.DestinationRatesTag] @@ -223,12 +223,12 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { } for _, destrateTiming := range drtm { Logger.Debug(fmt.Sprintf("Destination rate timing: %v", rpm)) - tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag) + tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingTag) Logger.Debug(fmt.Sprintf("Timing: %v", rpm)) if err != nil || len(tm) == 0 { - return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.TimingsTag, err) + return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.TimingTag, err) } - destrateTiming.timing = tm[destrateTiming.TimingsTag] + destrateTiming.timing = tm[destrateTiming.TimingTag] drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag) if err != nil || len(drm) == 0 { return fmt.Errorf("No Timings profile with id %s: %v", destrateTiming.DestinationRatesTag, err) @@ -240,7 +240,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { return fmt.Errorf("No Rates profile with id %s: %v", drate.RateTag, err) } Logger.Debug(fmt.Sprintf("Rate: %v", rpm)) - drate.Rate = rt[drate.RateTag] + drate.rates = rt[drate.RateTag] if _, exists := activationPeriods[destrateTiming.Tag]; !exists { activationPeriods[destrateTiming.Tag] = &RatingPlan{} } diff --git a/engine/loader_helpers.go b/engine/loader_helpers.go index 8d9291f88..e9b96e517 100644 --- a/engine/loader_helpers.go +++ b/engine/loader_helpers.go @@ -24,6 +24,7 @@ import ( "fmt" "github.com/cgrates/cgrates/utils" "log" + "math" "os" "path" "regexp" @@ -106,11 +107,24 @@ func NewLoadRate(tag, connectFee, price, ratedUnits, rateIncrements, groupInterv return } +func (present *LoadRate) ValidNextGroup(next *LoadRate) error { + if next.GroupIntervalStart <= present.GroupIntervalStart { + return errors.New(fmt.Sprintf("Next rate group interval start must be heigher than the last one: %#v", next)) + } + if math.Mod(next.GroupIntervalStart.Seconds(), present.RateIncrement.Seconds()) != 0 { + return errors.New(fmt.Sprintf("GroupIntervalStart of %#v must be a multiple of RateIncrement of %#v", next, present)) + } + if present.RoundingMethod != next.RoundingMethod || present.RoundingDecimals != next.RoundingDecimals { + return errors.New(fmt.Sprintf("Rounding stuff must be equal for sam rate tag: %#v, %#v", present, next)) + } + return nil +} + type DestinationRate struct { Tag string DestinationsTag string - RateTag string - Rate *LoadRate + RateTag string // intermediary used when loading from db + rates []*LoadRate } type Timing struct { @@ -135,43 +149,46 @@ func NewTiming(timingInfo ...string) (rt *Timing) { type DestinationRateTiming struct { Tag string - DestinationRatesTag string + DestinationRatesTag string // intermediary used when loading from db + destinationRates []*DestinationRate Weight float64 - TimingsTag string // intermediary used when loading from db + TimingTag string // intermediary used when loading from db timing *Timing } -func NewDestinationRateTiming(destinationRatesTag string, timing *Timing, weight string) (rt *DestinationRateTiming) { +func NewDestinationRateTiming(destinationRates []*DestinationRate, timing *Timing, weight string) (drt *DestinationRateTiming) { w, err := strconv.ParseFloat(weight, 64) if err != nil { log.Printf("Error parsing weight unit from: %v", weight) return } - rt = &DestinationRateTiming{ - DestinationRatesTag: destinationRatesTag, - Weight: w, - timing: timing, + drt = &DestinationRateTiming{ + destinationRates: destinationRates, + timing: timing, + Weight: w, } return } -func (rt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { +func (drt *DestinationRateTiming) GetRateInterval(dr *DestinationRate) (i *RateInterval) { i = &RateInterval{ - Years: rt.timing.Years, - Months: rt.timing.Months, - MonthDays: rt.timing.MonthDays, - WeekDays: rt.timing.WeekDays, - StartTime: rt.timing.StartTime, - Weight: rt.Weight, - ConnectFee: dr.Rate.ConnectFee, - RoundingMethod: dr.Rate.RoundingMethod, - RoundingDecimals: dr.Rate.RoundingDecimals, - Rates: RateGroups{&Rate{ - GroupIntervalStart: dr.Rate.GroupIntervalStart, - Value: dr.Rate.Price, - RateIncrement: dr.Rate.RateIncrement, - RateUnit: dr.Rate.RateUnit, - }}, + Years: drt.timing.Years, + Months: drt.timing.Months, + MonthDays: drt.timing.MonthDays, + WeekDays: drt.timing.WeekDays, + StartTime: drt.timing.StartTime, + Weight: drt.Weight, + ConnectFee: dr.rates[0].ConnectFee, + RoundingMethod: dr.rates[0].RoundingMethod, + RoundingDecimals: dr.rates[0].RoundingDecimals, + } + for _, rl := range dr.rates { + i.Rates = append(i.Rates, &Rate{ + GroupIntervalStart: rl.GroupIntervalStart, + Value: rl.Price, + RateIncrement: rl.RateIncrement, + RateUnit: rl.RateUnit, + }) } return } diff --git a/engine/ratingplan.go b/engine/ratingplan.go index 5e14bf74f..0b6211630 100644 --- a/engine/ratingplan.go +++ b/engine/ratingplan.go @@ -40,18 +40,17 @@ type xCachedRatingPlans struct { /* Adds one ore more intervals to the internal interval list only if it is not allready in the list. */ -func (ap *RatingPlan) AddRateInterval(is ...*RateInterval) { - for _, i := range is { +func (ap *RatingPlan) AddRateInterval(ris ...*RateInterval) { + for _, ri := range ris { found := false - for _, ei := range ap.RateIntervals { - if i.Equal(ei) { - (&ei.Rates).AddRate(i.Rates...) + for _, eri := range ap.RateIntervals { + if ri.Equal(eri) { found = true break } } if !found { - ap.RateIntervals = append(ap.RateIntervals, i) + ap.RateIntervals = append(ap.RateIntervals, ri) } } } diff --git a/engine/ratingplan_test.go b/engine/ratingplan_test.go index ef007ba93..e22413b77 100644 --- a/engine/ratingplan_test.go +++ b/engine/ratingplan_test.go @@ -79,8 +79,8 @@ func TestFallbackDirect(t *testing.T) { func TestFallbackMultiple(t *testing.T) { cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"} cd.LoadRatingPlans() - if len(cd.RatingPlans) != 1 { - t.Error("Error restoring activation periods: ", len(cd.RatingPlans)) + if len(cd.RatingPlans) != 2 { + t.Errorf("Error restoring rating plans: %#v", cd.RatingPlans) } } @@ -161,8 +161,8 @@ func TestApAddRateIntervalGroups(t *testing.T) { if len(ap.RateIntervals) != 1 { t.Error("Wronfully appended interval ;)") } - if len(ap.RateIntervals[0].Rates) != 2 { - t.Error("Group prices not formed: ", ap.RateIntervals[0].Rates) + if len(ap.RateIntervals[0].Rates) != 1 { + t.Errorf("Group prices not formed: %#v", ap.RateIntervals[0].Rates[0]) } } diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index 0c243de0f..a2185f316 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -31,20 +31,21 @@ type RatingProfile struct { } // Adds an activation period that applyes to current rating profile if not already present. -func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, aps ...*RatingPlan) { +func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, plans ...*RatingPlan) { if rp.DestinationMap == nil { rp.DestinationMap = make(map[string][]*RatingPlan, 1) } - for _, ap := range aps { + for _, plan := range plans { found := false - for _, eap := range rp.DestinationMap[destInfo] { - if ap.Equal(eap) { + for _, existingPlan := range rp.DestinationMap[destInfo] { + if plan.Equal(existingPlan) { + existingPlan.AddRateInterval(plan.RateIntervals...) found = true break } } if !found { - rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], ap) + rp.DestinationMap[destInfo] = append(rp.DestinationMap[destInfo], plan) } } } diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 0fe0b567a..2f91cdb8a 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -143,7 +143,7 @@ type LoadStorage interface { // loader functions GetTpDestinations(string, string) ([]*Destination, error) GetTpTimings(string, string) (map[string]*Timing, error) - GetTpRates(string, string) (map[string]*LoadRate, error) + GetTpRates(string, string) (map[string][]*LoadRate, error) GetTpDestinationRates(string, string) (map[string][]*DestinationRate, error) GetTpDestinationRateTimings(string, string) ([]*DestinationRateTiming, error) GetTpRatingProfiles(string, string) (map[string]*RatingProfile, error) diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 13d13f1a3..c11e43f46 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -363,7 +363,7 @@ func (self *SQLStorage) SetTPDestRateTimings(tpid string, drts map[string][]*Des qry += "," } qry += fmt.Sprintf("('%s','%s','%s','%s',%f)", - tpid, drtId, drt.DestinationRatesTag, drt.TimingsTag, drt.Weight) + tpid, drtId, drt.DestinationRatesTag, drt.TimingTag, drt.Weight) i++ } } @@ -921,8 +921,8 @@ func (self *SQLStorage) GetTpDestinations(tpid, tag string) ([]*Destination, err return dests, nil } -func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*LoadRate, error) { - rts := make(map[string]*LoadRate) +func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string][]*LoadRate, error) { + rts := make(map[string][]*LoadRate) q := fmt.Sprintf("SELECT tag, connect_fee, rate, rate_unit, rate_increment, group_interval_start, rounding_method, rounding_decimals, weight FROM %s WHERE tpid='%s' ", utils.TBL_TP_RATES, tpid) if tag != "" { q += fmt.Sprintf(" AND tag='%s'", tag) @@ -951,7 +951,14 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*LoadRate, erro RoundingDecimals: roundingDecimals, Weight: weight, } - rts[tag] = r + // same tag only to create rate groups + existingRates, exists := rts[tag] + if exists { + if err := existingRates[len(existingRates)-1].ValidNextGroup(r); err != nil { + return nil, err + } + } + rts[tag] = append(rts[tag], r) } return rts, nil } @@ -1027,7 +1034,7 @@ func (self *SQLStorage) GetTpDestinationRateTimings(tpid, tag string) ([]*Destin Tag: tag, DestinationRatesTag: destination_rates_tag, Weight: weight, - TimingsTag: timings_tag, + TimingTag: timings_tag, } rts = append(rts, rt) } diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 324c122ba..db8105390 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -208,7 +208,7 @@ func (self *TPCSVImporter) importDestRateTimings(fn string) error { drt := &DestinationRateTiming{Tag: record[0], DestinationRatesTag: record[1], Weight: weight, - TimingsTag: record[2], + TimingTag: record[2], } if err := self.StorDb.SetTPDestRateTimings(self.TPid, map[string][]*DestinationRateTiming{drt.Tag: []*DestinationRateTiming{drt}}); err != nil { if self.Verbose { diff --git a/engine/userbalance.go b/engine/userbalance.go index 1826c5b29..1c0e69fc9 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -287,6 +287,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { if paid { continue } + // TODO: Split if some increments were processed by minutes // debit monetary for _, b := range usefulMoneyBalances { if b.Value == 0 { From 94addca3e5f44e083cdbdf4cb72451587dde84a8 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 26 Sep 2013 13:08:03 +0300 Subject: [PATCH 26/39] implemented call url action it will send a POST request to the action ExtraParameters string url with the json encoded user balance as the body --- engine/action.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/engine/action.go b/engine/action.go index 9c4810da1..cad58c4d6 100644 --- a/engine/action.go +++ b/engine/action.go @@ -19,7 +19,10 @@ along with this program. If not, see package engine import ( + "bytes" + "encoding/json" "fmt" + "net/http" "sort" ) @@ -31,6 +34,7 @@ type Action struct { ActionType string BalanceId string Direction string + ExtraParameters string ExpirationString string Weight float64 Balance *Balance @@ -48,6 +52,7 @@ const ( DEBIT = "*debit" RESET_COUNTER = "*reset_counter" RESET_COUNTERS = "*reset_counters" + CALL_URL = "*call_url" UNLIMITED = "*unlimited" ) @@ -77,6 +82,8 @@ func getActionFunc(typ string) (actionTypeFunc, bool) { return resetCounterAction, true case RESET_COUNTERS: return resetCountersAction, true + case CALL_URL: + return callUrl, true } return nil, false } @@ -170,6 +177,15 @@ func genericReset(ub *UserBalance) { ub.resetActionTriggers(nil) } +func callUrl(ub *UserBalance, a *Action) error { + body, err := json.Marshal(ub) + if err != nil { + return err + } + _, err = http.Post(a.ExtraParameters, "application/json", bytes.NewBuffer(body)) + return err +} + // Structure to store actions according to weight type Actions []*Action From eec68807bae95b03675fe4520e9ecfb72c0d5347 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 26 Sep 2013 18:47:29 +0300 Subject: [PATCH 27/39] db loading hopefully the same as csv --- engine/loader_csv_test.go | 2 +- engine/loader_db.go | 82 +++++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 9fa64ae8a..85865b2cf 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -529,7 +529,7 @@ func TestLoadRatingProfiles(t *testing.T) { rp := csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] expected := &RatingProfile{} if reflect.DeepEqual(rp, expected) { - t.Errorf("Error loading rating profile: %#v", rp) + t.Errorf("Error loading rating profile: %#v", rp.DestinationMap["GERMANY"][1].RateIntervals[2].Rates[0]) } } diff --git a/engine/loader_db.go b/engine/loader_db.go index fd996c84b..c430f503f 100644 --- a/engine/loader_db.go +++ b/engine/loader_db.go @@ -26,19 +26,19 @@ import ( ) type DbReader struct { - tpid string - storDb LoadStorage - dataDb DataStorage - actions map[string][]*Action - actionsTimings map[string][]*ActionTiming - actionsTriggers map[string][]*ActionTrigger - accountActions []*UserBalance - destinations []*Destination - timings map[string]*Timing - rates map[string][]*LoadRate - destinationRates map[string][]*DestinationRate - activationPeriods map[string]*RatingPlan - ratingProfiles map[string]*RatingProfile + tpid string + storDb LoadStorage + dataDb DataStorage + actions map[string][]*Action + actionsTimings map[string][]*ActionTiming + actionsTriggers map[string][]*ActionTrigger + accountActions []*UserBalance + destinations []*Destination + timings map[string]*Timing + rates map[string][]*LoadRate + destinationRates map[string][]*DestinationRate + destinationRateTimings map[string][]*DestinationRateTiming + ratingProfiles map[string]*RatingProfile } func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader { @@ -46,7 +46,7 @@ func NewDbReader(storDB LoadStorage, storage DataStorage, tpid string) *DbReader c.storDb = storDB c.dataDb = storage c.tpid = tpid - c.activationPeriods = make(map[string]*RatingPlan) + c.destinationRateTimings = make(map[string][]*DestinationRateTiming) c.actionsTimings = make(map[string][]*ActionTiming) return c } @@ -146,33 +146,38 @@ func (dbr *DbReader) LoadDestinationRates() (err error) { return errors.New(fmt.Sprintf("Could not find rate for tag %v", dr.RateTag)) } dr.rates = rates + destinationExists := false + for _, d := range dbr.destinations { + if d.Id == dr.DestinationsTag { + destinationExists = true + break + } + } + if !destinationExists { + return errors.New(fmt.Sprintf("Could not get destination for tag %v", dr.DestinationsTag)) + } } } return nil } func (dbr *DbReader) LoadDestinationRateTimings() error { - rts, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, "") + drts, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, "") if err != nil { return err } - for _, rt := range rts { - t, exists := dbr.timings[rt.TimingTag] + for _, drt := range drts { + t, exists := dbr.timings[drt.TimingTag] if !exists { - return errors.New(fmt.Sprintf("Could not get timing for tag %v", rt.TimingTag)) + return errors.New(fmt.Sprintf("Could not get timing for tag %v", drt.TimingTag)) } - rt.timing = t - drs, exists := dbr.destinationRates[rt.DestinationRatesTag] + drt.timing = t + drs, exists := dbr.destinationRates[drt.DestinationRatesTag] if !exists { - return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", rt.DestinationRatesTag)) - } - for _, dr := range drs { - _, exists := dbr.activationPeriods[rt.Tag] - if !exists { - dbr.activationPeriods[rt.Tag] = &RatingPlan{} - } - dbr.activationPeriods[rt.Tag].AddRateInterval(rt.GetRateInterval(dr)) + return errors.New(fmt.Sprintf("Could not find destination rate for tag %v", drt.DestinationRatesTag)) } + drt.destinationRates = drs + dbr.destinationRateTimings[drt.Tag] = append(dbr.destinationRateTimings[drt.Tag], drt) } return nil } @@ -187,17 +192,18 @@ func (dbr *DbReader) LoadRatingProfiles() error { if err != nil { return errors.New(fmt.Sprintf("Cannot parse activation time from %v", rp.ActivationTime)) } - for _, d := range dbr.destinations { - ap, exists := dbr.activationPeriods[rp.DestRatesTimingTag] - if !exists { - return errors.New(fmt.Sprintf("Could not load rating timing for tag: %v", rp.DestRatesTimingTag)) - } - newAP := &RatingPlan{ActivationTime: at} - //copy(newAP.Intervals, ap.Intervals) - newAP.RateIntervals = append(newAP.RateIntervals, ap.RateIntervals...) - rp.AddRatingPlanIfNotPresent(d.Id, newAP) - + drts, exists := dbr.destinationRateTimings[rp.DestRatesTimingTag] + if !exists { + return errors.New(fmt.Sprintf("Could not load destination rate timings for tag: %v", rp.DestinationMap)) } + for _, drt := range drts { + plan := &RatingPlan{ActivationTime: at} + for _, dr := range drt.destinationRates { + plan.AddRateInterval(drt.GetRateInterval(dr)) + rp.AddRatingPlanIfNotPresent(dr.DestinationsTag, plan) + } + } + dbr.ratingProfiles[rp.Id] = rp } return nil } From 552aa0904fd8d74a543a5d4de3209689680c19b7 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 30 Sep 2013 22:07:39 +0300 Subject: [PATCH 28/39] using DestinationContainsPrefix method --- engine/calldesc.go | 6 +++--- engine/loader_csv_test.go | 2 +- engine/ratingprofile.go | 8 ++++---- engine/units_counter.go | 6 +++--- engine/userbalance.go | 6 +++--- engine/userbalance_test.go | 6 +++++- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index d82a69c7b..728f1d454 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -386,7 +386,7 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { Logger.Debug(fmt.Sprintf(" Attempting to debit from %v, value: %v", cd.GetUserBalanceKey(), cc.Cost+cc.ConnectFee)) defer storageGetter.SetUserBalance(userBalance) if cc.Cost != 0 || cc.ConnectFee != 0 { - userBalance.debitBalance(CREDIT+OUTBOUND, cc.Cost+cc.ConnectFee, true) + userBalance.debitCreditBalance(cc, true) } } return @@ -415,7 +415,7 @@ The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) DebitCents() (left float64, err error) { if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { defer storageGetter.SetUserBalance(userBalance) - return userBalance.debitBalance(CREDIT, cd.Amount, true), nil + return userBalance.debitGenericBalance(CREDIT+OUTBOUND, cd.Amount, true), nil } return 0.0, err } @@ -427,7 +427,7 @@ The amount filed has to be filled in call descriptor. func (cd *CallDescriptor) DebitSMS() (left float64, err error) { if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { defer storageGetter.SetUserBalance(userBalance) - return userBalance.debitBalance(SMS, cd.Amount, true), nil + return userBalance.debitGenericBalance(SMS+OUTBOUND, cd.Amount, true), nil } return 0, err } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 85865b2cf..158c48c84 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -529,7 +529,7 @@ func TestLoadRatingProfiles(t *testing.T) { rp := csvr.ratingProfiles["*out:CUSTOMER_1:0:rif:from:tm"] expected := &RatingProfile{} if reflect.DeepEqual(rp, expected) { - t.Errorf("Error loading rating profile: %#v", rp.DestinationMap["GERMANY"][1].RateIntervals[2].Rates[0]) + t.Errorf("Error loading rating profile: %+v", rp.DestinationMap["GERMANY"][1].RateIntervals[2].Rates[0]) } } diff --git a/engine/ratingprofile.go b/engine/ratingprofile.go index a2185f316..0a9bbe84d 100644 --- a/engine/ratingprofile.go +++ b/engine/ratingprofile.go @@ -52,13 +52,13 @@ func (rp *RatingProfile) AddRatingPlanIfNotPresent(destInfo string, plans ...*Ra func (rp *RatingProfile) GetRatingPlansForPrefix(destPrefix string) (foundPrefix string, aps []*RatingPlan, err error) { bestPrecision := 0 - for k, v := range rp.DestinationMap { - d, err := GetDestination(k) + for dId, v := range rp.DestinationMap { + precision, err := storageGetter.DestinationContainsPrefix(dId, destPrefix) if err != nil { - Logger.Err(fmt.Sprintf("Cannot find destination with id: %s", k)) + Logger.Err(fmt.Sprintf("Error checking destination: %v", err)) continue } - if precision, ok := d.containsPrefix(destPrefix); ok && precision > bestPrecision { + if precision > bestPrecision { bestPrecision = precision aps = v } diff --git a/engine/units_counter.go b/engine/units_counter.go index 78d573b68..deed2fdc7 100644 --- a/engine/units_counter.go +++ b/engine/units_counter.go @@ -52,12 +52,12 @@ func (uc *UnitsCounter) initMinuteBalances(ats []*ActionTrigger) { // is the same or ads the minute balance to the list if none matches. func (uc *UnitsCounter) addMinutes(amount float64, prefix string) { for _, mb := range uc.MinuteBalances { - d, err := GetDestination(mb.DestinationId) + precision, err := storageGetter.DestinationContainsPrefix(mb.DestinationId, prefix) if err != nil { - Logger.Err(fmt.Sprintf("Minutes counter: unknown destination: %s", mb.DestinationId)) + Logger.Err(fmt.Sprintf("Minutes counter: unknown destination: %v", mb.DestinationId)) continue } - if _, ok := d.containsPrefix(prefix); ok { + if precision > 0 { mb.Value += amount break } diff --git a/engine/userbalance.go b/engine/userbalance.go index 1c0e69fc9..f411ce073 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -82,11 +82,11 @@ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float if b.IsExpired() { continue } - d, err := GetDestination(b.DestinationId) + precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, prefix) if err != nil { continue } - if precision, ok := d.containsPrefix(prefix); ok { + if precision > 0 { b.precision = precision if b.Value > 0 { balances = append(balances, b) @@ -329,7 +329,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { /* Debits some amount of user's specified balance. Returns the remaining credit in user's balance. */ -func (ub *UserBalance) debitBalance(balanceId string, amount float64, count bool) float64 { +func (ub *UserBalance) debitGenericBalance(balanceId string, amount float64, count bool) float64 { if count { ub.countUnits(&Action{BalanceId: balanceId, Direction: OUTBOUND, Balance: &Balance{Value: amount}}) } diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 97c6ae6cb..e9c5012f4 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -145,7 +145,7 @@ func TestUserBalanceStorageStore(t *testing.T) { } } -func TestDebitMoneyBalance(t *testing.T) { +/*func TestDebitMoneyBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} @@ -185,6 +185,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } +*/ func TestDebitCreditZeroSecond(t *testing.T) { b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} @@ -626,6 +627,8 @@ func TestDebitNegativeMinuteBalance(t *testing.T) { } } */ + +/* func TestDebitSMSBalance(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.0, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} @@ -665,6 +668,7 @@ func TestDebitNegativeSMSBalance(t *testing.T) { t.Errorf("Expected %v was %v", 115, rifsBalance.BalanceMap[SMS+OUTBOUND]) } } +*/ func TestUserBalancedebitBalance(t *testing.T) { ub := &UserBalance{ From 09238f73559913fdc56140e8171d8ef694dfc0f6 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 17:30:10 +0300 Subject: [PATCH 29/39] spliting for money/minutes separation --- engine/timespans.go | 19 +++++++++++++----- engine/userbalance.go | 29 ++++++++++++++++++--------- engine/userbalance_test.go | 40 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/engine/timespans.go b/engine/timespans.go index c59f391d3..904de5b9c 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -37,10 +37,19 @@ type TimeSpan struct { } type Increment struct { - Duration time.Duration - Cost float64 - BalanceId string - BalanceType string + Duration time.Duration + Cost float64 + BalanceId string + BalanceType string + BalanceRateInterval *RateInterval + MinuteInfo *MinuteInfo +} + +// Holds the minute information related to a specified timespan +type MinuteInfo struct { + DestinationId string + Quantity float64 + Price float64 } // Returns the duration of the timespan @@ -168,7 +177,7 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { // Split the interval at the given increment start func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan { timeStart := ts.GetTimeStartForIncrement(index, increment) - newTs := &TimeSpan{TimeStart: timeStart, TimeEnd: ts.TimeEnd} + newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd} newTs.CallDuration = ts.CallDuration ts.TimeEnd = timeStart ts.Increments = ts.Increments[0:index] diff --git a/engine/userbalance.go b/engine/userbalance.go index f411ce073..0d33201f8 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -138,7 +138,7 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) BalanceChain { var usefulBalances BalanceChain for _, b := range balances { - if b.IsExpired() || (ub.Type == UB_TYPE_PREPAID && b.Value <= 0) { + if b.IsExpired() || (ub.Type != UB_TYPE_POSTPAID && b.Value <= 0) { continue } if b.DestinationId != "" { @@ -191,20 +191,21 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { // debit minutes for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ { ts := cc.Timespans[tsIndex] - ts.createIncrementsSlice() + tsWasSplited := false for incrementIndex, increment := range ts.Increments { + if tsWasSplited { + break + } paid := false for _, b := range usefulMinuteBalances { - if b.Value == 0 { - continue - } // check standard subject tags if b.RateSubject == ZEROSECOND || b.RateSubject == "" { amount := increment.Duration.Seconds() if b.Value >= amount { b.Value -= amount increment.BalanceId = b.Id + increment.MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) @@ -246,6 +247,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { cc.Timespans = append(cc.Timespans, nil) copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) cc.Timespans[tsIndex] = newTs + tsWasSplited = true } var newTimespans []*TimeSpan @@ -258,6 +260,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { cc.Timespans = newTimespans b.Value -= amount newTs.Increments[0].BalanceId = b.Id + newTs.Increments[0].MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { ub.countUnits(&Action{BalanceId: MINUTES, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) @@ -286,13 +289,21 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } if paid { continue + } else { + // Split if some increments were processed by minutes + if incrementIndex > 0 && ts.Increments[incrementIndex-1].MinuteInfo != nil { + newTs := ts.SplitByIncrement(incrementIndex, increment) + idx := tsIndex + 1 + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[idx+1:], cc.Timespans[idx:]) + cc.Timespans[idx] = newTs + newTs.createIncrementsSlice() + tsWasSplited = true + break + } } - // TODO: Split if some increments were processed by minutes // debit monetary for _, b := range usefulMoneyBalances { - if b.Value == 0 { - continue - } // check standard subject tags if b.RateSubject == "" { amount := increment.Cost diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index e9c5012f4..421e10040 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -355,14 +355,50 @@ func TestDebitCreditHasCredit(t *testing.T) { } if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 10 || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 30 { - t.Error("Error extracting minutes from balance: ", - rifsBalance.BalanceMap[MINUTES+OUTBOUND][0], rifsBalance.BalanceMap[CREDIT+OUTBOUND][0]) + t.Errorf("Error extracting minutes from balance: %+v, %+v", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != time.Minute || cc.Timespans[1].GetDuration() != 20*time.Second { t.Error("Error truncating extra timespans: ", cc.Timespans) } } +func TestDebitCreditSplitMinutesMoney(t *testing.T) { + b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + cc := &CallCost{ + 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, 20, 0, time.UTC), + CallDuration: 0, + RateInterval: &RateInterval{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}, + }, + }, + } + rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ + MINUTES + OUTBOUND: BalanceChain{b1}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + }} + err := rifsBalance.debitCreditBalance(cc, false) + if err != nil { + t.Error("Error debiting balance: ", err) + } + if cc.Timespans[0].Increments[0].BalanceId != "testb" || + cc.Timespans[0].Increments[0].Duration != 10*time.Second { + t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) + } + if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || + rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 40 { + t.Errorf("Error extracting minutes from balance: %+v, %+v", + rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) + } + if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != 10*time.Second || cc.Timespans[1].GetDuration() != 10*time.Second { + t.Error("Error truncating extra timespans: ", cc.Timespans) + } +} + func TestDebitCreditMoreTimespans(t *testing.T) { b1 := &Balance{Id: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ From 6cd7edd8a2110e0ff2124d9cc3a3d0c4ae4a0f1f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 20:09:42 +0300 Subject: [PATCH 30/39] refound money --- engine/balances.go | 13 ++++++-- engine/calldesc.go | 9 ++++++ engine/loader_csv.go | 2 +- engine/loader_csv_test.go | 4 +-- engine/responder.go | 16 ++++++++++ engine/timespans.go | 14 +++++++-- engine/userbalance.go | 29 ++++++++++++++--- engine/userbalance_test.go | 50 +++++++++++++++--------------- sessionmanager/fssessionmanager.go | 32 ++++++++++--------- 9 files changed, 118 insertions(+), 51 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 4065de810..0c7ea1c50 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -26,7 +26,7 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Id string + Uuid string Value float64 ExpirationDate time.Time Weight float64 @@ -51,7 +51,7 @@ func (b *Balance) IsExpired() bool { func (b *Balance) Clone() *Balance { return &Balance{ - Id: b.Id, + Uuid: b.Uuid, Value: b.Value, DestinationId: b.DestinationId, ExpirationDate: b.ExpirationDate, @@ -138,3 +138,12 @@ func (bc BalanceChain) Clone() BalanceChain { } return newChain } + +func (bc BalanceChain) GetBalance(uuid string) *Balance { + for _, balance := range bc { + if balance.Uuid == uuid { + return balance + } + } + return nil +} diff --git a/engine/calldesc.go b/engine/calldesc.go index 728f1d454..a5ab3c594 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -108,6 +108,7 @@ type CallDescriptor struct { Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject RatingPlans []*RatingPlan + Increments Increments userBalance *UserBalance } @@ -408,6 +409,14 @@ func (cd *CallDescriptor) MaxDebit(startTime time.Time) (cc *CallCost, err error return cd.Debit() } +func (cd *CallDescriptor) RefoundIncrements() (left float64, err error) { + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + defer storageGetter.SetUserBalance(userBalance) + userBalance.refoundIncrements(cd.Increments, true) + } + return 0.0, err +} + /* Interface method used to add/substract an amount of cents from user's money balance. The amount filed has to be filled in call descriptor. diff --git a/engine/loader_csv.go b/engine/loader_csv.go index b38bd96a4..2168661b2 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -381,7 +381,7 @@ func (csvr *CSVReader) LoadActions() (err error) { Weight: weight, ExpirationString: record[5], Balance: &Balance{ - Id: utils.GenUUID(), + Uuid: utils.GenUUID(), Value: units, Weight: minutesWeight, SpecialPrice: value, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 158c48c84..f2c6beacb 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -552,7 +552,7 @@ func TestLoadActions(t *testing.T) { ExpirationString: UNLIMITED, Weight: 10, Balance: &Balance{ - Id: as[0].Balance.Id, + Uuid: as[0].Balance.Uuid, Value: 10, Weight: 10, }, @@ -565,7 +565,7 @@ func TestLoadActions(t *testing.T) { ExpirationString: UNLIMITED, Weight: 10, Balance: &Balance{ - Id: as[1].Balance.Id, + Uuid: as[1].Balance.Uuid, Value: 100, Weight: 10, SpecialPriceType: PRICE_ABSOLUTE, diff --git a/engine/responder.go b/engine/responder.go index 631194729..2a164d66b 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -76,6 +76,18 @@ func (rs *Responder) MaxDebit(arg CallDescriptor, reply *CallCost) (err error) { return } +func (rs *Responder) RefoundIncrements(arg CallDescriptor, reply *float64) (err error) { + if rs.Bal != nil { + *reply, err = rs.callMethod(&arg, "Responder.RefoundIncrements") + } else { + r, e := AccLock.Guard(arg.GetUserBalanceKey(), func() (float64, error) { + return arg.RefoundIncrements() + }) + *reply, err = r, e + } + return +} + func (rs *Responder) DebitCents(arg CallDescriptor, reply *float64) (err error) { if rs.Bal != nil { *reply, err = rs.callMethod(&arg, "Responder.DebitCents") @@ -340,6 +352,7 @@ type Connector interface { GetCost(CallDescriptor, *CallCost) error Debit(CallDescriptor, *CallCost) error MaxDebit(CallDescriptor, *CallCost) error + RefoundIncrements(Increments, *float64) error DebitCents(CallDescriptor, *float64) error DebitSeconds(CallDescriptor, *float64) error GetMaxSessionTime(CallDescriptor, *float64) error @@ -360,6 +373,9 @@ func (rcc *RPCClientConnector) Debit(cd CallDescriptor, cc *CallCost) error { func (rcc *RPCClientConnector) MaxDebit(cd CallDescriptor, cc *CallCost) error { return rcc.Client.Call("Responder.MaxDebit", cd, cc) } +func (rcc *RPCClientConnector) RefoundIncrements(cd CallDescriptor, resp *float64) error { + return rcc.Client.Call("Responder.RefoundIncrements", cd, resp) +} func (rcc *RPCClientConnector) DebitCents(cd CallDescriptor, resp *float64) error { return rcc.Client.Call("Responder.DebitCents", cd, resp) } diff --git a/engine/timespans.go b/engine/timespans.go index 904de5b9c..2d47da876 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,13 +33,13 @@ type TimeSpan struct { RateInterval *RateInterval CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one - Increments []*Increment + Increments Increments } type Increment struct { Duration time.Duration Cost float64 - BalanceId string + BalanceUuid string BalanceType string BalanceRateInterval *RateInterval MinuteInfo *MinuteInfo @@ -52,6 +52,16 @@ type MinuteInfo struct { Price float64 } +type Increments []*Increment + +func (incs Increments) GetTotalCost() float64 { + cost := 0.0 + for _, increment := range incs { + cost += increment.Cost + } + return cost +} + // Returns the duration of the timespan func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) diff --git a/engine/userbalance.go b/engine/userbalance.go index 0d33201f8..d4221a5ee 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -108,8 +108,8 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { if a == nil { return errors.New("nil minute action!") } - if a.Balance.Id == "" { - a.Balance.Id = utils.GenUUID() + if a.Balance.Uuid == "" { + a.Balance.Uuid = utils.GenUUID() } if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain, 0) @@ -204,7 +204,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { amount := increment.Duration.Seconds() if b.Value >= amount { b.Value -= amount - increment.BalanceId = b.Id + increment.BalanceUuid = b.Uuid increment.MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { @@ -259,7 +259,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } cc.Timespans = newTimespans b.Value -= amount - newTs.Increments[0].BalanceId = b.Id + newTs.Increments[0].BalanceUuid = b.Uuid newTs.Increments[0].MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { @@ -309,7 +309,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { amount := increment.Cost if b.Value >= amount { b.Value -= amount - increment.BalanceId = b.Id + increment.BalanceUuid = b.Uuid paid = true if count { ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) @@ -337,6 +337,25 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { return nil } +func (ub *UserBalance) refoundIncrements(increments Increments, count bool) { + for _, increment := range increments { + var balance *Balance + for _, balanceChain := range ub.BalanceMap { + if balance = balanceChain.GetBalance(increment.BalanceUuid); balance != nil { + break + } + } + if balance != nil { + balance.Value += increment.Cost + if count { + ub.countUnits(&Action{BalanceId: increment.BalanceType, Direction: OUTBOUND, Balance: &Balance{Value: increment.Cost}}) + } + } else { + // TODO: where should put the money? + } + } +} + /* Debits some amount of user's specified balance. Returns the remaining credit in user's balance. */ diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 421e10040..71dcea440 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -46,7 +46,7 @@ func populateTestActionsForTriggers() { } func TestBalanceStoreRestore(t *testing.T) { - b := &Balance{Value: 14, Weight: 1, Id: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} + b := &Balance{Value: 14, Weight: 1, Uuid: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} marsh := NewCodecMsgpackMarshaler() output, err := marsh.Marshal(b) if err != nil { @@ -188,7 +188,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { */ func TestDebitCreditZeroSecond(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -206,7 +206,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" { + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || @@ -216,7 +216,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { } func TestDebitCreditZeroMinute(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -237,7 +237,7 @@ func TestDebitCreditZeroMinute(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -248,8 +248,8 @@ func TestDebitCreditZeroMinute(t *testing.T) { } } func TestDebitCreditZeroMixedMinute(t *testing.T) { - b1 := &Balance{Id: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Id: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -270,8 +270,8 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "tests" || - cc.Timespans[1].Increments[0].BalanceId != "testm" { + if cc.Timespans[0].Increments[0].BalanceUuid != "tests" || + cc.Timespans[1].Increments[0].BalanceUuid != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0], cc.Timespans[1].Increments[0]) } if rifsBalance.BalanceMap[MINUTES+OUTBOUND][1].Value != 0 || @@ -282,7 +282,7 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { } func TestDebitCreditNoCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -308,7 +308,7 @@ func TestDebitCreditNoCredit(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -322,7 +322,7 @@ func TestDebitCreditNoCredit(t *testing.T) { } func TestDebitCreditHasCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -343,13 +343,13 @@ func TestDebitCreditHasCredit(t *testing.T) { } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -364,7 +364,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } func TestDebitCreditSplitMinutesMoney(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -379,13 +379,13 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -400,7 +400,7 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } func TestDebitCreditMoreTimespans(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -426,7 +426,7 @@ func TestDebitCreditMoreTimespans(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -437,8 +437,8 @@ func TestDebitCreditMoreTimespans(t *testing.T) { } func TestDebitCreditMoreTimespansMixed(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Id: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -464,7 +464,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -476,7 +476,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { } func TestDebitCreditNoConectFeeCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -529,14 +529,14 @@ func TestDebitCreditMoneyOnly(t *testing.T) { }, } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "money", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "money", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "money" || + if cc.Timespans[0].Increments[0].BalanceUuid != "money" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 1e22acfd1..da76ec7d9 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -245,30 +245,34 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { hangupTime = time.Now() } end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd - refoundDuration := end.Sub(hangupTime).Seconds() - cost := 0.0 + refoundDuration := end.Sub(hangupTime) + var refoundIncrements []*engine.Increment engine.Logger.Info(fmt.Sprintf("Refund duration: %v", refoundDuration)) for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] - tsDuration := ts.GetDuration().Seconds() + tsDuration := ts.GetDuration() if refoundDuration <= tsDuration { - // find procentage - procentage := (refoundDuration * 100) / tsDuration - tmpCost := (procentage * ts.Cost) / 100 - ts.Cost -= tmpCost - cost += tmpCost - // set the end time to now - ts.TimeEnd = hangupTime + lastRefoundedIncrementIndex := 0 + var lastRefoundedIncrement *engine.Increment + for incrementIndex, increment := range ts.Increments { + if increment.Duration <= refoundDuration { + refoundIncrements = append(refoundIncrements, increment) + refoundDuration -= increment.Duration + lastRefoundedIncrementIndex = incrementIndex + lastRefoundedIncrement = increment + } + } + ts.SplitByIncrement(lastRefoundedIncrementIndex, lastRefoundedIncrement) break // do not go to other timespans } else { - cost += ts.Cost - // remove the timestamp entirely + refoundIncrements = append(refoundIncrements, ts.Increments...) + // remove the timespan entirely lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refound refoundDuration -= tsDuration } } - if cost > 0 { + if len(refoundIncrements) > 0 { cd := &engine.CallDescriptor{ Direction: lastCC.Direction, Tenant: lastCC.Tenant, @@ -276,7 +280,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, - Amount: -cost, + Increments: refoundIncrements, // FallbackSubject: lastCC.FallbackSubject, // TODO: check how to best add it } var response float64 From 5a6df951333106116014394749107340f947d6da Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 22:09:21 +0300 Subject: [PATCH 31/39] updated msgpack codec --- .../mysql/create_tariffplan_tables.sql | 4 ++-- engine/balances.go | 23 +++++++++---------- engine/loader_csv.go | 20 +++++++--------- engine/storage_interface.go | 14 ++++------- engine/storage_sql.go | 20 ++++++++-------- engine/tpimporter_csv.go | 15 ++++++------ 6 files changed, 43 insertions(+), 53 deletions(-) diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index 652256dcf..4908f87d6 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -120,9 +120,9 @@ CREATE TABLE `tp_actions` ( `units` DECIMAL(8,4) NOT NULL, `expiry_time` varchar(24) NOT NULL, `destination_tag` varchar(64) NOT NULL, - `rate_type` varchar(8) NOT NULL, - `rate` DECIMAL(8,4) NOT NULL, + `rate_subject` varchar(64) NOT NULL, `minutes_weight` DECIMAL(5,2) NOT NULL, + `extra_parameters` varchar(256) NOT NULL, `weight` DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`), KEY `tpid` (`tpid`), diff --git a/engine/balances.go b/engine/balances.go index 0c7ea1c50..37dbc7ffa 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -26,16 +26,16 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Uuid string - Value float64 - ExpirationDate time.Time - Weight float64 - GroupIds []string - SpecialPriceType string - SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) - DestinationId string - RateSubject string - precision int + Uuid string + Value float64 + ExpirationDate time.Time + Weight float64 + GroupIds []string + //SpecialPriceType string + //SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) + DestinationId string + RateSubject string + precision int } func (b *Balance) Equal(o *Balance) bool { @@ -84,8 +84,7 @@ func (bc BalanceChain) Swap(i, j int) { func (bc BalanceChain) Less(j, i int) bool { return bc[i].Weight < bc[j].Weight || - bc[i].precision < bc[j].precision || - bc[i].SpecialPrice > bc[j].SpecialPrice + bc[i].precision < bc[j].precision } func (bc BalanceChain) Sort() { diff --git a/engine/loader_csv.go b/engine/loader_csv.go index 2168661b2..bdc46eef1 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -361,13 +361,9 @@ func (csvr *CSVReader) LoadActions() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) } - value, err := strconv.ParseFloat(record[8], 64) + balanceWeight, err := strconv.ParseFloat(record[9], 64) if err != nil { - return errors.New(fmt.Sprintf("Could not parse action price: %v", err)) - } - minutesWeight, err := strconv.ParseFloat(record[9], 64) - if err != nil { - return errors.New(fmt.Sprintf("Could not parse action minutes weight: %v", err)) + return errors.New(fmt.Sprintf("Could not parse action balance weight: %v", err)) } weight, err := strconv.ParseFloat(record[9], 64) if err != nil { @@ -380,13 +376,13 @@ func (csvr *CSVReader) LoadActions() (err error) { Direction: record[3], Weight: weight, ExpirationString: record[5], + ExtraParameters: record[8], Balance: &Balance{ - Uuid: utils.GenUUID(), - Value: units, - Weight: minutesWeight, - SpecialPrice: value, - SpecialPriceType: record[7], - DestinationId: record[6], + Uuid: utils.GenUUID(), + Value: units, + Weight: balanceWeight, + DestinationId: record[6], + RateSubject: record[7], }, } if _, err := utils.ParseDate(a.ExpirationString); err != nil { diff --git a/engine/storage_interface.go b/engine/storage_interface.go index 2f91cdb8a..b77e740c7 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -50,13 +50,6 @@ const ( RATER_SOURCE = "RAT" ) -var ( - // for codec msgpack - mapStrIntfTyp = reflect.TypeOf(map[string]interface{}(nil)) - sliceByteTyp = reflect.TypeOf([]byte(nil)) - timeTyp = reflect.TypeOf(time.Time{}) -) - type Storage interface { Close() Flush() error @@ -208,11 +201,14 @@ type CodecMsgpackMarshaler struct { func NewCodecMsgpackMarshaler() *CodecMsgpackMarshaler { cmm := &CodecMsgpackMarshaler{new(codec.MsgpackHandle)} mh := cmm.mh + var mapStrIntfTyp = reflect.TypeOf(map[string]interface{}(nil)) + //sliceByteTyp = reflect.TypeOf([]byte(nil)) + //timeTyp = reflect.TypeOf(time.Time{}) mh.MapType = mapStrIntfTyp // configure extensions for msgpack, to enable Binary and Time support for tags 0 and 1 - mh.AddExt(sliceByteTyp, 0, mh.BinaryEncodeExt, mh.BinaryDecodeExt) - mh.AddExt(timeTyp, 1, mh.TimeEncodeExt, mh.TimeDecodeExt) + //mh.AddExt(sliceByteTyp, 0, mh.BinaryEncodeExt, mh.BinaryDecodeExt) + //mh.AddExt(timeTyp, 1, mh.TimeEncodeExt, mh.TimeDecodeExt) return cmm } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index c11e43f46..dae6940a6 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -539,9 +539,9 @@ func (self *SQLStorage) SetTPActions(tpid string, acts map[string][]*Action) err if i != 0 { //Consecutive values after the first will be prefixed with "," as separator qry += "," } - qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s','%s',%f,%f,%f)", + qry += fmt.Sprintf("('%s','%s','%s','%s','%s',%f,'%s','%s',%f,'%s',%f)", tpid, actId, act.ActionType, act.BalanceId, act.Direction, act.Balance.Value, act.ExpirationString, - act.Balance.DestinationId, act.Balance.SpecialPriceType, act.Balance.SpecialPrice, act.Balance.Weight, act.Weight) + act.Balance.DestinationId, act.Balance.RateSubject, act.Balance.Weight, act.ExtraParameters, act.Weight) i++ } } @@ -1085,9 +1085,9 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er defer rows.Close() for rows.Next() { var id int - var units, rate, minutes_weight, weight float64 - var tpid, tag, action, balance_type, direction, destinations_tag, rate_type, expirationDate string - if err := rows.Scan(&id, &tpid, &tag, &action, &balance_type, &direction, &units, &expirationDate, &destinations_tag, &rate_type, &rate, &minutes_weight, &weight); err != nil { + var units, balance_weight, weight float64 + var tpid, tag, action, balance_type, direction, destinations_tag, rate_subject, extra_parameters, expirationDate string + if err := rows.Scan(&id, &tpid, &tag, &action, &balance_type, &direction, &units, &expirationDate, &destinations_tag, &rate_subject, &balance_weight, &extra_parameters, &weight); err != nil { return nil, err } var price float64 @@ -1097,13 +1097,13 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er BalanceId: balance_type, Direction: direction, Weight: weight, + ExtraParameters: extra_parameters, ExpirationString: expirationDate, Balance: &Balance{ - Value: units, - Weight: minutes_weight, - SpecialPrice: price, - SpecialPriceType: rate_type, - DestinationId: destinations_tag, + Value: units, + Weight: balance_weight, + RateSubject: rate_subject, + DestinationId: destinations_tag, }, } as[tag] = append(as[tag], a) diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index db8105390..4ba7d2e7b 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -285,7 +285,7 @@ func (self *TPCSVImporter) importActions(fn string) error { } continue } - actId, actionType, balanceType, direction, destTag, rateType := record[0], record[1], record[2], record[3], record[6], record[7] + actId, actionType, balanceType, direction, destTag, rateSubject := record[0], record[1], record[2], record[3], record[6], record[7] units, err := strconv.ParseFloat(record[4], 64) if err != nil { if self.Verbose { @@ -293,8 +293,7 @@ func (self *TPCSVImporter) importActions(fn string) error { } continue } - rateValue, _ := strconv.ParseFloat(record[8], 64) // Ignore errors since empty string is error, we can find out based on rateType if defined - minutesWeight, _ := strconv.ParseFloat(record[9], 64) + balanceWeight, _ := strconv.ParseFloat(record[9], 64) weight, err := strconv.ParseFloat(record[10], 64) if err != nil { if self.Verbose { @@ -307,12 +306,12 @@ func (self *TPCSVImporter) importActions(fn string) error { BalanceId: balanceType, Direction: direction, ExpirationString: record[5], + ExtraParameters: record[8], Balance: &Balance{ - Value: units, - DestinationId: destTag, - SpecialPriceType: rateType, - SpecialPrice: rateValue, - Weight: minutesWeight, + Value: units, + DestinationId: destTag, + RateSubject: rateSubject, + Weight: balanceWeight, }, Weight: weight, } From fc0052800789cf0b0ad6c6c1c9d447023a9e8796 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 22:12:01 +0300 Subject: [PATCH 32/39] removed unused import --- engine/storage_interface.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/storage_interface.go b/engine/storage_interface.go index b77e740c7..ad97082a5 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -27,7 +27,6 @@ import ( "github.com/vmihailenco/msgpack" "labix.org/v2/mgo/bson" "reflect" - "time" ) const ( From 7db0919be09794001b219f9a2751fe1d552ca041 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 22:30:42 +0300 Subject: [PATCH 33/39] started removing special rates and adding new rate subject --- engine/balances.go | 20 ++++++++++---------- engine/balances_test.go | 11 ----------- engine/loader_csv.go | 6 +++--- engine/loader_csv_test.go | 17 +++++++++-------- engine/responder.go | 2 +- engine/storage_sql.go | 1 - engine/tpimporter_csv.go | 4 ++-- sessionmanager/fssessionmanager.go | 3 ++- 8 files changed, 27 insertions(+), 37 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 37dbc7ffa..30a1ae2e8 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -26,16 +26,16 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Uuid string - Value float64 - ExpirationDate time.Time - Weight float64 - GroupIds []string - //SpecialPriceType string - //SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) - DestinationId string - RateSubject string - precision int + Uuid string + Value float64 + ExpirationDate time.Time + Weight float64 + GroupIds []string + SpecialPriceType string + SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) + DestinationId string + RateSubject string + precision int } func (b *Balance) Equal(o *Balance) bool { diff --git a/engine/balances_test.go b/engine/balances_test.go index 20a3179af..003064ea5 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -45,17 +45,6 @@ func TestBalanceSortPrecision(t *testing.T) { } } -func TestBalanceSortSpecialPrice(t *testing.T) { - mb1 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} - mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 2} - var bs BalanceChain - bs = append(bs, mb2, mb1) - bs.Sort() - if bs[0] != mb1 || bs[1] != mb2 { - t.Error("Buckets not sorted by price!") - } -} - func TestBalanceEqual(t *testing.T) { mb1 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} mb2 := &Balance{Weight: 1, precision: 1, RateSubject: "1", DestinationId: ""} diff --git a/engine/loader_csv.go b/engine/loader_csv.go index bdc46eef1..caf110b64 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -361,11 +361,11 @@ func (csvr *CSVReader) LoadActions() (err error) { if err != nil { return errors.New(fmt.Sprintf("Could not parse action units: %v", err)) } - balanceWeight, err := strconv.ParseFloat(record[9], 64) + balanceWeight, err := strconv.ParseFloat(record[8], 64) if err != nil { return errors.New(fmt.Sprintf("Could not parse action balance weight: %v", err)) } - weight, err := strconv.ParseFloat(record[9], 64) + weight, err := strconv.ParseFloat(record[10], 64) if err != nil { return errors.New(fmt.Sprintf("Could not parse action weight: %v", err)) } @@ -376,7 +376,7 @@ func (csvr *CSVReader) LoadActions() (err error) { Direction: record[3], Weight: weight, ExpirationString: record[5], - ExtraParameters: record[8], + ExtraParameters: record[9], Balance: &Balance{ Uuid: utils.GenUUID(), Value: units, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index f2c6beacb..d12a57b85 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -87,8 +87,8 @@ vdf,0,*out,inf,2012-02-28T00:00:00Z,STANDARD,inf vdf,0,*out,fall,2012-02-28T00:00:00Z,PREMIUM,rif ` actions = ` -MINI,*topup_reset,*monetary,*out,10,*unlimited,,,0,10,10 -MINI,*topup,*minutes,*out,100,*unlimited,NAT,*absolute,0,10,10 +MINI,*topup_reset,*monetary,*out,10,*unlimited,,,10,,10 +MINI,*topup,*minutes,*out,100,*unlimited,NAT,test,10,,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 @@ -550,6 +550,7 @@ func TestLoadActions(t *testing.T) { BalanceId: CREDIT, Direction: OUTBOUND, ExpirationString: UNLIMITED, + ExtraParameters: "", Weight: 10, Balance: &Balance{ Uuid: as[0].Balance.Uuid, @@ -563,14 +564,14 @@ func TestLoadActions(t *testing.T) { BalanceId: MINUTES, Direction: OUTBOUND, ExpirationString: UNLIMITED, + ExtraParameters: "", Weight: 10, Balance: &Balance{ - Uuid: as[1].Balance.Uuid, - Value: 100, - Weight: 10, - SpecialPriceType: PRICE_ABSOLUTE, - SpecialPrice: 0, - DestinationId: "NAT", + Uuid: as[1].Balance.Uuid, + Value: 100, + Weight: 10, + RateSubject: "test", + DestinationId: "NAT", }, }, } diff --git a/engine/responder.go b/engine/responder.go index 2a164d66b..73bc44a30 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -352,7 +352,7 @@ type Connector interface { GetCost(CallDescriptor, *CallCost) error Debit(CallDescriptor, *CallCost) error MaxDebit(CallDescriptor, *CallCost) error - RefoundIncrements(Increments, *float64) error + RefoundIncrements(CallDescriptor, *float64) error DebitCents(CallDescriptor, *float64) error DebitSeconds(CallDescriptor, *float64) error GetMaxSessionTime(CallDescriptor, *float64) error diff --git a/engine/storage_sql.go b/engine/storage_sql.go index dae6940a6..c8cc00c5b 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -1090,7 +1090,6 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er if err := rows.Scan(&id, &tpid, &tag, &action, &balance_type, &direction, &units, &expirationDate, &destinations_tag, &rate_subject, &balance_weight, &extra_parameters, &weight); err != nil { return nil, err } - var price float64 a := &Action{ Id: utils.GenUUID(), ActionType: action, diff --git a/engine/tpimporter_csv.go b/engine/tpimporter_csv.go index 4ba7d2e7b..9d424ddee 100644 --- a/engine/tpimporter_csv.go +++ b/engine/tpimporter_csv.go @@ -293,7 +293,7 @@ func (self *TPCSVImporter) importActions(fn string) error { } continue } - balanceWeight, _ := strconv.ParseFloat(record[9], 64) + balanceWeight, _ := strconv.ParseFloat(record[8], 64) weight, err := strconv.ParseFloat(record[10], 64) if err != nil { if self.Verbose { @@ -306,7 +306,7 @@ func (self *TPCSVImporter) importActions(fn string) error { BalanceId: balanceType, Direction: direction, ExpirationString: record[5], - ExtraParameters: record[8], + ExtraParameters: record[9], Balance: &Balance{ Value: units, DestinationId: destTag, diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index da76ec7d9..283826422 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -246,7 +246,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { } end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refoundDuration := end.Sub(hangupTime) - var refoundIncrements []*engine.Increment + var refoundIncrements engine.Increments engine.Logger.Info(fmt.Sprintf("Refund duration: %v", refoundDuration)) for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] @@ -289,6 +289,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { engine.Logger.Err(fmt.Sprintf("Debit cents failed: %v", err)) } } + cost := refoundIncrements.GetTotalCost() lastCC.Cost -= cost engine.Logger.Info(fmt.Sprintf("Rambursed %v cents", cost)) From 8bd4ac1ebab4ef085b1f6242b5e97a83971246b0 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 22:33:36 +0300 Subject: [PATCH 34/39] msgpack fixes --- engine/storage_interface.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/storage_interface.go b/engine/storage_interface.go index ad97082a5..6ed4464a8 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -27,6 +27,7 @@ import ( "github.com/vmihailenco/msgpack" "labix.org/v2/mgo/bson" "reflect" + "time" ) const ( @@ -202,12 +203,12 @@ func NewCodecMsgpackMarshaler() *CodecMsgpackMarshaler { mh := cmm.mh var mapStrIntfTyp = reflect.TypeOf(map[string]interface{}(nil)) //sliceByteTyp = reflect.TypeOf([]byte(nil)) - //timeTyp = reflect.TypeOf(time.Time{}) + var timeTyp = reflect.TypeOf(time.Time{}) mh.MapType = mapStrIntfTyp // configure extensions for msgpack, to enable Binary and Time support for tags 0 and 1 //mh.AddExt(sliceByteTyp, 0, mh.BinaryEncodeExt, mh.BinaryDecodeExt) - //mh.AddExt(timeTyp, 1, mh.TimeEncodeExt, mh.TimeDecodeExt) + mh.AddExt(timeTyp, 1, mh.TimeEncodeExt, mh.TimeDecodeExt) return cmm } From c889eafb20152000889fb1f7496758d9492d270f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 3 Oct 2013 20:14:45 +0300 Subject: [PATCH 35/39] removed special price --- engine/action_trigger.go | 12 ++++++-- engine/actions_test.go | 49 +++++++++++++++---------------- engine/balances.go | 39 ++++++++++++++++--------- engine/balances_test.go | 8 +++--- engine/calldesc.go | 2 +- engine/calldesc_test.go | 2 +- engine/storage_test.go | 4 +-- engine/units_counter_test.go | 4 +-- engine/userbalance.go | 36 +++++++++++++++++------ engine/userbalance_test.go | 56 ++++++++++++++++++++++-------------- 10 files changed, 132 insertions(+), 80 deletions(-) diff --git a/engine/action_trigger.go b/engine/action_trigger.go index 2b1c7e2a7..e395bb6c7 100644 --- a/engine/action_trigger.go +++ b/engine/action_trigger.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "encoding/json" "fmt" "github.com/cgrates/cgrates/utils" "sort" @@ -73,9 +74,14 @@ func (at *ActionTrigger) Match(a *Action) bool { id := a.BalanceId == "" || at.BalanceId == a.BalanceId direction := a.Direction == "" || at.Direction == a.Direction thresholdType, thresholdValue := true, true - if a.Balance != nil { - thresholdType = a.Balance.SpecialPriceType == "" || at.ThresholdType == a.Balance.SpecialPriceType - thresholdValue = a.Balance.SpecialPrice == 0 || at.ThresholdValue == a.Balance.SpecialPrice + if a.ExtraParameters != "" { + t := struct { + ThresholdType string + ThresholdValue float64 + }{} + json.Unmarshal([]byte(a.ExtraParameters), &t) + thresholdType = t.ThresholdType == "" || at.ThresholdType == t.ThresholdType + thresholdValue = t.ThresholdValue == 0 || at.ThresholdValue == t.ThresholdValue } return id && direction && thresholdType && thresholdValue } diff --git a/engine/actions_test.go b/engine/actions_test.go index 6eba519dc..ddddc2743 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "fmt" "github.com/cgrates/cgrates/utils" "testing" "time" @@ -445,7 +446,7 @@ func TestActionTriggerMatchMinuteBucketFull(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} + a := &Action{ExtraParameters: fmt.Sprintf(`{"ThresholdType":"%v", "ThresholdValue": %v}`, TRIGGER_MAX_BALANCE, 2)} if !at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -458,7 +459,7 @@ func TestActionTriggerMatchAllFull(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, ExtraParameters: fmt.Sprintf(`{"ThresholdType":"%v", "ThresholdValue": %v}`, TRIGGER_MAX_BALANCE, 2)} if !at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -471,7 +472,7 @@ func TestActionTriggerMatchSomeFalse(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: INBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 2}} + a := &Action{Direction: INBOUND, BalanceId: CREDIT, ExtraParameters: fmt.Sprintf(`{"ThresholdType":"%v", "ThresholdValue": %v}`, TRIGGER_MAX_BALANCE, 2)} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -484,7 +485,7 @@ func TestActionTriggerMatcBalanceFalse(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_BALANCE, SpecialPrice: 3}} + a := &Action{Direction: OUTBOUND, BalanceId: CREDIT, ExtraParameters: fmt.Sprintf(`{"ThresholdType":"%v", "ThresholdValue": %v}`, TRIGGER_MAX_BALANCE, 3.0)} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -497,7 +498,7 @@ func TestActionTriggerMatcAllFalse(t *testing.T) { ThresholdType: TRIGGER_MAX_BALANCE, ThresholdValue: 2, } - a := &Action{Direction: INBOUND, BalanceId: MINUTES, Balance: &Balance{SpecialPriceType: TRIGGER_MAX_COUNTER, SpecialPrice: 3}} + a := &Action{Direction: INBOUND, BalanceId: MINUTES, ExtraParameters: fmt.Sprintf(`{"ThresholdType":"%v", "ThresholdValue": %v}`, TRIGGER_MAX_COUNTER, 3)} if at.Match(a) { t.Errorf("Action trigger [%v] does not match action [%v]", at, a) } @@ -518,7 +519,7 @@ func TestActionTriggerPriotityList(t *testing.T) { func TestActionResetTriggres(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -544,7 +545,7 @@ func TestActionResetTriggresExecutesThem(t *testing.T) { func TestActionResetTriggresActionFilter(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -558,7 +559,7 @@ func TestActionSetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -572,7 +573,7 @@ func TestActionSetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -586,7 +587,7 @@ func TestActionResetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -605,7 +606,7 @@ func TestActionResetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -623,7 +624,7 @@ func TestActionTopupResetCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -644,11 +645,11 @@ func TestActionTopupResetMinutes(t *testing.T) { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{ CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, - MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, DestinationId: "NAT"}} topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue() != 5 || @@ -664,7 +665,7 @@ func TestActionTopupCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -683,11 +684,11 @@ func TestActionTopupMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, DestinationId: "NAT"}} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[MINUTES+OUTBOUND].GetTotalValue() != 15 || @@ -703,7 +704,7 @@ func TestActionDebitCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -722,11 +723,11 @@ func TestActionDebitMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } - a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}} + a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Value: 5, Weight: 20, DestinationId: "NAT"}} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 5 || @@ -745,8 +746,8 @@ func TestActionResetAllCounters(t *testing.T) { BalanceMap: map[string]BalanceChain{ CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES: BalanceChain{ - &Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, - &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + &Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, + &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -774,7 +775,7 @@ func TestActionResetCounterMinutes(t *testing.T) { Type: UB_TYPE_POSTPAID, BalanceMap: map[string]BalanceChain{ CREDIT: BalanceChain{&Balance{Value: 100}}, - MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + MINUTES: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } @@ -801,7 +802,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } diff --git a/engine/balances.go b/engine/balances.go index 30a1ae2e8..d138ff6c1 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "fmt" "math" "sort" "time" @@ -26,16 +27,16 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Uuid string - Value float64 - ExpirationDate time.Time - Weight float64 - GroupIds []string - SpecialPriceType string - SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) - DestinationId string - RateSubject string - precision int + Uuid string + Value float64 + ExpirationDate time.Time + Weight float64 + GroupIds []string + //SpecialPriceType string + //SpecialPrice float64 // absolute for minutes and percent for monetary (can be positive or negative) + DestinationId string + RateSubject string + precision int } func (b *Balance) Equal(o *Balance) bool { @@ -61,14 +62,26 @@ func (b *Balance) Clone() *Balance { } // Returns the available number of seconds for a specified credit -func (b *Balance) GetSecondsForCredit(credit float64) (seconds float64) { +func (b *Balance) GetSecondsForCredit(cd *CallDescriptor, credit float64) (seconds float64) { seconds = b.Value - if b.SpecialPrice > 0 { - seconds = math.Min(credit/b.SpecialPrice, b.Value) + // TODO: fix this + cc, err := b.GetCost(cd) + if err != nil { + Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) + return 0 + } + if cc.Cost > 0 { + seconds = math.Min(credit/cc.Cost, b.Value) } return } +func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) { + cd.Subject = b.RateSubject + cd.Account = cd.Subject + return cd.GetCost() +} + /* Structure to store minute buckets according to weight, precision or price. */ diff --git a/engine/balances_test.go b/engine/balances_test.go index 003064ea5..f929834a5 100644 --- a/engine/balances_test.go +++ b/engine/balances_test.go @@ -24,8 +24,8 @@ import ( ) func TestBalanceSortWeight(t *testing.T) { - mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} - mb2 := &Balance{Weight: 2, precision: 1, SpecialPrice: 1} + mb1 := &Balance{Weight: 1, precision: 2} + mb2 := &Balance{Weight: 2, precision: 1} var bs BalanceChain bs = append(bs, mb2, mb1) bs.Sort() @@ -35,8 +35,8 @@ func TestBalanceSortWeight(t *testing.T) { } func TestBalanceSortPrecision(t *testing.T) { - mb1 := &Balance{Weight: 1, precision: 2, SpecialPrice: 2} - mb2 := &Balance{Weight: 1, precision: 1, SpecialPrice: 1} + mb1 := &Balance{Weight: 1, precision: 2} + mb2 := &Balance{Weight: 1, precision: 1} var bs BalanceChain bs = append(bs, mb2, mb1) bs.Sort() diff --git a/engine/calldesc.go b/engine/calldesc.go index a5ab3c594..80b765f63 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -334,7 +334,7 @@ func (cd *CallDescriptor) GetMaxSessionTime(startTime time.Time) (seconds float6 if userBalance.Type == UB_TYPE_POSTPAID { return -1, nil } else { - availableSeconds, availableCredit, _ = userBalance.getSecondsForPrefix(cd.Destination) + availableSeconds, availableCredit, _ = userBalance.getSecondsForPrefix(cd) Logger.Debug(fmt.Sprintf("available sec: %v credit: %v", availableSeconds, availableCredit)) } } else { diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 6871a8bca..0fd7c02ed 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -50,7 +50,7 @@ func populateDB() { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{ - &Balance{Value: 20, DestinationId: "NAT", Weight: 10, SpecialPrice: 1}, + &Balance{Value: 20, DestinationId: "NAT", Weight: 10}, &Balance{Value: 100, DestinationId: "RET", Weight: 20}, }}, } diff --git a/engine/storage_test.go b/engine/storage_test.go index 7b450c21c..268e82e92 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -99,7 +99,7 @@ func GetUB() *UserBalance { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBalances: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + MinuteBalances: BalanceChain{&Balance{Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}, } at := &ActionTrigger{ Id: "some_uuid", @@ -115,7 +115,7 @@ func GetUB() *UserBalance { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}, MINUTES: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}, MINUTES: BalanceChain{&Balance{Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{uc, uc}, ActionTriggers: ActionTriggerPriotityList{at, at, at}, } diff --git a/engine/units_counter_test.go b/engine/units_counter_test.go index 9917e4a79..eaa54332a 100644 --- a/engine/units_counter_test.go +++ b/engine/units_counter_test.go @@ -27,7 +27,7 @@ func TestUnitsCounterAddBalance(t *testing.T) { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBalances: []*Balance{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + MinuteBalances: []*Balance{&Balance{Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}, } uc.addMinutes(20, "test") if len(uc.MinuteBalances) != 2 { @@ -40,7 +40,7 @@ func TestUnitsCounterAddBalanceExists(t *testing.T) { Direction: OUTBOUND, BalanceId: SMS, Units: 100, - MinuteBalances: []*Balance{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}, + MinuteBalances: []*Balance{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}, } uc.addMinutes(5, "0723") if len(uc.MinuteBalances) != 2 || uc.MinuteBalances[0].Value != 15 { diff --git a/engine/userbalance.go b/engine/userbalance.go index d4221a5ee..0972455cf 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -72,17 +72,17 @@ type UserBalance struct { } // Returns user's available minutes for the specified destination -func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, balances BalanceChain) { - credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() - if len(ub.BalanceMap[MINUTES+OUTBOUND]) == 0 { +func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit float64, balances BalanceChain) { + credit = ub.getBalanceForPrefix(cd.Destination, ub.BalanceMap[CREDIT+cd.Direction]).GetTotalValue() + if len(ub.BalanceMap[MINUTES+cd.Direction]) == 0 { // Logger.Debug("There are no minute buckets to check for user: ", ub.Id) return } - for _, b := range ub.BalanceMap[MINUTES+OUTBOUND] { + for _, b := range ub.BalanceMap[MINUTES+cd.Direction] { if b.IsExpired() { continue } - precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, prefix) + precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, cd.Destination) if err != nil { continue } @@ -95,8 +95,13 @@ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float } balances.Sort() // sorts the buckets according to priority, precision or price for _, b := range balances { - s := b.GetSecondsForCredit(credit) - credit -= s * b.SpecialPrice + s := b.GetSecondsForCredit(cd, credit) + cc, err := b.GetCost(cd) + if err != nil { + Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) + continue + } + credit -= s * cc.Cost seconds += s } return @@ -274,7 +279,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration - newCC, err := cd.GetCost() + newCC, err := b.GetCost(cd) if err != nil { Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) continue @@ -318,6 +323,21 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } } else { // get the new rate + cd := cc.CreateCallDescriptor() + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd + cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration + newCC, err := b.GetCost(cd) + if err != nil { + Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) + continue + } + //debit new callcost + for _, nts := range newCC.Timespans { + for _, nIncrement := range nts.Increments { + _ = nIncrement + } + } } } if !paid { diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 71dcea440..ca601022d 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -35,7 +35,7 @@ func init() { func populateTestActionsForTriggers() { ats := []*Action{ &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Balance: &Balance{Value: 10}}, - &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Weight: 20, SpecialPrice: 1, Value: 10, DestinationId: "NAT"}}, + &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, Balance: &Balance{Weight: 20, Value: 10, DestinationId: "NAT"}}, } storageGetter.SetActions("TEST_ACTIONS", ats) ats1 := []*Action{ @@ -96,8 +96,8 @@ func TestBalanceChainStoreRestore(t *testing.T) { } func TestUserBalanceStorageStoreRestore(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) ub1, err := storageGetter.GetUserBalance("other") @@ -111,7 +111,10 @@ func TestGetSecondsForPrefix(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} - seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") + cd := &CallDescriptor{ + Destination: "0723", + } + seconds, credit, bucketList := ub1.getSecondsForPrefix(cd) expected := 110.0 if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { t.Errorf("Expected %v was %v", expected, seconds) @@ -119,11 +122,14 @@ func TestGetSecondsForPrefix(t *testing.T) { } func TestGetSpecialPricedSeconds(t *testing.T) { - b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} - b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} - seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") + cd := &CallDescriptor{ + Destination: "0723", + } + seconds, credit, bucketList := ub1.getSecondsForPrefix(cd) expected := 21.0 if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight { t.Errorf("Expected %v was %v", expected, seconds) @@ -131,8 +137,8 @@ func TestGetSpecialPricedSeconds(t *testing.T) { } func TestUserBalanceStorageStore(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) result, err := storageGetter.GetUserBalance(rifsBalance.Id) @@ -710,9 +716,9 @@ func TestUserBalancedebitBalance(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, } - newMb := &Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NEW"} + newMb := &Balance{Weight: 20, DestinationId: "NEW"} a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: newMb} ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 3 || ub.BalanceMap[MINUTES+OUTBOUND][2] != newMb { @@ -725,9 +731,9 @@ func TestUserBalancedebitBalanceExists(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 15, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 15, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, } - newMb := &Balance{Value: -10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"} + newMb := &Balance{Value: -10, Weight: 20, DestinationId: "NAT"} a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, Balance: newMb} ub.debitBalanceAction(a) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 || ub.BalanceMap[MINUTES+OUTBOUND][0].Value != 25 { @@ -739,7 +745,7 @@ func TestUserBalanceAddMinuteNil(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, } ub.debitBalanceAction(nil) if len(ub.BalanceMap[MINUTES+OUTBOUND]) != 2 { @@ -772,7 +778,7 @@ func TestUserBalanceAddMinutBucketEmpty(t *testing.T) { func TestUserBalanceExecuteTriggeredActions(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: TRIGGER_MAX_COUNTER, ActionsId: "TEST_ACTIONS"}}, } @@ -796,7 +802,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, SpecialPrice: 1, DestinationId: "NAT"}, &Balance{Weight: 10, SpecialPrice: 10, SpecialPriceType: PRICE_ABSOLUTE, DestinationId: "RET"}}}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}, MINUTES + OUTBOUND: BalanceChain{&Balance{Value: 10, Weight: 20, DestinationId: "NAT"}, &Balance{Weight: 10, DestinationId: "RET"}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: TRIGGER_MIN_COUNTER, ActionsId: "TEST_ACTIONS"}}, } @@ -887,19 +893,22 @@ func TestUserBalanceUnitCountingOutboundInbound(t *testing.T) { func BenchmarkGetSecondForPrefix(b *testing.B) { b.StopTimer() - b1 := &Balance{Value: 10, SpecialPrice: 10, Weight: 10, DestinationId: "NAT"} - b2 := &Balance{Value: 100, SpecialPrice: 1, Weight: 20, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} ub1 := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + cd := &CallDescriptor{ + Destination: "0723", + } b.StartTimer() for i := 0; i < b.N; i++ { - ub1.getSecondsForPrefix("0723") + ub1.getSecondsForPrefix(cd) } } func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { - b1 := &Balance{Value: 10, Weight: 10, SpecialPrice: 0.01, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, SpecialPrice: 0.0, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { storageGetter.SetUserBalance(rifsBalance) @@ -911,7 +920,10 @@ func BenchmarkGetSecondsForPrefix(b *testing.B) { b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} + cd := &CallDescriptor{ + Destination: "0723", + } for i := 0; i < b.N; i++ { - ub1.getSecondsForPrefix("0723") + ub1.getSecondsForPrefix(cd) } } From 7632a8f14ec503c985fe367ca12d10242ff345cc Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Oct 2013 16:18:28 +0300 Subject: [PATCH 36/39] tests passing on get max session time --- apier/v1/tpactions.go | 10 ++++---- engine/balances.go | 18 ++++++++++---- engine/callcost.go | 2 +- engine/callcost_test.go | 8 +++---- engine/calldesc_test.go | 2 +- engine/storage_sql.go | 8 +++---- engine/userbalance.go | 38 +++++++++--------------------- engine/userbalance_test.go | 26 ++++++++++++++++---- sessionmanager/fssessionmanager.go | 2 +- sessionmanager/session.go | 2 +- utils/apitpdata.go | 20 ++++++++-------- 11 files changed, 72 insertions(+), 64 deletions(-) diff --git a/apier/v1/tpactions.go b/apier/v1/tpactions.go index b2cde95c2..15ee69612 100644 --- a/apier/v1/tpactions.go +++ b/apier/v1/tpactions.go @@ -51,12 +51,12 @@ func (self *ApierV1) SetTPActions(attrs utils.TPActions, reply *string) error { BalanceId: act.BalanceType, Direction: act.Direction, ExpirationString: act.ExpiryTime, + ExtraParameters: act.ExtraParameters, Balance: &engine.Balance{ - Value: act.Units, - DestinationId: act.DestinationId, - SpecialPriceType: act.RateType, - SpecialPrice: act.Rate, - Weight: act.MinutesWeight, + Value: act.Units, + DestinationId: act.DestinationId, + RateSubject: act.RateSubject, + Weight: act.BalanceWeight, }, Weight: act.Weight, } diff --git a/engine/balances.go b/engine/balances.go index d138ff6c1..88658ad00 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -64,22 +64,30 @@ func (b *Balance) Clone() *Balance { // Returns the available number of seconds for a specified credit func (b *Balance) GetSecondsForCredit(cd *CallDescriptor, credit float64) (seconds float64) { seconds = b.Value - // TODO: fix this cc, err := b.GetCost(cd) if err != nil { Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) return 0 } if cc.Cost > 0 { - seconds = math.Min(credit/cc.Cost, b.Value) + secondCost := cc.Cost / cc.GetDuration().Seconds() + // TODO: this is not very accurate + // we should iterate timespans and increment to get exact number of minutes for + // available credit + seconds = math.Min(credit/secondCost, b.Value) } return } func (b *Balance) GetCost(cd *CallDescriptor) (*CallCost, error) { - cd.Subject = b.RateSubject - cd.Account = cd.Subject - return cd.GetCost() + if b.RateSubject != "" { + cd.Subject = b.RateSubject + cd.Account = cd.Subject + return cd.GetCost() + } + cc := cd.CreateCallCost() + cc.Cost = 0 + return cc, nil } /* diff --git a/engine/callcost.go b/engine/callcost.go index 778b54e53..3ac52e29a 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -67,7 +67,7 @@ func (cc *CallCost) GetStartTime() time.Time { return cc.Timespans[0].TimeStart } -func (cc *CallCost) GetTotalDuration() (td time.Duration) { +func (cc *CallCost) GetDuration() (td time.Duration) { for _, ts := range cc.Timespans { td += ts.GetDuration() } diff --git a/engine/callcost_test.go b/engine/callcost_test.go index 0c2d49328..afd85e8b6 100644 --- a/engine/callcost_test.go +++ b/engine/callcost_test.go @@ -128,8 +128,8 @@ func TestMultipleInputRightMerge(t *testing.T) { func TestCallCostGetDurationZero(t *testing.T) { cc := &CallCost{} - if cc.GetTotalDuration().Seconds() != 0 { - t.Error("Wrong call cost duration for zero timespans: ", cc.GetTotalDuration()) + if cc.GetDuration().Seconds() != 0 { + t.Error("Wrong call cost duration for zero timespans: ", cc.GetDuration()) } } @@ -146,7 +146,7 @@ func TestCallCostGetDuration(t *testing.T) { }, }, } - if cc.GetTotalDuration().Seconds() != 90 { - t.Error("Wrong call cost duration: ", cc.GetTotalDuration()) + if cc.GetDuration().Seconds() != 90 { + t.Error("Wrong call cost duration: ", cc.GetDuration()) } } diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 0fd7c02ed..5823f5e7e 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -50,7 +50,7 @@ func populateDB() { Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{ - &Balance{Value: 20, DestinationId: "NAT", Weight: 10}, + &Balance{Value: 20, DestinationId: "NAT", Weight: 10, RateSubject: "rif"}, &Balance{Value: 100, DestinationId: "RET", Weight: 20}, }}, } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index c8cc00c5b..eed027380 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -561,12 +561,12 @@ func (self *SQLStorage) GetTPActions(tpid, actsId string) (*utils.TPActions, err i := 0 for rows.Next() { i++ //Keep here a reference so we know we got at least one result - var action, balanceId, dir, destId, rateType, expTime string - var units, rate, minutesWeight, weight float64 - if err = rows.Scan(&action, &balanceId, &dir, &units, &expTime, &destId, &rateType, &rate, &minutesWeight, &weight); err != nil { + var action, balanceId, dir, destId, rateSubject, expTime, extraParameters string + var units, balanceWeight, weight float64 + if err = rows.Scan(&action, &balanceId, &dir, &units, &expTime, &destId, &rateSubject, &balanceWeight, &extraParameters, &weight); err != nil { return nil, err } - acts.Actions = append(acts.Actions, utils.Action{action, balanceId, dir, units, expTime, destId, rateType, rate, minutesWeight, weight}) + acts.Actions = append(acts.Actions, utils.Action{action, balanceId, dir, units, expTime, destId, rateSubject, balanceWeight, extraParameters, weight}) } if i == 0 { return nil, nil diff --git a/engine/userbalance.go b/engine/userbalance.go index 0972455cf..e8e0f0ac5 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -65,35 +65,15 @@ type UserBalance struct { BalanceMap map[string]BalanceChain UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList - - Groups GroupLinks // user info about groups + Groups GroupLinks // user info about groups // group information UserIds []string // group info about users } // Returns user's available minutes for the specified destination func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit float64, balances BalanceChain) { - credit = ub.getBalanceForPrefix(cd.Destination, ub.BalanceMap[CREDIT+cd.Direction]).GetTotalValue() - if len(ub.BalanceMap[MINUTES+cd.Direction]) == 0 { - // Logger.Debug("There are no minute buckets to check for user: ", ub.Id) - return - } - for _, b := range ub.BalanceMap[MINUTES+cd.Direction] { - if b.IsExpired() { - continue - } - precision, err := storageGetter.DestinationContainsPrefix(b.DestinationId, cd.Destination) - if err != nil { - continue - } - if precision > 0 { - b.precision = precision - if b.Value > 0 { - balances = append(balances, b) - } - } - } - balances.Sort() // sorts the buckets according to priority, precision or price + credit = ub.getBalancesForPrefix(cd.Destination, ub.BalanceMap[CREDIT+cd.Direction]).GetTotalValue() + balances = ub.getBalancesForPrefix(cd.Destination, ub.BalanceMap[MINUTES+cd.Direction]) for _, b := range balances { s := b.GetSecondsForCredit(cd, credit) cc, err := b.GetCost(cd) @@ -101,7 +81,11 @@ func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit Logger.Err(fmt.Sprintf("Error getting new cost for balance subject: %v", err)) continue } - credit -= s * cc.Cost + if cc.Cost > 0 && cc.GetDuration() > 0 { + // TODO: fix this + secondCost := cc.Cost / cc.GetDuration().Seconds() + credit -= s * secondCost + } seconds += s } return @@ -140,7 +124,7 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { return nil //ub.BalanceMap[id].GetTotalValue() } -func (ub *UserBalance) getBalanceForPrefix(prefix string, balances BalanceChain) BalanceChain { +func (ub *UserBalance) getBalancesForPrefix(prefix string, balances BalanceChain) BalanceChain { var usefulBalances BalanceChain for _, b := range balances { if b.IsExpired() || (ub.Type != UB_TYPE_POSTPAID && b.Value <= 0) { @@ -170,8 +154,8 @@ This method is the core of userbalance debiting: don't panic just follow the bra func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { minuteBalances := ub.BalanceMap[MINUTES+cc.Direction] moneyBalances := ub.BalanceMap[CREDIT+cc.Direction] - usefulMinuteBalances := ub.getBalanceForPrefix(cc.Destination, minuteBalances) - usefulMoneyBalances := ub.getBalanceForPrefix(cc.Destination, moneyBalances) + usefulMinuteBalances := ub.getBalancesForPrefix(cc.Destination, minuteBalances) + usefulMoneyBalances := ub.getBalancesForPrefix(cc.Destination, moneyBalances) // debit connect fee if cc.ConnectFee > 0 { amount := cc.ConnectFee diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index ca601022d..b41816160 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -112,26 +112,42 @@ func TestGetSecondsForPrefix(t *testing.T) { b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} cd := &CallDescriptor{ - Destination: "0723", + TOR: "0", + Tenant: "vdf", + TimeStart: time.Date(2013, 10, 4, 15, 46, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 4, 15, 46, 10, 0, time.UTC), + LoopIndex: 0, + CallDuration: 10 * time.Second, + Direction: OUTBOUND, + Destination: "0723", } seconds, credit, bucketList := ub1.getSecondsForPrefix(cd) expected := 110.0 if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { + t.Log(seconds, credit, bucketList) t.Errorf("Expected %v was %v", expected, seconds) } } func TestGetSpecialPricedSeconds(t *testing.T) { - b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT"} - b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET"} + b1 := &Balance{Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: "minu"} + b2 := &Balance{Value: 100, Weight: 20, DestinationId: "RET", RateSubject: "minu"} ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]BalanceChain{MINUTES + OUTBOUND: BalanceChain{b1, b2}, CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} cd := &CallDescriptor{ - Destination: "0723", + TOR: "0", + Tenant: "vdf", + TimeStart: time.Date(2013, 10, 4, 15, 46, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 10, 4, 15, 46, 10, 0, time.UTC), + LoopIndex: 0, + CallDuration: 10 * time.Second, + Direction: OUTBOUND, + Destination: "0723", } seconds, credit, bucketList := ub1.getSecondsForPrefix(cd) expected := 21.0 if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight { + t.Log(seconds, credit, bucketList) t.Errorf("Expected %v was %v", expected, seconds) } } @@ -510,7 +526,7 @@ func TestDebitCreditNoConectFeeCredit(t *testing.T) { t.Error("Error debiting balance: ", err) } - if len(cc.Timespans) != 0 || cc.GetTotalDuration() != 0 { + if len(cc.Timespans) != 0 || cc.GetDuration() != 0 { t.Error("Error cutting at no connect fee: ", cc.Timespans) } } diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 283826422..987aaf72f 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -307,7 +307,7 @@ func (sm *FSSessionManager) LoopAction(s *Session, cd *engine.CallDescriptor, in s.sessionManager.DisconnectSession(s, SYSTEM_ERROR) } engine.Logger.Debug(fmt.Sprintf("Result of MaxDebit call: %v", cc)) - if cc.GetTotalDuration() == 0 || err != nil { + if cc.GetDuration() == 0 || err != nil { engine.Logger.Info(fmt.Sprintf("No credit left: Disconnect %v", s)) sm.DisconnectSession(s, INSUFFICIENT_FUNDS) return diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 3e5cadb20..c527ebc63 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -87,7 +87,7 @@ func (s *Session) startDebitLoop() { } nextCd.TimeEnd = nextCd.TimeStart.Add(s.sessionManager.GetDebitPeriod()) cc := s.sessionManager.LoopAction(s, &nextCd, index) - time.Sleep(cc.GetTotalDuration()) + time.Sleep(cc.GetDuration()) index++ } } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index fcd020c46..37fe35ac9 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -91,16 +91,16 @@ type TPActions struct { } type Action struct { - Identifier string // Identifier mapped in the code - BalanceType string // Type of balance the action will operate on - Direction string // Balance direction - Units float64 // Number of units to add/deduct - ExpiryTime string // Time when the units will expire - DestinationId string // Destination profile id - RateType string // Type of rate <*absolute|*percent> - Rate float64 // Price value - MinutesWeight float64 // Minutes weight - Weight float64 // Action's weight + Identifier string // Identifier mapped in the code + BalanceType string // Type of balance the action will operate on + Direction string // Balance direction + Units float64 // Number of units to add/deduct + ExpiryTime string // Time when the units will expire + DestinationId string // Destination profile id + RateSubject string // Type of rate <*absolute|*percent> + BalanceWeight float64 // Balance weight + ExtraParameters string + Weight float64 // Action's weight } type ApiTPActionTimings struct { From 85027f07644885ce1a8dab19434d06ffcac16ae9 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Oct 2013 22:13:37 +0300 Subject: [PATCH 37/39] new subject for monetary balances --- engine/timespans.go | 57 +++++++++++++-- engine/timespans_test.go | 153 ++++++++++++++++++++++++++++++++++----- engine/userbalance.go | 89 ++++++++++++++++++----- 3 files changed, 255 insertions(+), 44 deletions(-) diff --git a/engine/timespans.go b/engine/timespans.go index 2d47da876..a10986944 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -52,6 +52,17 @@ type MinuteInfo struct { Price float64 } +func (incr *Increment) Clone() *Increment { + return &Increment{ + Duration: incr.Duration, + Cost: incr.Cost, + BalanceUuid: incr.BalanceUuid, + BalanceType: incr.BalanceType, + BalanceRateInterval: incr.BalanceRateInterval, + MinuteInfo: incr.MinuteInfo, + } +} + type Increments []*Increment func (incs Increments) GetTotalCost() float64 { @@ -184,13 +195,47 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) { return } -// Split the interval at the given increment start -func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan { - timeStart := ts.GetTimeStartForIncrement(index, increment) +// Split the timespan at the given increment start +func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan { + if index <= 0 || index >= len(ts.Increments) { + return nil + } + timeStart := ts.GetTimeStartForIncrement(index) newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd} newTs.CallDuration = ts.CallDuration ts.TimeEnd = timeStart - ts.Increments = ts.Increments[0:index] + newTs.Increments = ts.Increments[index:] + ts.Increments = ts.Increments[:index] + ts.SetNewCallDuration(newTs) + return newTs +} + +// Split the timespan at the given second +func (ts *TimeSpan) SplitByDuration(duration time.Duration) *TimeSpan { + if duration <= 0 || duration >= ts.GetDuration() { + return nil + } + timeStart := ts.TimeStart.Add(duration) + newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd} + newTs.CallDuration = ts.CallDuration + ts.TimeEnd = timeStart + // split the increment + for incrIndex, incr := range ts.Increments { + if duration-incr.Duration >= 0 { + duration -= incr.Duration + } else { + + splitIncrement := ts.Increments[incrIndex].Clone() + splitIncrement.Duration -= duration + ts.Increments[incrIndex].Duration = duration + newTs.Increments = Increments{splitIncrement} + if incrIndex < len(ts.Increments)-1 { + newTs.Increments = append(newTs.Increments, ts.Increments[incrIndex+1:]...) + } + ts.Increments = ts.Increments[:incrIndex+1] + break + } + } ts.SetNewCallDuration(newTs) return newTs } @@ -230,8 +275,8 @@ func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) { } // returns a time for the specified second in the time span -func (ts *TimeSpan) GetTimeStartForIncrement(index int, increment *Increment) time.Time { - return ts.TimeStart.Add(time.Duration(int64(index) * increment.Duration.Nanoseconds())) +func (ts *TimeSpan) GetTimeStartForIncrement(index int) time.Time { + return ts.TimeStart.Add(time.Duration(int64(index) * ts.Increments[0].Duration.Nanoseconds())) } func (ts *TimeSpan) RoundToDuration(duration time.Duration) { diff --git a/engine/timespans_test.go b/engine/timespans_test.go index fbeaab1d2..e330bcf75 100644 --- a/engine/timespans_test.go +++ b/engine/timespans_test.go @@ -19,7 +19,7 @@ along with this program. If not, see package engine import ( - //"github.com/cgrates/cgrates/utils" + "github.com/cgrates/cgrates/utils" "testing" "time" ) @@ -450,7 +450,6 @@ func TestTimespanExpandingPastEnd(t *testing.T) { } } -/* func TestTimespanExpandingCallDuration(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -468,11 +467,11 @@ func TestTimespanExpandingCallDuration(t *testing.T) { cd := &CallDescriptor{} timespans = cd.roundTimeSpansToIncrement(timespans) - if timespans[0].CallDuration != time.Minute { + if len(timespans) != 1 || timespans[0].GetDuration() != time.Minute { t.Error("Error setting call duration: ", timespans[0]) } } -*/ + func TestTimespanExpandingRoundingPastEnd(t *testing.T) { timespans := []*TimeSpan{ &TimeSpan{ @@ -626,8 +625,7 @@ func TestTimespanCreateSecondsSlice(t *testing.T) { } } -/* -func TestTimespanCreateSecondsFract(t *testing.T) { +func TestTimespanCreateIncrements(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 100000000, time.UTC), @@ -635,15 +633,18 @@ func TestTimespanCreateSecondsFract(t *testing.T) { RoundingMethod: utils.ROUNDING_MIDDLE, RoundingDecimals: 2, Rates: RateGroups{ - &Rate{Value: 2.0}, + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, }, }, } ts.createIncrementsSlice() - if len(ts.Increments) != 31 { - t.Error("Error creating second slice: ", ts.Increments) + if len(ts.Increments) != 3 { + t.Error("Error creating increment slice: ", len(ts.Increments)) } - if len(ts.Increments) < 31 || ts.Increments[30].Cost != 0.2 { + if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20 { t.Error("Wrong second slice: ", ts.Increments) } } @@ -651,16 +652,130 @@ func TestTimespanCreateSecondsFract(t *testing.T) { func TestTimespanSplitByIncrement(t *testing.T) { ts := &TimeSpan{ TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), - TimeEnd: time.Date(2013, 9, 19, 18, 30, 30, 0, time.UTC), - CallDuration: 50 * time.Second, + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, } - i := &Increment{Duration: time.Second} - newTs := ts.SplitByIncrement(5, i) - if ts.GetDuration() != 5*time.Second || newTs.GetDuration() != 25*time.Second { - t.Error("Error spliting by second: ", ts.GetDuration(), newTs.GetDuration()) + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) } - if ts.CallDuration != 25*time.Second || newTs.CallDuration != 50*time.Second { - t.Error("Error spliting by second at setting call duration: ", ts.GetDuration(), newTs.GetDuration()) + newTs := ts.SplitByIncrement(5) + if ts.GetDuration() != 50*time.Second || newTs.GetDuration() != 10*time.Second { + t.Error("Error spliting by increment: ", ts.GetDuration(), newTs.GetDuration()) + } + if ts.CallDuration != 50*time.Second || newTs.CallDuration != 60*time.Second { + t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration, newTs.CallDuration) + } + if len(ts.Increments) != 5 || len(newTs.Increments) != 1 { + t.Error("Error spliting increments: ", ts.Increments, newTs.Increments) + } +} + +func TestTimespanSplitByIncrementStart(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByIncrement(0) + if ts.GetDuration() != 60*time.Second || newTs != nil { + t.Error("Error spliting by increment: ", ts.GetDuration()) + } + if ts.CallDuration != 60*time.Second { + t.Error("Error spliting by incrementat setting call duration: ", ts.CallDuration) + } + if len(ts.Increments) != 6 { + t.Error("Error spliting increments: ", ts.Increments) + } +} + +func TestTimespanSplitByIncrementEnd(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByIncrement(6) + if ts.GetDuration() != 60*time.Second || newTs != nil { + t.Error("Error spliting by increment: ", ts.GetDuration()) + } + if ts.CallDuration != 60*time.Second { + t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration) + } + if len(ts.Increments) != 6 { + t.Error("Error spliting increments: ", ts.Increments) + } +} + +func TestTimespanSplitByDuration(t *testing.T) { + ts := &TimeSpan{ + TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC), + TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC), + CallDuration: 60 * time.Second, + RateInterval: &RateInterval{ + RoundingMethod: utils.ROUNDING_MIDDLE, + RoundingDecimals: 2, + Rates: RateGroups{ + &Rate{ + Value: 2.0, + RateIncrement: 10 * time.Second, + }, + }, + }, + } + ts.createIncrementsSlice() + if len(ts.Increments) != 6 { + t.Error("Error creating increment slice: ", len(ts.Increments)) + } + newTs := ts.SplitByDuration(46 * time.Second) + if ts.GetDuration() != 46*time.Second || newTs.GetDuration() != 14*time.Second { + t.Error("Error spliting by duration: ", ts.GetDuration(), newTs.GetDuration()) + } + if ts.CallDuration != 46*time.Second || newTs.CallDuration != 60*time.Second { + t.Error("Error spliting by duration at setting call duration: ", ts.CallDuration, newTs.CallDuration) + } + if len(ts.Increments) != 5 || len(newTs.Increments) != 2 { + t.Error("Error spliting increments: ", ts.Increments, newTs.Increments) + } + if ts.Increments[4].Duration != 6*time.Second || newTs.Increments[0].Duration != 4*time.Second { + t.Error("Error spliting increment: ", ts.Increments[4], newTs.Increments[0]) } } -*/ diff --git a/engine/userbalance.go b/engine/userbalance.go index e8e0f0ac5..565d239c1 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -82,7 +82,7 @@ func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit continue } if cc.Cost > 0 && cc.GetDuration() > 0 { - // TODO: fix this + // TODO: improve this secondCost := cc.Cost / cc.GetDuration().Seconds() credit -= s * secondCost } @@ -181,9 +181,9 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ { ts := cc.Timespans[tsIndex] ts.createIncrementsSlice() - tsWasSplited := false + tsWasSplit := false for incrementIndex, increment := range ts.Increments { - if tsWasSplited { + if tsWasSplit { break } paid := false @@ -208,7 +208,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { newTs := ts if incrementIndex != 0 { // if increment it's not at the begining we must split the timespan - newTs = ts.SplitByIncrement(incrementIndex, increment) + newTs = ts.SplitByIncrement(incrementIndex) } newTs.RoundToDuration(time.Minute) newTs.RateInterval = &RateInterval{ @@ -236,7 +236,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { cc.Timespans = append(cc.Timespans, nil) copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) cc.Timespans[tsIndex] = newTs - tsWasSplited = true + tsWasSplit = true } var newTimespans []*TimeSpan @@ -260,7 +260,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { // newTs.SplitByIncrement() // get the new rate cd := cc.CreateCallDescriptor() - cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration newCC, err := b.GetCost(cd) @@ -269,25 +269,76 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { continue } //debit new callcost + var paidTs []*TimeSpan for _, nts := range newCC.Timespans { - for _, nIncrement := range nts.Increments { + paidTs = append(paidTs, nts) + for nIdx, nInc := range nts.Increments { // debit minutes and money - _ = nIncrement + amount := nInc.Cost + if b.Value >= amount { + b.Value -= amount + nInc.BalanceUuid = b.Uuid + if count { + ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}}) + } + } else { + nts.SplitByIncrement(nIdx) + } } } + // calculate overlaped timespans + var paidDuration time.Duration + for _, pts := range paidTs { + paidDuration += pts.GetDuration() + } + if paidDuration > 0 { + // split from current increment + newTs := ts.SplitByIncrement(incrementIndex) + remainingTs := []*TimeSpan{newTs} + + for tsi := tsIndex + 1; tsi < len(cc.Timespans); tsi++ { + remainingTs = append(remainingTs, cc.Timespans[tsi]) + } + for remainingIndex, rts := range remainingTs { + if paidDuration >= rts.GetDuration() { + paidDuration -= rts.GetDuration() + } else { + if paidDuration > 0 { + // this ts was not fully paid + fragment := rts.SplitByDuration(paidDuration) + paidTs = append(paidTs, fragment) + } + // delete from tsIndex to current + cc.Timespans = append(cc.Timespans[:tsIndex], cc.Timespans[remainingIndex:]...) + break + } + } + + // append the timpespans to outer timespans + for _, pts := range paidTs { + tsIndex++ + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) + cc.Timespans[tsIndex] = pts + } + paid = true + tsWasSplit = true + } } if paid { continue } else { // Split if some increments were processed by minutes if incrementIndex > 0 && ts.Increments[incrementIndex-1].MinuteInfo != nil { - newTs := ts.SplitByIncrement(incrementIndex, increment) - idx := tsIndex + 1 - cc.Timespans = append(cc.Timespans, nil) - copy(cc.Timespans[idx+1:], cc.Timespans[idx:]) - cc.Timespans[idx] = newTs - newTs.createIncrementsSlice() - tsWasSplited = true + newTs := ts.SplitByIncrement(incrementIndex) + if newTs != nil { + idx := tsIndex + 1 + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[idx+1:], cc.Timespans[idx:]) + cc.Timespans[idx] = newTs + newTs.createIncrementsSlice() + tsWasSplit = true + } break } } @@ -308,7 +359,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } else { // get the new rate cd := cc.CreateCallDescriptor() - cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment) + cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration newCC, err := b.GetCost(cd) @@ -326,11 +377,11 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } if !paid { // no balance was attached to this increment: cut the rest of increments/timespans - ts.SplitByIncrement(incrementIndex, increment) - if len(ts.Increments) == 0 { - // if there are no increments left in the ts leav it out + if incrementIndex == 0 { + // if we are right at the begining in the ts leave it out cc.Timespans = cc.Timespans[:tsIndex] } else { + ts.SplitByIncrement(incrementIndex) cc.Timespans = cc.Timespans[:tsIndex+1] } return nil From 33f24534b30a77fe184ab9d797f8810dafee9998 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 4 Oct 2013 22:15:25 +0300 Subject: [PATCH 38/39] finished split by increment refactoring --- sessionmanager/fssessionmanager.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 987aaf72f..0492967d5 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -253,16 +253,14 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { tsDuration := ts.GetDuration() if refoundDuration <= tsDuration { lastRefoundedIncrementIndex := 0 - var lastRefoundedIncrement *engine.Increment for incrementIndex, increment := range ts.Increments { if increment.Duration <= refoundDuration { refoundIncrements = append(refoundIncrements, increment) refoundDuration -= increment.Duration lastRefoundedIncrementIndex = incrementIndex - lastRefoundedIncrement = increment } } - ts.SplitByIncrement(lastRefoundedIncrementIndex, lastRefoundedIncrement) + ts.SplitByIncrement(lastRefoundedIncrementIndex) break // do not go to other timespans } else { refoundIncrements = append(refoundIncrements, ts.Increments...) From f5059e55faacb9f3af20ba0ae954268399174f8b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Sun, 6 Oct 2013 11:21:16 +0300 Subject: [PATCH 39/39] started minute new subject --- engine/userbalance.go | 60 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/engine/userbalance.go b/engine/userbalance.go index 565d239c1..8288ddf67 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -257,7 +257,6 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { break } } - // newTs.SplitByIncrement() // get the new rate cd := cc.CreateCallDescriptor() cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex) @@ -274,9 +273,10 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { paidTs = append(paidTs, nts) for nIdx, nInc := range nts.Increments { // debit minutes and money + seconds := nInc.Duration.Seconds() amount := nInc.Cost - if b.Value >= amount { - b.Value -= amount + if b.Value >= seconds { + b.Value -= seconds nInc.BalanceUuid = b.Uuid if count { ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}}) @@ -368,11 +368,61 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { continue } //debit new callcost + var paidTs []*TimeSpan for _, nts := range newCC.Timespans { - for _, nIncrement := range nts.Increments { - _ = nIncrement + paidTs = append(paidTs, nts) + for nIdx, nInc := range nts.Increments { + // debit money + amount := nInc.Cost + if b.Value >= amount { + b.Value -= amount + nInc.BalanceUuid = b.Uuid + if count { + ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}}) + } + } else { + nts.SplitByIncrement(nIdx) + } } } + // calculate overlaped timespans + var paidDuration time.Duration + for _, pts := range paidTs { + paidDuration += pts.GetDuration() + } + if paidDuration > 0 { + // split from current increment + newTs := ts.SplitByIncrement(incrementIndex) + remainingTs := []*TimeSpan{newTs} + + for tsi := tsIndex + 1; tsi < len(cc.Timespans); tsi++ { + remainingTs = append(remainingTs, cc.Timespans[tsi]) + } + for remainingIndex, rts := range remainingTs { + if paidDuration >= rts.GetDuration() { + paidDuration -= rts.GetDuration() + } else { + if paidDuration > 0 { + // this ts was not fully paid + fragment := rts.SplitByDuration(paidDuration) + paidTs = append(paidTs, fragment) + } + // delete from tsIndex to current + cc.Timespans = append(cc.Timespans[:tsIndex], cc.Timespans[remainingIndex:]...) + break + } + } + + // append the timpespans to outer timespans + for _, pts := range paidTs { + tsIndex++ + cc.Timespans = append(cc.Timespans, nil) + copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:]) + cc.Timespans[tsIndex] = pts + } + paid = true + tsWasSplit = true + } } } if !paid {