SetAccountAction API and filter for RESET_TRIGGERS

This commit is contained in:
Radu Ioan Fericean
2013-07-19 20:06:06 +03:00
parent 6c8d2c7187
commit 210f290e67
13 changed files with 239 additions and 148 deletions

View File

@@ -238,8 +238,8 @@ func (self *Apier) AddAccount(attr *AttrAccount, reply *float64) error {
}
type AttrSetAccountAction struct {
TPid string
RateProfileId string
TPid string
AccountActionId string
}
// Process dependencies and load a specific rating profile from storDb into dataDb.
@@ -249,8 +249,15 @@ func (self *Apier) SetAccountAction(attrs AttrSetAccountAction, reply *string) e
}
dbReader := rater.NewDbReader(self.StorDb, self.DataDb, attrs.TPid)
if err := dbReader.LoadAccountActionByTag(attrs.RateProfileId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
rater.AccLock.Guard(attrs.AccountActionId, func() (float64, error) {
if err := dbReader.LoadAccountActionsByTag(attrs.AccountActionId); err != nil {
return 0, fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
return 0, nil
})
if self.Sched != nil {
self.Sched.LoadActionTimings(self.DataDb)
self.Sched.Restart()
}
*reply = "OK"
return nil

View File

@@ -331,10 +331,65 @@ AddAcount
Example
AddAccount(attr \*AttrAccount, reply \*float64)
Apier.SetAccountAction
++++++++++++++++++++++
Process dependencies and load a specific rating profile from storDb into dataDb.
**Request**:
Data:
::
type AttrSetAccountAction struct {
TPid string
AccountActionId string
}
Mandatory parameters: ``[]string{"TPid", "AccountActionId"}``
*JSON sample*:
::
{
"id": 0,
"method": "Apier.SetAccountAction",
"params": [
{
"AccountActionId": "ACC_SAMPLE_1",
"TPid": "TPID_SAMPLE_1"
}
]
}
**Reply**:
Data:
::
string
Possible answers:
``OK`` - Success.
*JSON sample*:
::
{
"error": null,
"id": 0,
"result": "OK"
}
**Errors**:
``MANDATORY_IE_MISSING`` - Mandatory parameter missing from request.
``SERVER_ERROR`` - Server error occurred.
RatingProfiles
~~~~~~~~~~~~~~
.. toctree::
:maxdepth: 2

View File

@@ -88,7 +88,7 @@ func logAction(ub *UserBalance, a *Action) (err error) {
}
func resetTriggersAction(ub *UserBalance, a *Action) (err error) {
ub.resetActionTriggers()
ub.resetActionTriggers(a)
return
}
@@ -180,7 +180,7 @@ func genericReset(ub *UserBalance) {
}
ub.MinuteBuckets = make([]*MinuteBucket, 0)
ub.UnitCounters = make([]*UnitsCounter, 0)
ub.resetActionTriggers()
ub.resetActionTriggers(nil)
}
// Structure to store actions according to weight

View File

@@ -415,7 +415,7 @@ func TestActionResetTriggres(t *testing.T) {
Id: "TEST_UB",
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetTriggersAction(ub, nil)
@@ -424,13 +424,40 @@ func TestActionResetTriggres(t *testing.T) {
}
}
func TestActionResetTriggresExecutesThem(t *testing.T) {
ub := &UserBalance{
Id: "TEST_UB",
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetTriggersAction(ub, nil)
if ub.ActionTriggers[0].Executed == true || ub.BalanceMap[CREDIT][0].Value == 12 {
t.Error("Reset triggers action failed!")
}
}
func TestActionResetTriggresActionFilter(t *testing.T) {
ub := &UserBalance{
Id: "TEST_UB",
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetTriggersAction(ub, &Action{BalanceId: SMS})
if ub.ActionTriggers[0].Executed == false || ub.ActionTriggers[1].Executed == false {
t.Error("Reset triggers action failed!")
}
}
func TestActionSetPostpaid(t *testing.T) {
ub := &UserBalance{
Id: "TEST_UB",
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
setPostpaidAction(ub, nil)
@@ -445,7 +472,7 @@ func TestActionSetPrepaid(t *testing.T) {
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
setPrepaidAction(ub, nil)
@@ -460,8 +487,8 @@ func TestActionResetPrepaid(t *testing.T) {
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetPrepaidAction(ub, nil)
if ub.Type != UB_TYPE_PREPAID ||
@@ -479,8 +506,8 @@ func TestActionResetPostpaid(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: SMS, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetPostpaidAction(ub, nil)
if ub.Type != UB_TYPE_POSTPAID ||
@@ -498,7 +525,7 @@ func TestActionTopupResetCredit(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}
@@ -518,7 +545,7 @@ func TestActionTopupResetMinutes(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}}
@@ -539,7 +566,7 @@ func TestActionTopupCredit(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}
@@ -559,7 +586,7 @@ func TestActionTopupMinutes(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}}
@@ -580,7 +607,7 @@ func TestActionDebitCredit(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}
@@ -600,7 +627,7 @@ func TestActionDebitMinutes(t *testing.T) {
Type: UB_TYPE_PREPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: MINUTES, MinuteBucket: &MinuteBucket{Seconds: 5, Weight: 20, Price: 1, DestinationId: "NAT"}}
@@ -621,7 +648,7 @@ func TestActionResetAllCounters(t *testing.T) {
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
resetCountersAction(ub, nil)
@@ -648,7 +675,7 @@ func TestActionResetCounterMinutes(t *testing.T) {
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: MINUTES}
@@ -676,7 +703,7 @@ func TestActionResetCounterCREDIT(t *testing.T) {
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}},
}
a := &Action{BalanceId: CREDIT, Direction: OUTBOUND}

View File

@@ -394,17 +394,10 @@ func (csvr *CSVReader) LoadActions() (err error) {
ExpirationDate: expDate,
}
} else {
price, percent := 0.0, 0.0
value, err := strconv.ParseFloat(record[8], 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action price: %v", err))
}
if record[7] == PERCENT {
percent = value
}
if record[7] == ABSOLUTE {
price = value
}
minutesWeight, err := strconv.ParseFloat(record[9], 64)
if err != nil {
return errors.New(fmt.Sprintf("Could not parse action minutes weight: %v", err))
@@ -423,8 +416,8 @@ func (csvr *CSVReader) LoadActions() (err error) {
MinuteBucket: &MinuteBucket{
Seconds: units,
Weight: minutesWeight,
Price: price,
Percent: percent,
Price: value,
PriceType: record[7],
DestinationId: record[6],
ExpirationDate: expDate,
},

View File

@@ -313,7 +313,7 @@ func (dbr *DbReader) LoadAccountActions() (err error) {
tag := fmt.Sprintf("%s:%s:%s", aa.Direction, aa.Tenant, aa.Account)
aTriggers, exists := dbr.actionsTriggers[aa.ActionTriggersTag]
if aa.ActionTriggersTag != "" && !exists {
// only return error if there was something ther for the tag
// only return error if there was something there for the tag
return errors.New(fmt.Sprintf("Could not get action triggers for tag %v", aa.ActionTriggersTag))
}
ub := &UserBalance{
@@ -341,27 +341,84 @@ func (dbr *DbReader) LoadAccountActionsByTag(tag string) error {
return err
}
accountAction := accountActions[0]
actionTimingsMap, err := dbr.storDb.GetTpActionTimings(dbr.tpid, accountAction.ActionTimingsTag)
if err != nil {
return err
}
actionTriggersMap, err := dbr.storDb.GetTpActionTriggers(dbr.tpid, accountAction.ActionTriggersTag)
if err != nil {
return err
}
id := fmt.Sprintf("%s:%s:%s", accountAction.Direction, accountAction.Tenant, accountAction.Account)
// collecting action ids
var actionsIds []string
for _, atms := range actionTimingsMap {
for _, atm := range atms {
actionsIds = append(actionsIds, atm.ActionsId)
var actionsIds []string // collects action ids
// action timings
if accountAction.ActionTimingsTag != "" {
// get old userBalanceIds
var exitingUserBalanceIds []string
exitingActionTimings, err := dbr.dataDb.GetActionTimings(accountAction.ActionTimingsTag)
if err == nil && len(exitingActionTimings) > 0 {
// all action timings from a specific tag shuld have the same list of user balances from the first one
exitingUserBalanceIds = exitingActionTimings[0].UserBalanceIds
}
actionTimingsMap, err := dbr.storDb.GetTpActionTimings(dbr.tpid, accountAction.ActionTimingsTag)
if err != nil {
return err
}
var actionTimings []*ActionTiming
for _, at := range actionTimingsMap[accountAction.ActionTimingsTag] {
_, exists := dbr.actions[at.ActionsId]
if !exists {
return errors.New(fmt.Sprintf("ActionTiming: Could not load the action for tag: %v", at.ActionsId))
}
t, exists := dbr.timings[at.Tag]
if !exists {
return errors.New(fmt.Sprintf("ActionTiming: Could not load the timing for tag: %v", at.Tag))
}
actTmg := &ActionTiming{
Id: utils.GenUUID(),
Tag: at.Tag,
Weight: at.Weight,
Timing: &Interval{
Months: t.Months,
MonthDays: t.MonthDays,
WeekDays: t.WeekDays,
StartTime: t.StartTime,
},
ActionsId: at.ActionsId,
}
// collect action ids from timings
actionsIds = append(actionsIds, actTmg.ActionsId)
//add user balance id if no already in
found := false
for _, ubId := range exitingUserBalanceIds {
if ubId == id {
found = true
break
}
}
if !found {
at.UserBalanceIds = append(exitingUserBalanceIds, id)
}
actionTimings = append(actionTimings, actTmg)
}
// write action timings
err = dbr.dataDb.SetActionTimings(accountAction.ActionTimingsTag, actionTimings)
if err != nil {
return err
}
}
for _, atrs := range actionTriggersMap {
for _, atr := range atrs {
// action triggers
var actionTriggers ActionTriggerPriotityList
if accountAction.ActionTriggersTag != "" {
actionTriggersMap, err := dbr.storDb.GetTpActionTriggers(dbr.tpid, accountAction.ActionTriggersTag)
if err != nil {
return err
}
actionTriggers = actionTriggersMap[accountAction.ActionTriggersTag]
// collect action ids from triggers
for _, atr := range actionTriggers {
actionsIds = append(actionsIds, atr.ActionsId)
}
}
// actions
var acts map[string][]*Action
for _, actId := range actionsIds {
actions, err := dbr.storDb.GetTpActions(dbr.tpid, actId)
@@ -372,79 +429,19 @@ func (dbr *DbReader) LoadAccountActionsByTag(tag string) error {
acts[id] = act
}
}
// write action timings
for k, atms := range actionTimingsMap {
err = dbr.storDb.SetActionTimings(k, atms)
if err != nil {
return err
}
}
// write actions
for k, as := range acts {
err = dbr.storDb.SetActions(k, as)
err = dbr.dataDb.SetActions(k, as)
if err != nil {
return err
}
}
activationPeriods := make(map[string]*ActivationPeriod)
resultRatingProfile := &RatingProfile{Id: tag}
rpm, err := dbr.storDb.GetTpRatingProfiles(dbr.tpid, tag)
ub, err := dbr.dataDb.GetUserBalance(id)
if err != nil {
return err
} else if len(rpm) == 0 {
return fmt.Errorf("No RateProfile with id: %s", tag)
}
for _, ratingProfile := range rpm {
resultRatingProfile.FallbackKey = ratingProfile.FallbackKey // it will be the last fallback key
at := time.Unix(ratingProfile.activationTime, 0)
drtm, err := dbr.storDb.GetTpDestinationRateTimings(dbr.tpid, ratingProfile.destRatesTimingTag)
if err != nil {
return err
} else if len(drtm) == 0 {
return fmt.Errorf("No DestRateTimings profile with id: %s", ratingProfile.destRatesTimingTag)
}
for _, destrateTiming := range drtm {
tm, err := dbr.storDb.GetTpTimings(dbr.tpid, destrateTiming.TimingsTag)
if err != nil {
return err
} else if len(tm) == 0 {
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.TimingsTag)
}
destrateTiming.timing = tm[destrateTiming.TimingsTag]
drm, err := dbr.storDb.GetTpDestinationRates(dbr.tpid, destrateTiming.DestinationRatesTag)
if err != nil {
return err
} else if len(drm) == 0 {
return fmt.Errorf("No Timings profile with id: %s", destrateTiming.DestinationRatesTag)
}
for _, drate := range drm[destrateTiming.DestinationRatesTag] {
rt, err := dbr.storDb.GetTpRates(dbr.tpid, drate.RateTag)
if err != nil {
return err
} else if len(rt) == 0 {
return fmt.Errorf("No Rates profile with id: %s", drate.RateTag)
}
drate.Rate = rt[drate.RateTag]
if _, exists := activationPeriods[destrateTiming.Tag]; !exists {
activationPeriods[destrateTiming.Tag] = &ActivationPeriod{}
}
activationPeriods[destrateTiming.Tag].AddIntervalIfNotPresent(destrateTiming.GetInterval(drate))
dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag)
if err != nil {
return err
}
for _, destination := range dm {
ap := activationPeriods[ratingProfile.destRatesTimingTag]
newAP := &ActivationPeriod{ActivationTime: at}
newAP.Intervals = append(newAP.Intervals, ap.Intervals...)
resultRatingProfile.AddActivationPeriodIfNotPresent(destination.Id, newAP)
dbr.dataDb.SetDestination(destination)
}
}
}
}
ub.ActionTriggers = actionTriggers
return dbr.dataDb.SetRatingProfile(resultRatingProfile)
return dbr.dataDb.SetUserBalance(ub)
}

View File

@@ -27,13 +27,18 @@ import (
type MinuteBucket struct {
Seconds float64
Weight float64
Price float64
Percent float64 // percentage from standard price
Price float64 // percentage from standard price or absolute value depending on Type
PriceType string
DestinationId string
ExpirationDate time.Time
precision int
}
const (
PERCENT = "PERCENT"
ABSOLUTE = "ABSOLUTE"
)
// Returns the available number of seconds for a specified credit
func (mb *MinuteBucket) GetSecondsForCredit(credit float64) (seconds float64) {
seconds = mb.Seconds
@@ -49,7 +54,7 @@ func (mb *MinuteBucket) Clone() *MinuteBucket {
Seconds: mb.Seconds,
Weight: mb.Weight,
Price: mb.Price,
Percent: mb.Percent,
PriceType: mb.PriceType,
DestinationId: mb.DestinationId,
}
}
@@ -59,7 +64,7 @@ func (mb *MinuteBucket) Equal(o *MinuteBucket) bool {
return mb.DestinationId == o.DestinationId &&
mb.Weight == o.Weight &&
mb.Price == o.Price &&
mb.Percent == o.Percent
mb.PriceType == o.PriceType
}
func (mb *MinuteBucket) IsExpired() bool {

View File

@@ -57,16 +57,16 @@ func TestMinutBucketSortPrice(t *testing.T) {
}
func TestMinutBucketEqual(t *testing.T) {
mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, Percent: 1, DestinationId: ""}
mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, Percent: 1, DestinationId: ""}
mb3 := &MinuteBucket{Weight: 1, precision: 1, Price: 2, Percent: 1, DestinationId: ""}
mb1 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: ABSOLUTE, DestinationId: ""}
mb2 := &MinuteBucket{Weight: 1, precision: 1, Price: 1, PriceType: ABSOLUTE, DestinationId: ""}
mb3 := &MinuteBucket{Weight: 1, precision: 1, Price: 2, PriceType: ABSOLUTE, DestinationId: ""}
if !mb1.Equal(mb2) || mb2.Equal(mb3) {
t.Error("Equal failure!", mb1, mb2, mb3)
}
}
func TestMinutBucketClone(t *testing.T) {
mb1 := &MinuteBucket{Seconds: 1, Weight: 2, Price: 3, Percent: 4, DestinationId: "5"}
mb1 := &MinuteBucket{Seconds: 1, Weight: 2, Price: 3, PriceType: ABSOLUTE, DestinationId: "5"}
mb2 := mb1.Clone()
if mb1 == mb2 || !reflect.DeepEqual(mb1, mb2) {
t.Error("Cloning failure: ", mb1, mb2)

View File

@@ -522,7 +522,7 @@ func (mb *MinuteBucket) Store() (result string, err error) {
result += strconv.FormatFloat(mb.Seconds, 'f', -1, 64) + ";"
result += strconv.FormatFloat(mb.Weight, 'f', -1, 64) + ";"
result += strconv.FormatFloat(mb.Price, 'f', -1, 64) + ";"
result += strconv.FormatFloat(mb.Percent, 'f', -1, 64) + ";"
result += mb.PriceType + ";"
result += mb.DestinationId
return
}
@@ -533,7 +533,7 @@ func (mb *MinuteBucket) Restore(input string) error {
mb.Seconds, _ = strconv.ParseFloat(elements[0], 64)
mb.Weight, _ = strconv.ParseFloat(elements[1], 64)
mb.Price, _ = strconv.ParseFloat(elements[2], 64)
mb.Percent, _ = strconv.ParseFloat(elements[3], 64)
mb.PriceType = elements[3]
mb.DestinationId = elements[4]
return nil
}

View File

@@ -990,13 +990,7 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er
ExpirationDate: expDate,
}
} else {
var percent, price float64
if rate_type == PERCENT {
percent = rate
}
if rate_type == ABSOLUTE {
price = rate
}
var price float64
a = &Action{
Id: utils.GenUUID(),
ActionType: action,
@@ -1008,7 +1002,7 @@ func (self *SQLStorage) GetTpActions(tpid, tag string) (map[string][]*Action, er
Seconds: units,
Weight: minutes_weight,
Price: price,
Percent: percent,
PriceType: rate_type,
DestinationId: destinations_tag,
ExpirationDate: expDate,
},

View File

@@ -28,10 +28,10 @@ func TestUnitsCounterStoreRestore(t *testing.T) {
Direction: OUTBOUND,
BalanceId: SMS,
Units: 100,
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
r, err := uc.Store()
if err != nil || r != "OUT/SMS/100/0;20;1;0;NAT,0;10;10;0;RET" {
if err != nil || r != "OUT/SMS/100/0;20;1;;NAT,0;10;10;ABSOLUTE;RET" {
t.Errorf("Error serializing units counter: %v", string(r))
}
o := &UnitsCounter{}
@@ -46,7 +46,7 @@ func TestUnitsCounterAddMinuteBucket(t *testing.T) {
Direction: OUTBOUND,
BalanceId: SMS,
Units: 100,
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
uc.addMinutes(20, "test")
if len(uc.MinuteBuckets) != 2 {
@@ -59,7 +59,7 @@ func TestUnitsCounterAddMinuteBucketExists(t *testing.T) {
Direction: OUTBOUND,
BalanceId: SMS,
Units: 100,
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
uc.addMinutes(5, "0723")
if len(uc.MinuteBuckets) != 2 || uc.MinuteBuckets[0].Seconds != 15 {

View File

@@ -38,9 +38,6 @@ const (
TRAFFIC = "INTERNET"
TRAFFIC_TIME = "INTERNET_TIME"
MINUTES = "MINUTES"
// Price types
PERCENT = "PERCENT"
ABSOLUTE = "ABSOLUTE"
)
/*
@@ -283,7 +280,7 @@ func (ub *UserBalance) debitBalance(balanceId string, amount float64, count bool
}
// Scans the action trigers and execute the actions for which trigger is met
func (ub *UserBalance) executeActionTriggers() {
func (ub *UserBalance) executeActionTriggers(a *Action) {
ub.ActionTriggers.Sort()
for _, at := range ub.ActionTriggers {
if at.Executed {
@@ -291,6 +288,13 @@ func (ub *UserBalance) executeActionTriggers() {
// the next reset (see RESET_TRIGGERS action type)
continue
}
if a != nil && (at.BalanceId != a.BalanceId ||
at.Direction != a.Direction ||
(a.MinuteBucket != nil &&
(at.ThresholdType != a.MinuteBucket.PriceType ||
at.ThresholdValue != a.MinuteBucket.Price))) {
continue
}
if strings.Contains(at.ThresholdType, "COUNTER") {
for _, uc := range ub.UnitCounters {
if uc.BalanceId == at.BalanceId {
@@ -358,10 +362,19 @@ func (ub *UserBalance) executeActionTriggers() {
}
// Mark all action trigers as ready for execution
func (ub *UserBalance) resetActionTriggers() {
// If the action is not nil it acts like a filter
func (ub *UserBalance) resetActionTriggers(a *Action) {
for _, at := range ub.ActionTriggers {
if a != nil && (at.BalanceId != a.BalanceId ||
at.Direction != a.Direction ||
(a.MinuteBucket != nil &&
(at.ThresholdType != a.MinuteBucket.PriceType ||
at.ThresholdValue != a.MinuteBucket.Price))) {
continue
}
at.Executed = false
}
ub.executeActionTriggers(a)
}
// Returns the unit counter that matches the specified action type
@@ -396,7 +409,7 @@ func (ub *UserBalance) countUnits(a *Action) {
} else {
unitsCounter.Units += a.Units
}
ub.executeActionTriggers()
ub.executeActionTriggers(nil)
}
// Create minute counters for all triggered actions that have actions operating on minute buckets

View File

@@ -100,7 +100,7 @@ func TestUserBalanceStoreRestore(t *testing.T) {
Direction: OUTBOUND,
BalanceId: SMS,
Units: 100,
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
at := &ActionTrigger{
Id: "some_uuid",
@@ -117,7 +117,7 @@ func TestUserBalanceStoreRestore(t *testing.T) {
Id: "rif",
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
UnitCounters: []*UnitsCounter{uc, uc},
ActionTriggers: ActionTriggerPriotityList{at, at, at},
}
@@ -361,7 +361,7 @@ func TestUserBalancedebitMinuteBucket(t *testing.T) {
Id: "rif",
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
newMb := &MinuteBucket{Weight: 20, Price: 1, DestinationId: "NEW"}
ub.debitMinuteBucket(newMb)
@@ -376,7 +376,7 @@ func TestUserBalancedebitMinuteBucketExists(t *testing.T) {
Id: "rif",
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
newMb := &MinuteBucket{Seconds: -10, Weight: 20, Price: 1, DestinationId: "NAT"}
ub.debitMinuteBucket(newMb)
@@ -390,7 +390,7 @@ func TestUserBalanceAddMinuteNil(t *testing.T) {
Id: "rif",
Type: UB_TYPE_POSTPAID,
BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
}
ub.debitMinuteBucket(nil)
if len(ub.MinuteBuckets) != 2 {
@@ -422,7 +422,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) {
Id: "TEST_UB",
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS"}},
}
ub.countUnits(&Action{BalanceId: CREDIT, Units: 1})
@@ -435,7 +435,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) {
t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds)
}
// we can reset them
ub.resetActionTriggers()
ub.resetActionTriggers(nil)
ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1})
if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 120 || ub.MinuteBuckets[0].Seconds != 30 {
t.Error("Error executing triggered actions", ub.BalanceMap[CREDIT+OUTBOUND][0].Value, ub.MinuteBuckets[0].Seconds)
@@ -447,7 +447,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) {
Id: "TEST_UB",
BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}},
UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}},
ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "MIN_COUNTER", ActionsId: "TEST_ACTIONS"}},
}
ub.countUnits(&Action{BalanceId: CREDIT, Units: 1})