From 768cdcc43a129d24685aa3ff577d386c7d0688c6 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 22 Apr 2016 15:07:05 +0300 Subject: [PATCH] value formula passing test --- apier/v1/accounts.go | 6 +-- apier/v1/apier.go | 4 +- cmd/cgr-loader/migrator_rc8.go | 2 +- cmd/cgr-loader/migrator_rc8int.go | 2 +- engine/account.go | 1 + engine/account_test.go | 8 +-- engine/action_plan.go | 1 - engine/actions_test.go | 82 +++++++++++++++++------------ engine/balance_filter.go | 45 ++-------------- engine/calldesc_test.go | 6 +-- engine/loader_csv_test.go | 12 +++-- engine/storage_test.go | 2 +- engine/tp_reader.go | 6 +-- utils/dateseries.go | 10 ++++ utils/dateseries_test.go | 27 ++++++++++ utils/value_formula.go | 85 +++++++++++++++++++++++++++++++ utils/value_formula_test.go | 39 ++++++++++++++ 17 files changed, 240 insertions(+), 98 deletions(-) create mode 100644 utils/value_formula.go create mode 100644 utils/value_formula_test.go diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index 39173d652..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: &engine.ValueFormula{Static: attr.Value}, + Value: &utils.ValueFormula{Static: attr.Value}, ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Weight: attr.Weight, @@ -522,7 +522,7 @@ func (self *ApierV1) SetBalance(attr *AttrSetBalance, reply *string) error { }, } if attr.Value != nil { - a.Balance.Value = &engine.ValueFormula{Static: *attr.Value} + a.Balance.Value = &utils.ValueFormula{Static: *attr.Value} } if attr.Directions != nil { a.Balance.Directions = utils.StringMapPointer(utils.ParseStringMap(*attr.Directions)) @@ -582,7 +582,7 @@ func (self *ApierV1) RemoveBalances(attr *AttrSetBalance, reply *string) error { }, } if attr.Value != nil { - a.Balance.Value = &engine.ValueFormula{Static: *attr.Value} + 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 24a5d3a49..cf3f86389 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -504,9 +504,9 @@ func (self *ApierV1) SetActions(attrs utils.AttrSetActions, reply *string) error } storeActions := make(engine.Actions, len(attrs.Actions)) for idx, apiAct := range attrs.Actions { - var vf *engine.ValueFormula + var vf *utils.ValueFormula if apiAct.Units != "" { - if x, err := engine.ParseBalanceFilterValue(apiAct.Units); err == nil { + if x, err := utils.ParseBalanceFilterValue(apiAct.Units); err == nil { vf = x } else { return err diff --git a/cmd/cgr-loader/migrator_rc8.go b/cmd/cgr-loader/migrator_rc8.go index f28e04766..b5574a1d5 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 = &engine.ValueFormula{Static: 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_rc8int.go b/cmd/cgr-loader/migrator_rc8int.go index 3d335c914..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 = &engine.ValueFormula{Static: 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/engine/account.go b/engine/account.go index 1bd3ac42e..d70a974d3 100644 --- a/engine/account.go +++ b/engine/account.go @@ -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 42dd1cb4c..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: &ValueFormula{Static: -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: &ValueFormula{Static: -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: &ValueFormula{Static: -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: &ValueFormula{Static: -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_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 d37695055..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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 15}, + Value: &utils.ValueFormula{Static: 15}, }, }, &Action{ ActionType: TOPUP, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), - Value: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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: &ValueFormula{Static: 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 bc98c2bae..78ff64140 100644 --- a/engine/balance_filter.go +++ b/engine/balance_filter.go @@ -1,10 +1,7 @@ package engine import ( - "encoding/json" - "errors" "reflect" - "strconv" "time" "github.com/cgrates/cgrates/utils" @@ -14,7 +11,7 @@ type BalanceFilter struct { Uuid *string ID *string Type *string - Value *ValueFormula + Value *utils.ValueFormula Directions *utils.StringMap ExpirationDate *time.Time Weight *float64 @@ -61,7 +58,7 @@ func (bf *BalanceFilter) Clone() *BalanceFilter { *result.ID = *bf.ID } if bf.Value != nil { - result.Value = new(ValueFormula) + result.Value = new(utils.ValueFormula) *result.Value = *bf.Value } if bf.RatingSubject != nil { @@ -180,7 +177,7 @@ func (bp *BalanceFilter) GetValue() float64 { return bp.Value.Static } // calculate using formula - formula, exists := valueFormulas[bp.Value.Method] + formula, exists := utils.ValueFormulas[bp.Value.Method] if !exists { return 0.0 } @@ -189,7 +186,7 @@ func (bp *BalanceFilter) GetValue() float64 { func (bp *BalanceFilter) SetValue(v float64) { if bp.Value == nil { - bp.Value = new(ValueFormula) + bp.Value = new(utils.ValueFormula) } bp.Value.Static = v } @@ -334,37 +331,3 @@ func (bf *BalanceFilter) ModifyBalance(b *Balance) { } b.SetDirty() // Mark the balance as dirty since we have modified and it should be checked by action triggers } - -//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 - err = json.Unmarshal([]byte(val), &vf) - if err == nil { - return &vf, err - } - return nil, errors.New("Invalid value: " + val) -} - -type valueFormula func(map[string]interface{}) float64 - -const ( - PERIODIC = "*periodic" -) - -var valueFormulas = map[string]valueFormula{ - PERIODIC: periodicFormula, -} - -func periodicFormula(params map[string]interface{}) float64 { - return 0.0 -} diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index d37b417b0..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: &ValueFormula{Static: 10}}}, - &Action{ActionType: "*topup", Balance: &BalanceFilter{Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), Value: &ValueFormula{Static: 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: &ValueFormula{Static: 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 851d474e9..e88b57218 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"":""*periodic"",""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: &ValueFormula{Static: 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: &ValueFormula{Static: 100}, + Value: &utils.ValueFormula{Static: 100}, Weight: utils.Float64Pointer(10), RatingSubject: utils.StringPointer("test"), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT")), @@ -887,7 +889,7 @@ func TestLoadActions(t *testing.T) { Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)), DestinationIDs: nil, Uuid: as2[0].Balance.Uuid, - Value: &ValueFormula{Static: 100}, + Value: &utils.ValueFormula{Static: 100}, Weight: utils.Float64Pointer(10), SharedGroups: utils.StringMapPointer(utils.NewStringMap("SG1")), TimingIDs: nil, @@ -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_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 6f85f3d89..a2a71470b 100644 --- a/engine/tp_reader.go +++ b/engine/tp_reader.go @@ -531,7 +531,7 @@ func (tpr *TpReader) LoadActions() (err error) { } if tpact.Units != "" && tpact.Units != utils.ANY { - vf, err := ParseBalanceFilterValue(tpact.Units) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } @@ -1007,7 +1007,7 @@ func (tpr *TpReader) LoadAccountActionsFiltered(qriedAA *TpAccountAction) error } if tpact.Units != "" && tpact.Units != utils.ANY { - vf, err := ParseBalanceFilterValue(tpact.Units) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } @@ -1355,7 +1355,7 @@ func (tpr *TpReader) LoadCdrStatsFiltered(tag string, save bool) (err error) { } if tpact.Units != "" && tpact.Units != utils.ANY { - vf, err := ParseBalanceFilterValue(tpact.Units) + vf, err := utils.ParseBalanceFilterValue(tpact.Units) if err != nil { return err } 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/value_formula.go b/utils/value_formula.go new file mode 100644 index 000000000..2a554f39e --- /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 ( + PERIODIC = "*periodic" +) + +var ValueFormulas = map[string]valueFormula{ + PERIODIC: periodicFormula, +} + +func periodicFormula(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..bb140a15d --- /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 := periodicFormula(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 := periodicFormula(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 := periodicFormula(params); x != 10/DaysInYear(now.Year()) { + t.Error("error caclulating value using formula: ", x) + } +}