diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index add9e9b97..620b64c48 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -426,7 +426,7 @@ func (self *ApierV1) modifyBalance(aType string, attr *AttrAddBalance, reply *st Uuid: attr.BalanceUuid, ID: attr.BalanceId, Type: utils.StringPointer(attr.BalanceType), - Value: utils.Float64Pointer(attr.Value), + Value: &utils.ValueFormula{Static: attr.Value}, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Weight: attr.Weight, @@ -514,7 +514,6 @@ func (self *ApierV1) SetBalance(attr *AttrSetBalance, reply *string) error { Uuid: attr.BalanceUUID, ID: attr.BalanceID, Type: utils.StringPointer(attr.BalanceType), - Value: attr.Value, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Weight: attr.Weight, @@ -522,6 +521,9 @@ func (self *ApierV1) SetBalance(attr *AttrSetBalance, reply *string) error { Disabled: attr.Disabled, }, } + if attr.Value != nil { + a.Balance.Value = &utils.ValueFormula{Static: *attr.Value} + } if attr.Directions != nil { a.Balance.Directions = utils.StringMapPointer(utils.ParseStringMap(*attr.Directions)) } @@ -572,7 +574,6 @@ func (self *ApierV1) RemoveBalances(attr *AttrSetBalance, reply *string) error { Uuid: attr.BalanceUUID, ID: attr.BalanceID, Type: utils.StringPointer(attr.BalanceType), - Value: attr.Value, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Weight: attr.Weight, @@ -580,6 +581,9 @@ func (self *ApierV1) RemoveBalances(attr *AttrSetBalance, reply *string) error { Disabled: attr.Disabled, }, } + if attr.Value != nil { + a.Balance.Value = &utils.ValueFormula{Static: *attr.Value} + } if attr.Directions != nil { a.Balance.Directions = utils.StringMapPointer(utils.ParseStringMap(*attr.Directions)) } diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 95fba7081..cf3f86389 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -504,10 +504,10 @@ func (self *ApierV1) SetActions(attrs utils.AttrSetActions, reply *string) error } storeActions := make(engine.Actions, len(attrs.Actions)) for idx, apiAct := range attrs.Actions { - var units *float64 + var vf *utils.ValueFormula if apiAct.Units != "" { - if x, err := strconv.ParseFloat(apiAct.Units, 64); err == nil { - units = &x + if x, err := utils.ParseBalanceFilterValue(apiAct.Units); err == nil { + vf = x } else { return err } @@ -533,7 +533,7 @@ func (self *ApierV1) SetActions(attrs utils.AttrSetActions, reply *string) error Uuid: utils.StringPointer(utils.GenUUID()), ID: utils.StringPointer(apiAct.BalanceId), Type: utils.StringPointer(apiAct.BalanceType), - Value: units, + Value: vf, Weight: weight, Directions: utils.StringMapPointer(utils.ParseStringMap(apiAct.Directions)), DestinationIDs: utils.StringMapPointer(utils.ParseStringMap(apiAct.DestinationIds)), diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index 653382ced..c342e56ba 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -22,6 +22,7 @@ import ( "flag" "fmt" "log" + _ "net/http/pprof" "os" "runtime" "runtime/pprof" @@ -507,6 +508,10 @@ func main() { } defer accountDb.Close() engine.SetAccountingStorage(accountDb) + if err := engine.CheckVersion(); err != nil { + fmt.Println(err.Error()) + return + } } if cfg.RALsEnabled || cfg.CDRSEnabled || cfg.SchedulerEnabled { // Only connect to storDb if necessary logDb, err = engine.ConfigureLogStorage(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort, diff --git a/cmd/cgr-engine/rater.go b/cmd/cgr-engine/rater.go index 0c3f90021..8fe1ee27d 100644 --- a/cmd/cgr-engine/rater.go +++ b/cmd/cgr-engine/rater.go @@ -211,7 +211,6 @@ func startRater(internalRaterChan chan rpcclient.RpcClientConnection, cacheDoneC utils.RegisterRpcParams("PubSubV1", &engine.PubSub{}) utils.RegisterRpcParams("AliasesV1", &engine.AliasHandler{}) utils.RegisterRpcParams("UsersV1", &engine.UserMap{}) - utils.RegisterRpcParams("UsersV1", &engine.UserMap{}) utils.RegisterRpcParams("", &v1.CdrsV1{}) utils.RegisterRpcParams("", &v2.CdrsV2{}) utils.RegisterRpcParams("", &v1.SessionManagerV1{}) @@ -219,6 +218,6 @@ func startRater(internalRaterChan chan rpcclient.RpcClientConnection, cacheDoneC utils.RegisterRpcParams("", responder) utils.RegisterRpcParams("", apierRpcV1) utils.RegisterRpcParams("", apierRpcV2) - + utils.GetRpcParams("") internalRaterChan <- responder // Rater done } diff --git a/cmd/cgr-loader/cgr-loader.go b/cmd/cgr-loader/cgr-loader.go index 7f26375fb..3ac60d57c 100644 --- a/cmd/cgr-loader/cgr-loader.go +++ b/cmd/cgr-loader/cgr-loader.go @@ -92,77 +92,107 @@ func main() { var rater, cdrstats, users *rpc.Client var loader engine.LoadReader if *migrateRC8 != "" { - var db_nb int - db_nb, err = strconv.Atoi(*datadb_name) - if err != nil { - log.Print("Redis db name must be an integer!") - return - } - host := *datadb_host - if *datadb_port != "" { - host += ":" + *datadb_port - } - migratorRC8acc, err := NewMigratorRC8(host, db_nb, *datadb_pass, *dbdata_encoding) - if err != nil { - log.Print(err.Error()) - return - } - if strings.Contains(*migrateRC8, "acc") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8acc.migrateAccounts(); err != nil { + if *datadb_type == "redis" && *tpdb_type == "redis" { + var db_nb int + db_nb, err = strconv.Atoi(*datadb_name) + if err != nil { + log.Print("Redis db name must be an integer!") + return + } + host := *datadb_host + if *datadb_port != "" { + host += ":" + *datadb_port + } + migratorRC8acc, err := NewMigratorRC8(host, db_nb, *datadb_pass, *dbdata_encoding) + if err != nil { log.Print(err.Error()) + return + } + if strings.Contains(*migrateRC8, "acc") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8acc.migrateAccounts(); err != nil { + log.Print(err.Error()) + } + } + + db_nb, err = strconv.Atoi(*tpdb_name) + if err != nil { + log.Print("Redis db name must be an integer!") + return + } + host = *tpdb_host + if *tpdb_port != "" { + host += ":" + *tpdb_port + } + migratorRC8rat, err := NewMigratorRC8(host, db_nb, *tpdb_pass, *dbdata_encoding) + if err != nil { + log.Print(err.Error()) + return + } + if strings.Contains(*migrateRC8, "atr") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateActionTriggers(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "act") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateActions(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "dcs") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateDerivedChargers(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "apl") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateActionPlans(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "shg") || strings.Contains(*migrateRC8, "*all") { + if err := migratorRC8rat.migrateSharedGroups(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "int") { + if err := migratorRC8acc.migrateAccountsInt(); err != nil { + log.Print(err.Error()) + } + if err := migratorRC8rat.migrateActionTriggersInt(); err != nil { + log.Print(err.Error()) + } + if err := migratorRC8rat.migrateActionsInt(); err != nil { + log.Print(err.Error()) + } + } + if strings.Contains(*migrateRC8, "vf") { + if err := migratorRC8rat.migrateActionsInt2(); err != nil { + log.Print(err.Error()) + } + if err := migratorRC8acc.writeVersion(); err != nil { + log.Print(err.Error()) + } + } + } else if *datadb_type == "mongo" && *tpdb_type == "mongo" { + mongoMigratorAcc, err := NewMongoMigrator(*datadb_host, *datadb_port, *datadb_name, *datadb_user, *datadb_pass) + if err != nil { + log.Print(err.Error()) + return + } + mongoMigratorRat, err := NewMongoMigrator(*tpdb_host, *tpdb_port, *tpdb_name, *tpdb_user, *tpdb_pass) + if err != nil { + log.Print(err.Error()) + return + } + if strings.Contains(*migrateRC8, "vf") { + if err := mongoMigratorRat.migrateActions(); err != nil { + log.Print(err.Error()) + } + if err := mongoMigratorAcc.writeVersion(); err != nil { + log.Print(err.Error()) + } } } - db_nb, err = strconv.Atoi(*tpdb_name) - if err != nil { - log.Print("Redis db name must be an integer!") - return - } - host = *tpdb_host - if *tpdb_port != "" { - host += ":" + *tpdb_port - } - migratorRC8rat, err := NewMigratorRC8(host, db_nb, *tpdb_pass, *dbdata_encoding) - if err != nil { - log.Print(err.Error()) - return - } - if strings.Contains(*migrateRC8, "atr") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8rat.migrateActionTriggers(); err != nil { - log.Print(err.Error()) - } - } - if strings.Contains(*migrateRC8, "act") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8rat.migrateActions(); err != nil { - log.Print(err.Error()) - } - } - if strings.Contains(*migrateRC8, "dcs") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8rat.migrateDerivedChargers(); err != nil { - log.Print(err.Error()) - } - } - if strings.Contains(*migrateRC8, "apl") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8rat.migrateActionPlans(); err != nil { - log.Print(err.Error()) - } - } - if strings.Contains(*migrateRC8, "shg") || strings.Contains(*migrateRC8, "*all") { - if err := migratorRC8rat.migrateSharedGroups(); err != nil { - log.Print(err.Error()) - } - } - if strings.Contains(*migrateRC8, "int") { - if err := migratorRC8acc.migrateAccountsInt(); err != nil { - log.Print(err.Error()) - } - if err := migratorRC8rat.migrateActionTriggersInt(); err != nil { - log.Print(err.Error()) - } - if err := migratorRC8rat.migrateActionsInt(); err != nil { - log.Print(err.Error()) - } - } log.Print("Done!") return } diff --git a/cmd/cgr-loader/migrator_mongo.go b/cmd/cgr-loader/migrator_mongo.go new file mode 100644 index 000000000..9436dfd25 --- /dev/null +++ b/cmd/cgr-loader/migrator_mongo.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "log" + + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +type MongoMigrator struct { + session *mgo.Session + db *mgo.Database +} + +func NewMongoMigrator(host, port, db, user, pass string) (*MongoMigrator, error) { + address := fmt.Sprintf("%s:%s", host, port) + if user != "" && pass != "" { + address = fmt.Sprintf("%s:%s@%s", user, pass, address) + } + session, err := mgo.Dial(address) + if err != nil { + return nil, err + } + ndb := session.DB(db) + return &MongoMigrator{session: session, db: ndb}, nil +} + +func (mig MongoMigrator) migrateActions() error { + newAcsMap := make(map[string]engine.Actions) + iter := mig.db.C("actions").Find(nil).Iter() + var oldAcs struct { + Key string + Value Actions2 + } + for iter.Next(&oldAcs) { + log.Printf("Migrating action: %s...", oldAcs.Key) + newAcs := make(engine.Actions, len(oldAcs.Value)) + for index, oldAc := range oldAcs.Value { + a := &engine.Action{ + Id: oldAc.Id, + ActionType: oldAc.ActionType, + ExtraParameters: oldAc.ExtraParameters, + ExpirationString: oldAc.ExpirationString, + Filter: oldAc.Filter, + Weight: oldAc.Weight, + Balance: &engine.BalanceFilter{ + Uuid: oldAc.Balance.Uuid, + ID: oldAc.Balance.ID, + Type: oldAc.Balance.Type, + Directions: oldAc.Balance.Directions, + ExpirationDate: oldAc.Balance.ExpirationDate, + Weight: oldAc.Balance.Weight, + DestinationIDs: oldAc.Balance.DestinationIDs, + RatingSubject: oldAc.Balance.RatingSubject, + Categories: oldAc.Balance.Categories, + SharedGroups: oldAc.Balance.SharedGroups, + TimingIDs: oldAc.Balance.TimingIDs, + Timings: oldAc.Balance.Timings, + Disabled: oldAc.Balance.Disabled, + Factor: oldAc.Balance.Factor, + Blocker: oldAc.Balance.Blocker, + }, + } + if oldAc.Balance.Value != nil { + a.Balance.Value = &utils.ValueFormula{Static: *oldAc.Balance.Value} + } + newAcs[index] = a + } + newAcsMap[oldAcs.Key] = newAcs + } + if err := iter.Close(); err != nil { + return err + } + + // write data back + for key, acs := range newAcsMap { + if _, err := mig.db.C("actions").Upsert(bson.M{"key": key}, &struct { + Key string + Value engine.Actions + }{Key: key, Value: acs}); err != nil { + return err + } + } + return nil +} + +func (mig MongoMigrator) writeVersion() error { + _, err := mig.db.C("versions").Upsert(bson.M{"key": utils.VERSION_PREFIX + "struct"}, &struct { + Key string + Value *engine.StructVersion + }{utils.VERSION_PREFIX + "struct", engine.CurrentVersion}) + return err +} diff --git a/cmd/cgr-loader/migrator_rc8.go b/cmd/cgr-loader/migrator_rc8.go index 9d085d2f6..15579db98 100644 --- a/cmd/cgr-loader/migrator_rc8.go +++ b/cmd/cgr-loader/migrator_rc8.go @@ -482,7 +482,7 @@ func (mig MigratorRC8) migrateActions() error { bf.Type = utils.StringPointer(oldAc.BalanceType) } if oldAc.Balance.Value != 0 { - bf.Value = utils.Float64Pointer(oldAc.Balance.Value) + bf.Value = &utils.ValueFormula{Static: oldAc.Balance.Value} } if oldAc.Balance.RatingSubject != "" { bf.RatingSubject = utils.StringPointer(oldAc.Balance.RatingSubject) @@ -669,3 +669,11 @@ func (mig MigratorRC8) migrateSharedGroups() error { } return nil } + +func (mig MigratorRC8) writeVersion() error { + result, err := mig.ms.Marshal(engine.CurrentVersion) + if err != nil { + return err + } + return mig.db.Cmd("SET", utils.VERSION_PREFIX+"struct", result).Err +} diff --git a/cmd/cgr-loader/migrator_rc8int.go b/cmd/cgr-loader/migrator_rc8int.go index 41f55afb1..dd04b91ec 100644 --- a/cmd/cgr-loader/migrator_rc8int.go +++ b/cmd/cgr-loader/migrator_rc8int.go @@ -364,7 +364,7 @@ func (mig MigratorRC8) migrateActionsInt() error { bf.Type = utils.StringPointer(oldAc.BalanceType) } if oldAc.Balance.Value != 0 { - bf.Value = utils.Float64Pointer(oldAc.Balance.Value) + bf.Value = &utils.ValueFormula{Static: oldAc.Balance.Value} } if oldAc.Balance.RatingSubject != "" { bf.RatingSubject = utils.StringPointer(oldAc.Balance.RatingSubject) diff --git a/cmd/cgr-loader/migrator_rc8int2.go b/cmd/cgr-loader/migrator_rc8int2.go new file mode 100644 index 000000000..46c7caeeb --- /dev/null +++ b/cmd/cgr-loader/migrator_rc8int2.go @@ -0,0 +1,102 @@ +package main + +import ( + "log" + "time" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +type BalanceFilter2 struct { + Uuid *string + ID *string + Type *string + Value *float64 + Directions *utils.StringMap + ExpirationDate *time.Time + Weight *float64 + DestinationIDs *utils.StringMap + RatingSubject *string + Categories *utils.StringMap + SharedGroups *utils.StringMap + TimingIDs *utils.StringMap + Timings []*engine.RITiming + Disabled *bool + Factor *engine.ValueFactor + Blocker *bool +} + +type Action2 struct { + Id string + ActionType string + ExtraParameters string + Filter string + ExpirationString string // must stay as string because it can have relative values like 1month + Weight float64 + Balance *BalanceFilter2 +} + +type Actions2 []*Action2 + +func (mig MigratorRC8) migrateActionsInt2() error { + keys, err := mig.db.Cmd("KEYS", utils.ACTION_PREFIX+"*").List() + if err != nil { + return err + } + newAcsMap := make(map[string]engine.Actions, len(keys)) + for _, key := range keys { + log.Printf("Migrating action: %s...", key) + var oldAcs Actions2 + var values []byte + if values, err = mig.db.Cmd("GET", key).Bytes(); err == nil { + if err := mig.ms.Unmarshal(values, &oldAcs); err != nil { + return err + } + } + newAcs := make(engine.Actions, len(oldAcs)) + for index, oldAc := range oldAcs { + a := &engine.Action{ + Id: oldAc.Id, + ActionType: oldAc.ActionType, + ExtraParameters: oldAc.ExtraParameters, + ExpirationString: oldAc.ExpirationString, + Filter: oldAc.Filter, + Weight: oldAc.Weight, + Balance: &engine.BalanceFilter{ + Uuid: oldAc.Balance.Uuid, + ID: oldAc.Balance.ID, + Type: oldAc.Balance.Type, + Directions: oldAc.Balance.Directions, + ExpirationDate: oldAc.Balance.ExpirationDate, + Weight: oldAc.Balance.Weight, + DestinationIDs: oldAc.Balance.DestinationIDs, + RatingSubject: oldAc.Balance.RatingSubject, + Categories: oldAc.Balance.Categories, + SharedGroups: oldAc.Balance.SharedGroups, + TimingIDs: oldAc.Balance.TimingIDs, + Timings: oldAc.Balance.Timings, + Disabled: oldAc.Balance.Disabled, + Factor: oldAc.Balance.Factor, + Blocker: oldAc.Balance.Blocker, + }, + } + if oldAc.Balance.Value != nil { + a.Balance.Value = &utils.ValueFormula{Static: *oldAc.Balance.Value} + } + newAcs[index] = a + } + newAcsMap[key] = newAcs + } + // write data back + for key, acs := range newAcsMap { + result, err := mig.ms.Marshal(&acs) + if err != nil { + return err + } + if err = mig.db.Cmd("SET", key, result).Err; err != nil { + return err + } + } + return nil +} diff --git a/data/docker/devel/Dockerfile b/data/docker/devel/Dockerfile index 5e4917784..5bf440c97 100644 --- a/data/docker/devel/Dockerfile +++ b/data/docker/devel/Dockerfile @@ -26,7 +26,7 @@ COPY mongod.conf /etc/mongod.conf RUN useradd -c CGRateS -d /var/run/cgrates -s /bin/false -r cgrates # install golang -RUN wget -qO- https://storage.googleapis.com/golang/go1.6.1.linux-amd64.tar.gz | tar xzf - -C /root/ +RUN wget -qO- https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz | tar xzf - -C /root/ #install glide RUN GOROOT=/root/go GOPATH=/root/code /root/go/bin/go get github.com/Masterminds/glide diff --git a/data/storage/mysql/create_tariffplan_tables.sql b/data/storage/mysql/create_tariffplan_tables.sql index b6240cdc5..52bbf074b 100644 --- a/data/storage/mysql/create_tariffplan_tables.sql +++ b/data/storage/mysql/create_tariffplan_tables.sql @@ -152,7 +152,7 @@ CREATE TABLE `tp_actions` ( `balance_tag` varchar(64) NOT NULL, `balance_type` varchar(24) NOT NULL, `directions` varchar(8) NOT NULL, - `units` varchar(24) NOT NULL, + `units` varchar(256) NOT NULL, `expiry_time` varchar(24) NOT NULL, `timing_tags` varchar(128) NOT NULL, `destination_tags` varchar(64) NOT NULL, diff --git a/data/storage/postgres/create_tariffplan_tables.sql b/data/storage/postgres/create_tariffplan_tables.sql index b5d8193c8..e2bc89a57 100644 --- a/data/storage/postgres/create_tariffplan_tables.sql +++ b/data/storage/postgres/create_tariffplan_tables.sql @@ -147,7 +147,7 @@ CREATE TABLE tp_actions ( balance_tag VARCHAR(64) NOT NULL, balance_type VARCHAR(24) NOT NULL, directions VARCHAR(8) NOT NULL, - units VARCHAR(10) NOT NULL, + units VARCHAR(256) NOT NULL, expiry_time VARCHAR(24) NOT NULL, timing_tags VARCHAR(128) NOT NULL, destination_tags VARCHAR(64) NOT NULL, diff --git a/engine/account.go b/engine/account.go index b49ef4edd..d70a974d3 100644 --- a/engine/account.go +++ b/engine/account.go @@ -127,7 +127,7 @@ func (acc *Account) setBalanceAction(a *Action) error { if a.Balance.ID != nil && *a.Balance.ID == utils.META_DEFAULT { balance.ID = utils.META_DEFAULT if a.Balance.Value != nil { - balance.Value = *a.Balance.Value + balance.Value = a.Balance.GetValue() } } else { a.Balance.ModifyBalance(balance) @@ -170,6 +170,7 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error { return errors.New("nil action") } bClone := a.Balance.CreateBalance() + //log.Print("Bclone: ", utils.ToJSON(a.Balance)) if bClone == nil { return errors.New("nil balance") } diff --git a/engine/account_test.go b/engine/account_test.go index 90be903d2..7abd8d6c1 100644 --- a/engine/account_test.go +++ b/engine/account_test.go @@ -856,7 +856,7 @@ func TestAccountdebitBalanceExists(t *testing.T) { BalanceMap: map[string]Balances{utils.SMS: Balances{&Balance{Value: 14}}, utils.DATA: Balances{&Balance{Value: 1024}}, utils.VOICE: Balances{&Balance{Value: 15, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}, Directions: utils.NewStringMap(utils.OUT)}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } newMb := &BalanceFilter{ - Value: utils.Float64Pointer(-10), + Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), @@ -883,19 +883,19 @@ func TestAccountAddMinuteNil(t *testing.T) { func TestAccountAddMinutBucketEmpty(t *testing.T) { mb1 := &BalanceFilter{ - Value: utils.Float64Pointer(-10), + Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), } mb2 := &BalanceFilter{ - Value: utils.Float64Pointer(-10), + Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), } mb3 := &BalanceFilter{ - Value: utils.Float64Pointer(-10), + Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"OTHER": true}), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), diff --git a/engine/action.go b/engine/action.go index 903ce7d90..5a3c8e754 100644 --- a/engine/action.go +++ b/engine/action.go @@ -93,59 +93,36 @@ func (a *Action) Clone() *Action { type actionTypeFunc func(*Account, *StatsQueueTriggered, *Action, Actions) error func getActionFunc(typ string) (actionTypeFunc, bool) { - switch typ { - case LOG: - return logAction, true - case CDRLOG: - return cdrLogAction, true - case RESET_TRIGGERS: - return resetTriggersAction, true - case SET_RECURRENT: - return setRecurrentAction, true - case UNSET_RECURRENT: - return unsetRecurrentAction, true - case ALLOW_NEGATIVE: - return allowNegativeAction, true - case DENY_NEGATIVE: - return denyNegativeAction, true - case RESET_ACCOUNT: - return resetAccountAction, true - case TOPUP_RESET: - return topupResetAction, true - case TOPUP: - return topupAction, true - case DEBIT_RESET: - return debitResetAction, true - case DEBIT: - return debitAction, true - case RESET_COUNTERS: - return resetCountersAction, true - case ENABLE_ACCOUNT: - return enableUserAction, true - case DISABLE_ACCOUNT: - return disableUserAction, true - //case ENABLE_DISABLE_BALANCE: - // return enableDisableBalanceAction, true - case CALL_URL: - return callUrl, true - case CALL_URL_ASYNC: - return callUrlAsync, true - case MAIL_ASYNC: - return mailAsync, true - case SET_DDESTINATIONS: - return setddestinations, true - case REMOVE_ACCOUNT: - return removeAccountAction, true - case REMOVE_BALANCE: - return removeBalanceAction, true - case SET_BALANCE: - return setBalanceAction, true - case TRANSFER_MONETARY_DEFAULT: - return transferMonetaryDefaultAction, true - case CGR_RPC: - return cgrRPCAction, true + actionFuncMap := map[string]actionTypeFunc{ + LOG: logAction, + CDRLOG: cdrLogAction, + RESET_TRIGGERS: resetTriggersAction, + SET_RECURRENT: setRecurrentAction, + UNSET_RECURRENT: unsetRecurrentAction, + ALLOW_NEGATIVE: allowNegativeAction, + DENY_NEGATIVE: denyNegativeAction, + RESET_ACCOUNT: resetAccountAction, + TOPUP_RESET: topupResetAction, + TOPUP: topupAction, + DEBIT_RESET: debitResetAction, + DEBIT: debitAction, + RESET_COUNTERS: resetCountersAction, + ENABLE_ACCOUNT: enableUserAction, + DISABLE_ACCOUNT: disableUserAction, + //case ENABLE_DISABLE_BALANCE: + // return enableDisableBalanceAction, true + CALL_URL: callUrl, + CALL_URL_ASYNC: callUrlAsync, + MAIL_ASYNC: mailAsync, + SET_DDESTINATIONS: setddestinations, + REMOVE_ACCOUNT: removeAccountAction, + REMOVE_BALANCE: removeBalanceAction, + SET_BALANCE: setBalanceAction, + TRANSFER_MONETARY_DEFAULT: transferMonetaryDefaultAction, + CGR_RPC: cgrRPCAction, } - return nil, false + f, exists := actionFuncMap[typ] + return f, exists } func logAction(ub *Account, sq *StatsQueueTriggered, a *Action, acs Actions) (err error) { diff --git a/engine/action_plan.go b/engine/action_plan.go index 79095227c..563ea9413 100644 --- a/engine/action_plan.go +++ b/engine/action_plan.go @@ -291,7 +291,6 @@ func (at *ActionTiming) Execute() (err error) { transactionFailed := false removeAccountActionFound := false for _, a := range aac { - //log.Print("A: ", utils.ToJSON(a)) // check action filter if len(a.Filter) > 0 { matched, err := acc.matchActionFilter(a.Filter) diff --git a/engine/actions_test.go b/engine/actions_test.go index fb1481ef4..ebfd7b86f 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -413,7 +413,7 @@ func TestActionPlanLogFunction(t *testing.T) { ActionType: "*log", Balance: &BalanceFilter{ Type: utils.StringPointer("test"), - Value: utils.Float64Pointer(1.1), + Value: &utils.ValueFormula{Static: 1.1}, }, } at := &ActionTiming{ @@ -430,7 +430,7 @@ func TestActionPlanFunctionNotAvailable(t *testing.T) { ActionType: "VALID_FUNCTION_TYPE", Balance: &BalanceFilter{ Type: utils.StringPointer("test"), - Value: utils.Float64Pointer(1.1), + Value: &utils.ValueFormula{Static: 1.1}, }, } at := &ActionTiming{ @@ -659,7 +659,7 @@ func TestActionTriggerMatchAll(t *testing.T) { Type: utils.StringPointer(utils.MONETARY), RatingSubject: utils.StringPointer("test1"), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), - Value: utils.Float64Pointer(2), + Value: &utils.ValueFormula{Static: 2}, Weight: utils.Float64Pointer(1.0), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), SharedGroups: utils.StringMapPointer(utils.NewStringMap("test2")), @@ -669,7 +669,7 @@ func TestActionTriggerMatchAll(t *testing.T) { Type: utils.StringPointer(utils.MONETARY), RatingSubject: utils.StringPointer("test1"), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), - Value: utils.Float64Pointer(2), + Value: &utils.ValueFormula{Static: 2}, Weight: utils.Float64Pointer(1.0), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), SharedGroups: utils.StringMapPointer(utils.NewStringMap("test2")), @@ -799,7 +799,7 @@ func TestActionTopupResetCredit(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1, Filter: &BalanceFilter{Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupResetAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.MONETARY].GetTotalValue() != 10 || @@ -818,7 +818,7 @@ func TestActionTopupValueFactor(t *testing.T) { a := &Action{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), - Value: utils.Float64Pointer(10), + Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), }, ExtraParameters: `{"*monetary":2.0}`, @@ -839,7 +839,7 @@ func TestActionTopupResetCreditId(t *testing.T) { }, }, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), ID: utils.StringPointer("TEST_B"), Value: utils.Float64Pointer(10), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), ID: utils.StringPointer("TEST_B"), Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupResetAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.MONETARY].GetTotalValue() != 110 || @@ -858,7 +858,7 @@ func TestActionTopupResetCreditNoId(t *testing.T) { }, }, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupResetAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.MONETARY].GetTotalValue() != 20 || @@ -876,7 +876,7 @@ func TestActionTopupResetMinutes(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1, Filter: &BalanceFilter{Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: utils.Float64Pointer(5), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: &utils.ValueFormula{Static: 5}, Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupResetAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.VOICE].GetTotalValue() != 5 || @@ -895,7 +895,7 @@ func TestActionTopupCredit(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1, Filter: &BalanceFilter{Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.MONETARY].GetTotalValue() != 110 || @@ -913,7 +913,7 @@ func TestActionTopupMinutes(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: utils.Float64Pointer(5), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: &utils.ValueFormula{Static: 5}, Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} topupAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.VOICE].GetTotalValue() != 15 || @@ -932,7 +932,7 @@ func TestActionDebitCredit(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1, Filter: &BalanceFilter{Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} debitAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.MONETARY].GetTotalValue() != 90 || @@ -950,7 +950,7 @@ func TestActionDebitMinutes(t *testing.T) { UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{&UnitCounter{Counters: CounterFilters{&CounterFilter{Value: 1}}}}}, ActionTriggers: ActionTriggers{&ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } - a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: utils.Float64Pointer(5), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} + a := &Action{Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Value: &utils.ValueFormula{Static: 5}, Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT))}} debitAction(ub, nil, a, nil) if ub.AllowNegative || ub.BalanceMap[utils.VOICE][0].GetValue() != 5 || @@ -1119,7 +1119,7 @@ func TestActionPlanLogging(t *testing.T) { } func TestActionMakeNegative(t *testing.T) { - a := &Action{Balance: &BalanceFilter{Value: utils.Float64Pointer(10)}} + a := &Action{Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 10}}} genericMakeNegative(a) if a.Balance.GetValue() > 0 { t.Error("Failed to make negative: ", a) @@ -1153,7 +1153,7 @@ func TestTopupAction(t *testing.T) { initialUb, _ := accountingStorage.GetAccount("vdf:minu") a := &Action{ ActionType: TOPUP, - Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), Weight: utils.Float64Pointer(20)}, } at := &ActionTiming{ @@ -1174,7 +1174,7 @@ func TestTopupActionLoaded(t *testing.T) { initialUb, _ := accountingStorage.GetAccount("vdf:minitsboy") a := &Action{ ActionType: TOPUP, - Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), Weight: utils.Float64Pointer(20)}, } at := &ActionTiming{ @@ -1201,7 +1201,7 @@ func TestActionCdrlogEmpty(t *testing.T) { err := cdrLogAction(acnt, nil, cdrlog, Actions{ &Action{ ActionType: DEBIT, - Balance: &BalanceFilter{Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, }, }) if err != nil { @@ -1223,11 +1223,11 @@ func TestActionCdrlogWithParams(t *testing.T) { err := cdrLogAction(acnt, nil, cdrlog, Actions{ &Action{ ActionType: DEBIT, - Balance: &BalanceFilter{Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, }, &Action{ ActionType: DEBIT_RESET, - Balance: &BalanceFilter{Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, }, }) if err != nil { @@ -1250,11 +1250,11 @@ func TestActionCdrLogParamsWithOverload(t *testing.T) { err := cdrLogAction(acnt, nil, cdrlog, Actions{ &Action{ ActionType: DEBIT, - Balance: &BalanceFilter{Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, }, &Action{ ActionType: DEBIT_RESET, - Balance: &BalanceFilter{Value: utils.Float64Pointer(25), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}, }, }) if err != nil { @@ -1335,11 +1335,11 @@ func TestActionTransactionFuncType(t *testing.T) { actions: []*Action{ &Action{ ActionType: TOPUP, - Balance: &BalanceFilter{Value: utils.Float64Pointer(1.1), Type: utils.StringPointer(utils.MONETARY)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 1.1}, Type: utils.StringPointer(utils.MONETARY)}, }, &Action{ ActionType: "VALID_FUNCTION_TYPE", - Balance: &BalanceFilter{Value: utils.Float64Pointer(1.1), Type: utils.StringPointer("test")}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 1.1}, Type: utils.StringPointer("test")}, }, }, } @@ -1371,7 +1371,7 @@ func TestActionTransactionBalanceType(t *testing.T) { actions: []*Action{ &Action{ ActionType: TOPUP, - Balance: &BalanceFilter{Value: utils.Float64Pointer(1.1), Type: utils.StringPointer(utils.MONETARY)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 1.1}, Type: utils.StringPointer(utils.MONETARY)}, }, &Action{ ActionType: TOPUP, @@ -1407,7 +1407,7 @@ func TestActionTransactionBalanceNotType(t *testing.T) { actions: []*Action{ &Action{ ActionType: TOPUP, - Balance: &BalanceFilter{Value: utils.Float64Pointer(1.1), Type: utils.StringPointer(utils.VOICE)}, + Balance: &BalanceFilter{Value: &utils.ValueFormula{Static: 1.1}, Type: utils.StringPointer(utils.VOICE)}, }, &Action{ ActionType: TOPUP, @@ -1445,14 +1445,14 @@ func TestActionWithExpireWithoutExpire(t *testing.T) { ActionType: TOPUP, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), - Value: utils.Float64Pointer(15), + Value: &utils.ValueFormula{Static: 15}, }, }, &Action{ ActionType: TOPUP, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), - Value: utils.Float64Pointer(30), + Value: &utils.ValueFormula{Static: 30}, ExpirationDate: utils.TimePointer(time.Date(2025, time.November, 11, 22, 39, 0, 0, time.UTC)), }, }, @@ -1672,7 +1672,7 @@ func TestActionConditionalTopup(t *testing.T) { Filter: `{"Type":"*monetary","Value":1,"Weight":10}`, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), - Value: utils.Float64Pointer(11), + Value: &utils.ValueFormula{Static: 11}, Weight: utils.Float64Pointer(30), }, } @@ -1736,7 +1736,7 @@ func TestActionConditionalTopupNoMatch(t *testing.T) { Filter: `{"Type":"*monetary","Value":2,"Weight":10}`, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), - Value: utils.Float64Pointer(11), + Value: &utils.ValueFormula{Static: 11}, Weight: utils.Float64Pointer(30), }, } @@ -1800,7 +1800,7 @@ func TestActionConditionalTopupExistingBalance(t *testing.T) { Filter: `{"Type":"*voice","Value":{"*gte":100}}`, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), - Value: utils.Float64Pointer(11), + Value: &utils.ValueFormula{Static: 11}, Weight: utils.Float64Pointer(10), }, } @@ -2021,7 +2021,7 @@ func TestActionSetBalance(t *testing.T) { Balance: &BalanceFilter{ ID: utils.StringPointer("m2"), Type: utils.StringPointer(utils.MONETARY), - Value: utils.Float64Pointer(11), + Value: &utils.ValueFormula{Static: 11}, Weight: utils.Float64Pointer(10), }, } @@ -2122,7 +2122,7 @@ func TestActionCdrlogBalanceValue(t *testing.T) { Balance: &BalanceFilter{ ID: utils.StringPointer("*default"), Uuid: utils.StringPointer("25a02c82-f09f-4c6e-bacf-8ed4b076475a"), - Value: utils.Float64Pointer(1.1), + Value: &utils.ValueFormula{Static: 1.1}, Type: utils.StringPointer(utils.MONETARY), }, }, @@ -2132,7 +2132,7 @@ func TestActionCdrlogBalanceValue(t *testing.T) { Balance: &BalanceFilter{ ID: utils.StringPointer("*default"), Uuid: utils.StringPointer("25a02c82-f09f-4c6e-bacf-8ed4b076475a"), - Value: utils.Float64Pointer(2.1), + Value: &utils.ValueFormula{Static: 2.1}, Type: utils.StringPointer(utils.MONETARY), }, }, @@ -2222,6 +2222,22 @@ func TestCgrRpcAction(t *testing.T) { } } +func TestValueFormulaDebit(t *testing.T) { + if _, err := accountingStorage.GetAccount("cgrates.org:vf"); err != nil { + t.Errorf("account to be removed not found: %v", err) + } + + at := &ActionTiming{ + accountIDs: utils.StringMap{"cgrates.org:vf": true}, + ActionsID: "VF", + } + at.Execute() + afterUb, err := accountingStorage.GetAccount("cgrates.org:vf") + if err != nil || afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != -0.333334 { + t.Error("error debiting account: ", err, utils.ToIJSON(afterUb)) + } +} + /**************** Benchmarks ********************************/ func BenchmarkUUID(b *testing.B) { diff --git a/engine/balance_filter.go b/engine/balance_filter.go index f9e408d39..78ff64140 100644 --- a/engine/balance_filter.go +++ b/engine/balance_filter.go @@ -11,7 +11,7 @@ type BalanceFilter struct { Uuid *string ID *string Type *string - Value *float64 + Value *utils.ValueFormula Directions *utils.StringMap ExpirationDate *time.Time Weight *float64 @@ -58,7 +58,7 @@ func (bf *BalanceFilter) Clone() *BalanceFilter { *result.ID = *bf.ID } if bf.Value != nil { - result.Value = new(float64) + result.Value = new(utils.ValueFormula) *result.Value = *bf.Value } if bf.RatingSubject != nil { @@ -116,7 +116,7 @@ func (bf *BalanceFilter) LoadFromBalance(b *Balance) *BalanceFilter { bf.ID = &b.ID } if b.Value != 0 { - bf.Value = &b.Value + bf.Value.Static = b.Value } if !b.Directions.IsEmpty() { bf.Directions = &b.Directions @@ -173,14 +173,22 @@ func (bp *BalanceFilter) GetValue() float64 { if bp == nil || bp.Value == nil { return 0.0 } - return *bp.Value + if bp.Value.Method == "" { + return bp.Value.Static + } + // calculate using formula + formula, exists := utils.ValueFormulas[bp.Value.Method] + if !exists { + return 0.0 + } + return formula(bp.Value.Params) } func (bp *BalanceFilter) SetValue(v float64) { if bp.Value == nil { - bp.Value = new(float64) + bp.Value = new(utils.ValueFormula) } - *bp.Value = v + bp.Value.Static = v } func (bp *BalanceFilter) GetUuid() string { @@ -292,7 +300,7 @@ func (bf *BalanceFilter) ModifyBalance(b *Balance) { b.Directions = *bf.Directions } if bf.Value != nil { - b.Value = *bf.Value + b.Value = bf.GetValue() } if bf.ExpirationDate != nil { b.ExpirationDate = *bf.ExpirationDate diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 607a97a23..f2dbbdd08 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -41,12 +41,12 @@ func init() { func populateDB() { ats := []*Action{ - &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10)}}, - &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), Value: utils.Float64Pointer(10), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT"))}}, + &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}}}, + &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), Value: &utils.ValueFormula{Static: 10}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT"))}}, } ats1 := []*Action{ - &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: utils.Float64Pointer(10)}, Weight: 10}, + &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 10}}, Weight: 10}, &Action{ActionType: "*reset_account", Weight: 20}, } diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index d26ebbcc6..f2fb1f898 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -180,6 +180,7 @@ BLOCK_EMPTY,*topup,,,bfree,*monetary,*out,,,,,*unlimited,,20,10,false,false,10 FILTER,*topup,,"{""*and"":[{""Value"":{""*lt"":0}},{""Id"":{""*eq"":""*default""}}]}",bfree,*monetary,*out,,,,,*unlimited,,20,10,false,false,10 EXP,*topup,,,,*voice,*out,,,,,*monthly,*any,300,10,false,false,10 NOEXP,*topup,,,,*voice,*out,,,,,*unlimited,*any,50,10,false,false,10 +VF,*debit,,,,*monetary,*out,,,,,*unlimited,*any,"{""Method"":""*incremental"",""Params"":{""Units"":10, ""Interval"":""month"", ""Increment"":""day""}}",10,false,false,10 ` actionPlans = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 @@ -223,6 +224,7 @@ cgrates.org,block,BLOCK_AT,,false,false cgrates.org,block_empty,BLOCK_EMPTY_AT,,false,false cgrates.org,expo,EXP_AT,,false,false cgrates.org,expnoexp,,,false,false +cgrates.org,vf,,,false,false ` derivedCharges = ` @@ -825,7 +827,7 @@ func TestLoadRatingProfiles(t *testing.T) { } func TestLoadActions(t *testing.T) { - if len(csvr.actions) != 14 { + if len(csvr.actions) != 15 { t.Error("Failed to load actions: ", len(csvr.actions)) } as1 := csvr.actions["MINI"] @@ -840,7 +842,7 @@ func TestLoadActions(t *testing.T) { Type: utils.StringPointer(utils.MONETARY), Uuid: as1[0].Balance.Uuid, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), - Value: utils.Float64Pointer(10), + Value: &utils.ValueFormula{Static: 10}, Weight: utils.Float64Pointer(10), DestinationIDs: nil, TimingIDs: nil, @@ -860,7 +862,7 @@ func TestLoadActions(t *testing.T) { Type: utils.StringPointer(utils.VOICE), Uuid: as1[1].Balance.Uuid, Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), - Value: utils.Float64Pointer(100), + Value: &utils.ValueFormula{Static: 100}, Weight: utils.Float64Pointer(10), RatingSubject: utils.StringPointer("test"), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), @@ -873,7 +875,7 @@ func TestLoadActions(t *testing.T) { }, } if !reflect.DeepEqual(as1, expected) { - t.Errorf("Error loading action1: %+v", as1[0].Balance) + t.Errorf("Error loading action1: %s", utils.ToIJSON(as1)) } as2 := csvr.actions["SHARED"] expected = []*Action{ @@ -887,7 +889,7 @@ func TestLoadActions(t *testing.T) { Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), DestinationIDs: nil, Uuid: as2[0].Balance.Uuid, - Value: utils.Float64Pointer(100), + Value: &utils.ValueFormula{Static: 100}, Weight: utils.Float64Pointer(10), SharedGroups: utils.StringMapPointer(utils.NewStringMap("SG1")), TimingIDs: nil, @@ -898,7 +900,7 @@ func TestLoadActions(t *testing.T) { }, } if !reflect.DeepEqual(as2, expected) { - t.Errorf("Error loading action: %+v", as2[0].Balance) + t.Errorf("Error loading action: %s", utils.ToIJSON(as2)) } as3 := csvr.actions["DEFEE"] expected = []*Action{ @@ -1106,7 +1108,7 @@ func TestLoadActionTriggers(t *testing.T) { } func TestLoadAccountActions(t *testing.T) { - if len(csvr.accountActions) != 15 { + if len(csvr.accountActions) != 16 { t.Error("Failed to load account actions: ", len(csvr.accountActions)) } aa := csvr.accountActions["vdf:minitsboy"] diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go index 68ba06277..0e2e59113 100644 --- a/engine/storage_mongo_datadb.go +++ b/engine/storage_mongo_datadb.go @@ -53,7 +53,7 @@ const ( colLogAtr = "action_trigger_logs" colLogApl = "action_plan_logs" colLogErr = "error_logs" - colVer = "version" + colVer = "versions" ) var ( @@ -1374,10 +1374,15 @@ func (ms *MongoStorage) SetStructVersion(v *StructVersion) (err error) { } func (ms *MongoStorage) GetStructVersion() (rsv *StructVersion, err error) { - rsv = new(StructVersion) - err = ms.db.C(colVer).Find(bson.M{"key": utils.VERSION_PREFIX + "struct"}).One(rsv) + var result struct { + Key string + Value StructVersion + } + + err = ms.db.C(colVer).Find(bson.M{"key": utils.VERSION_PREFIX + "struct"}).One(&result) if err == mgo.ErrNotFound { rsv = nil } + rsv = &result.Value return } diff --git a/engine/storage_test.go b/engine/storage_test.go index eb007243b..06c40e0d1 100644 --- a/engine/storage_test.go +++ b/engine/storage_test.go @@ -274,7 +274,7 @@ func TestDifferentUuid(t *testing.T) { func TestStorageTask(t *testing.T) { // clean previous unused tasks - for i := 0; i < 19; i++ { + for i := 0; i < 20; i++ { ratingStorage.PopTask() } diff --git a/engine/tp_reader.go b/engine/tp_reader.go index e458e130e..a2a71470b 100644 --- a/engine/tp_reader.go +++ b/engine/tp_reader.go @@ -531,11 +531,11 @@ func (tpr *TpReader) LoadActions() (err error) { } if tpact.Units != "" && tpact.Units != utils.ANY { - u, err := strconv.ParseFloat(tpact.Units, 64) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } - acts[idx].Balance.Value = utils.Float64Pointer(u) + acts[idx].Balance.Value = vf } if tpact.BalanceWeight != "" && tpact.BalanceWeight != utils.ANY { @@ -1007,11 +1007,11 @@ func (tpr *TpReader) LoadAccountActionsFiltered(qriedAA *TpAccountAction) error } if tpact.Units != "" && tpact.Units != utils.ANY { - u, err := strconv.ParseFloat(tpact.Units, 64) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } - acts[idx].Balance.Value = utils.Float64Pointer(u) + acts[idx].Balance.Value = vf } if tpact.BalanceWeight != "" && tpact.BalanceWeight != utils.ANY { @@ -1355,11 +1355,11 @@ func (tpr *TpReader) LoadCdrStatsFiltered(tag string, save bool) (err error) { } if tpact.Units != "" && tpact.Units != utils.ANY { - u, err := strconv.ParseFloat(tpact.Units, 64) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } - acts[idx].Balance.Value = utils.Float64Pointer(u) + acts[idx].Balance.Value = vf } if tpact.BalanceWeight != "" && tpact.BalanceWeight != utils.ANY { diff --git a/engine/version.go b/engine/version.go index 047036261..08849db5c 100644 --- a/engine/version.go +++ b/engine/version.go @@ -1,29 +1,41 @@ package engine import ( + "errors" "fmt" "github.com/cgrates/cgrates/utils" ) -func init() { +func CheckVersion() error { // get current db version dbVersion, err := accountingStorage.GetStructVersion() if err != nil { - utils.Logger.Warning(fmt.Sprintf("Could not retrive current version from db: %v", err)) - return - } - // comparing versions - if currentVersion.CompareAndMigrate(dbVersion) { - // write the new values - if err := accountingStorage.SetStructVersion(currentVersion); err != nil { - utils.Logger.Warning(fmt.Sprintf("Could not write current version to db: %v", err)) + if lhList, err := accountingStorage.GetLoadHistory(1, true); err != nil || len(lhList) == 0 { + // no data, write version + if err := accountingStorage.SetStructVersion(CurrentVersion); err != nil { + utils.Logger.Warning(fmt.Sprintf("Could not write current version to db: %v", err)) + } + } else { + // has data but no version => run migration + msg := "Could not detect data structures version: run appropriate migration" + utils.Logger.Crit(msg) + return errors.New(msg) + } + } else { + // comparing versions + if len(CurrentVersion.CompareAndMigrate(dbVersion)) > 0 { + // write the new values + msg := "Migration needed: please backup cgr data and run cgr-cloader -migrate" + utils.Logger.Crit(msg) + return errors.New(msg) } } + return nil } var ( - currentVersion = &StructVersion{ + CurrentVersion = &StructVersion{ Destinations: "1", RatingPlans: "1", RatingProfiles: "1", @@ -67,67 +79,133 @@ type StructVersion struct { SMCosts string } -func (sv *StructVersion) CompareAndMigrate(dbVer *StructVersion) bool { - migrationPerformed := false +type MigrationInfo struct { + Prefix string + DbVersion string + CurrentVersion string +} + +func (sv *StructVersion) CompareAndMigrate(dbVer *StructVersion) []*MigrationInfo { + var migrationInfoList []*MigrationInfo if sv.Destinations != dbVer.Destinations { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.DESTINATION_PREFIX, + DbVersion: dbVer.Destinations, + CurrentVersion: CurrentVersion.Destinations, + }) } if sv.RatingPlans != dbVer.RatingPlans { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.RATING_PLAN_PREFIX, + DbVersion: dbVer.RatingPlans, + CurrentVersion: CurrentVersion.RatingPlans, + }) } - if sv.RatingProfiles != dbVer.RatingPlans { - migrationPerformed = true - + if sv.RatingProfiles != dbVer.RatingProfiles { + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.RATING_PROFILE_PREFIX, + DbVersion: dbVer.RatingProfiles, + CurrentVersion: CurrentVersion.RatingProfiles, + }) } if sv.Lcrs != dbVer.Lcrs { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.LCR_PREFIX, + DbVersion: dbVer.Lcrs, + CurrentVersion: CurrentVersion.Lcrs, + }) } if sv.DerivedChargers != dbVer.DerivedChargers { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.DERIVEDCHARGERS_PREFIX, + DbVersion: dbVer.DerivedChargers, + CurrentVersion: CurrentVersion.DerivedChargers, + }) } if sv.Actions != dbVer.Actions { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.ACTION_PREFIX, + DbVersion: dbVer.Actions, + CurrentVersion: CurrentVersion.Actions, + }) } if sv.ActionPlans != dbVer.ActionPlans { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.ACTION_PLAN_PREFIX, + DbVersion: dbVer.ActionPlans, + CurrentVersion: CurrentVersion.ActionPlans, + }) } if sv.ActionTriggers != dbVer.ActionTriggers { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.ACTION_TRIGGER_PREFIX, + DbVersion: dbVer.ActionTriggers, + CurrentVersion: CurrentVersion.ActionTriggers, + }) } if sv.SharedGroups != dbVer.SharedGroups { - migrationPerformed = true - + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.SHARED_GROUP_PREFIX, + DbVersion: dbVer.SharedGroups, + CurrentVersion: CurrentVersion.SharedGroups, + }) } if sv.Accounts != dbVer.Accounts { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.ACCOUNT_PREFIX, + DbVersion: dbVer.Accounts, + CurrentVersion: CurrentVersion.Accounts, + }) } if sv.CdrStats != dbVer.CdrStats { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.CDR_STATS_PREFIX, + DbVersion: dbVer.CdrStats, + CurrentVersion: CurrentVersion.CdrStats, + }) } if sv.Users != dbVer.Users { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.USERS_PREFIX, + DbVersion: dbVer.Users, + CurrentVersion: CurrentVersion.Users, + }) } if sv.Alias != dbVer.Alias { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.ALIASES_PREFIX, + DbVersion: dbVer.Alias, + CurrentVersion: CurrentVersion.Alias, + }) } if sv.PubSubs != dbVer.PubSubs { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.PUBSUB_SUBSCRIBERS_PREFIX, + DbVersion: dbVer.PubSubs, + CurrentVersion: CurrentVersion.PubSubs, + }) } if sv.LoadHistory != dbVer.LoadHistory { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.LOADINST_KEY, + DbVersion: dbVer.LoadHistory, + CurrentVersion: CurrentVersion.LoadHistory, + }) } if sv.Cdrs != dbVer.Cdrs { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.CDRS_SOURCE, + DbVersion: dbVer.RatingPlans, + CurrentVersion: CurrentVersion.RatingPlans, + }) } if sv.SMCosts != dbVer.SMCosts { - migrationPerformed = true + migrationInfoList = append(migrationInfoList, &MigrationInfo{ + Prefix: utils.SMG, + DbVersion: dbVer.SMCosts, + CurrentVersion: CurrentVersion.SMCosts, + }) } - return migrationPerformed + return migrationInfoList } diff --git a/utils/coreutils.go b/utils/coreutils.go index 461fb2d98..abdbc1fa7 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -127,8 +127,9 @@ func Round(x float64, prec int, method string) float64 { } func ParseTimeDetectLayout(tmStr string, timezone string) (time.Time, error) { + tmStr = strings.TrimSpace(tmStr) var nilTime time.Time - if len(tmStr) == 0 { + if len(tmStr) == 0 || tmStr == UNLIMITED { return nilTime, nil } loc, err := time.LoadLocation(timezone) @@ -179,7 +180,7 @@ func ParseTimeDetectLayout(tmStr string, timezone string) (time.Time, error) { func ParseDate(date string) (expDate time.Time, err error) { date = strings.TrimSpace(date) switch { - case date == "*unlimited" || date == "": + case date == UNLIMITED || date == "": // leave it at zero case strings.HasPrefix(date, "+"): d, err := time.ParseDuration(date[1:]) diff --git a/utils/dateseries.go b/utils/dateseries.go index 130bac5f2..818efa414 100644 --- a/utils/dateseries.go +++ b/utils/dateseries.go @@ -277,3 +277,13 @@ func (wd WeekDays) Serialize(sep string) string { } return wdStr } + +func DaysInMonth(year int, month time.Month) float64 { + return float64(time.Date(year, month, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, -1).Day()) +} + +func DaysInYear(year int) float64 { + first := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC) + last := first.AddDate(1, 0, 0) + return float64(last.Sub(first).Hours() / 24) +} diff --git a/utils/dateseries_test.go b/utils/dateseries_test.go index 363aee178..ffd498827 100644 --- a/utils/dateseries_test.go +++ b/utils/dateseries_test.go @@ -161,3 +161,30 @@ func TestDateseriesMonthsIsCompleteYes(t *testing.T) { t.Error("Error months IsComplete: ", months) } } + +func TestDateseriesDaysInMonth(t *testing.T) { + if n := DaysInMonth(2016, 4); n != 30 { + t.Error("error calculating days: ", n) + } + if n := DaysInMonth(2016, 2); n != 29 { + t.Error("error calculating days: ", n) + } + if n := DaysInMonth(2016, 1); n != 31 { + t.Error("error calculating days: ", n) + } + if n := DaysInMonth(2016, 12); n != 31 { + t.Error("error calculating days: ", n) + } + if n := DaysInMonth(2015, 2); n != 28 { + t.Error("error calculating days: ", n) + } +} + +func TestDateseriesDaysInYear(t *testing.T) { + if n := DaysInYear(2016); n != 366 { + t.Error("error calculating days: ", n) + } + if n := DaysInYear(2015); n != 365 { + t.Error("error calculating days: ", n) + } +} diff --git a/utils/rpc_params.go b/utils/rpc_params.go index c48e84085..3e5f3f166 100644 --- a/utils/rpc_params.go +++ b/utils/rpc_params.go @@ -1,8 +1,6 @@ package utils -import ( - "reflect" -) +import "reflect" var rpcParamsMap map[string]*RpcParams @@ -39,12 +37,10 @@ func RegisterRpcParams(name string, obj interface{}) { OutParam: reflect.New(out).Interface(), } } - } } func GetRpcParams(method string) (*RpcParams, error) { - Logger.Info("REGISTERED: " + ToJSON(rpcParamsMap)) x, found := rpcParamsMap[method] if !found { return nil, ErrNotFound diff --git a/utils/value_formula.go b/utils/value_formula.go new file mode 100644 index 000000000..1ac113aa1 --- /dev/null +++ b/utils/value_formula.go @@ -0,0 +1,85 @@ +package utils + +import ( + "encoding/json" + "errors" + "log" + "strconv" + "time" +) + +//for computing a dynamic value for Value field +type ValueFormula struct { + Method string + Params map[string]interface{} + Static float64 +} + +func ParseBalanceFilterValue(val string) (*ValueFormula, error) { + u, err := strconv.ParseFloat(val, 64) + if err == nil { + return &ValueFormula{Static: u}, err + } + var vf ValueFormula + if err := json.Unmarshal([]byte(val), &vf); err == nil { + return &vf, err + } + return nil, errors.New("Invalid value: " + val) +} + +type valueFormula func(map[string]interface{}) float64 + +const ( + INCREMENTAL = "*incremental" +) + +var ValueFormulas = map[string]valueFormula{ + INCREMENTAL: incrementalFormula, +} + +func incrementalFormula(params map[string]interface{}) float64 { + // check parameters + unitsInterface, unitsFound := params["Units"] + intervalInterface, intervalFound := params["Interval"] + incrementInterface, incrementFound := params["Increment"] + + if !unitsFound || !intervalFound || !incrementFound { + return 0.0 + } + units, ok := unitsInterface.(float64) + if !ok { + log.Print("units") + return 0.0 + } + var interval string + switch intr := intervalInterface.(type) { + case string: + interval = intr + case []byte: + interval = string(intr) + default: + return 0.0 + } + var increment string + switch incr := incrementInterface.(type) { + case string: + increment = incr + case []byte: + increment = string(incr) + default: + return 0.0 + } + now := time.Now() + if increment == "day" { + if interval == "week" { + return units / 7 + } + if interval == "month" { + return units / DaysInMonth(now.Year(), now.Month()) + } + if interval == "year" { + return units / DaysInYear(now.Year()) + } + } + return 0.0 +} diff --git a/utils/value_formula_test.go b/utils/value_formula_test.go new file mode 100644 index 000000000..1aab18106 --- /dev/null +++ b/utils/value_formula_test.go @@ -0,0 +1,39 @@ +package utils + +import ( + "encoding/json" + "testing" + "time" +) + +func TestValueFormulaDayWeek(t *testing.T) { + params := make(map[string]interface{}) + if err := json.Unmarshal([]byte(`{"Units":10, "Interval":"week", "Increment":"day"}`), ¶ms); err != nil { + t.Error("error unmarshalling params: ", err) + } + if x := incrementalFormula(params); x != 10/7.0 { + t.Error("error caclulating value using formula: ", x) + } +} + +func TestValueFormulaDayMonth(t *testing.T) { + params := make(map[string]interface{}) + if err := json.Unmarshal([]byte(`{"Units":10, "Interval":"month", "Increment":"day"}`), ¶ms); err != nil { + t.Error("error unmarshalling params: ", err) + } + now := time.Now() + if x := incrementalFormula(params); x != 10/DaysInMonth(now.Year(), now.Month()) { + t.Error("error caclulating value using formula: ", x) + } +} + +func TestValueFormulaDayYear(t *testing.T) { + params := make(map[string]interface{}) + if err := json.Unmarshal([]byte(`{"Units":10, "Interval":"year", "Increment":"day"}`), ¶ms); err != nil { + t.Error("error unmarshalling params: ", err) + } + now := time.Now() + if x := incrementalFormula(params); x != 10/DaysInYear(now.Year()) { + t.Error("error caclulating value using formula: ", x) + } +}