Adventures in Groovy – Part 30: Dynamically Identify Actionable Months

Almost every ePBCS application will require the need to run business logic and execute data movements on specific months based on a scenario selected.  Almost every Data Form will require action to be taken dynamically based on the selection of the Scenario.  Assuming a fiscal year of Jan to Dec and Actuals being final through Jun,

  • processes on Actuals will run on Jan through Jun
  • processes on Budget/Plan will run on Jan through Dec
  • processes on Forecast will run on Jul through Dec

Prior to Groovy, this was done in an Essbase calculation by writing if statements based on the scenario selected in the POV and checking if the month was part of a variable range.  It might have looked something like this.

IF((@ISMBR("Plan") AND @ISMBR(&v_BudYear) AND @ISMBR(&v_BudMths)) OR
  (@ISMBR("Forecast") AND @ISMBR(&v_FcstYr1) AND @ISMBR(&v_FcstMths1)))
 ...
ENDIF

Groovy provides a much more efficient way to accomplish this.  Although it may be different based on specific needs, the example below will assume that Forecast and Actuals can be identified based on a variable that is updated monthly identifying the last month of Actuals.

Setting Up The Variables and Run Time Prompts

This application has a substitution variable named v_CurMonth used to identify the current reporting month of Actuals, or the last month Actuals are final.  This will be used in the Run Time Prompt as a default value in several places and this is required.

A Run Time Prompt is also required.  There is nothing unique about the RTP for this use and the application may likely already have one.  This will be a member Type, be connected to the Period dimension, and have a default value of the substitution variable above.  It would look similar to this.

Finally, a variable in the Groovy business rule must be created.  This variable will be set as an override value with the default value equal to the substitution variable created above.  In a Groovy business rule, a variable is instantiated by adding the following line at the top of the rule.  It looks like a comment, but it acts differently when it starts with RTPS:.

/*RTPS: {RTP_Month}*/

After this is added, the Variables tab of the business rule will show all the variable defined.  Set the variable so that it is hidden and is used as an override value.

The Code

At this point, all of the required variables are setup.  The first step is to define the months.  This can be hard coded since this is not something that will change frequently.  If this is added to a script and included in all the business rules it is needed, changing it would be easy if the months did change since it is in one place and shared throughout.

// Hard coded months 
def months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

Optionally, this can be set dynamically.  Either option, in my opinion, is acceptable.  The result of the following would produce the exact same value for months.

// Dynamically get the list of months based on the application's fiscal year 
Cube cube = operation.application.getCube("GP") 
Dimension productDim = operation.application.getDimension("Period", cube) 
def months = productDim.getEvaluatedMembers("ILvl0Descendants(YearTotal)", cube)

The next piece of this rule is to get the month from the variable.  This will be the key to identify which months are used in calculations that run on Actuals and Forecast.

// Get the month from the hidden RTP 
def actMonth = rtps.RTP_Month.toString()

The last step is to get the appropriate months based on the Scenario selected.  If Actuals is selected, all the elements in the months list from the first to the index of the month in the RTP are selected.  If Plan is selected, all the months are included.  This can be altered based on needs.  Some companies do mid year plans, for example, and may need to only execute the logic on the last 6 months.  If Forecast is selected, all the elements in the months list AFTER the RTP value to the end of the list are selected.

// Create dynamic list of months based on user selected Scenario
def useMonths = []
if(operation.grid.pov.find{it.dimName =='Scenario'}.essbaseMbrName.toString().toLowerCase().contains('plan')){
  // This will create a list of all months - assuming the the plan is for 12 months
  useMonths = months 
  }
if(operation.grid.pov.find{it.dimName =='Scenario'}.essbaseMbrName.toString().toLowerCase().contains('forecast')){
  // This will create a list all months after the actMonth variable - or the out months
  useMonths = months[months.findIndexOf{it == actMonth}+1..-1] 
  }
else{
  // This will create a list of all actual months
  useMonths = months[0..months.findIndexOf{it == actMonth}] 
  }

// Create delimited list for methods that don't require a List object
def useMonthsString = """\"${useMonths.join('", "')}\""""

Using useMonths and useMonthsString

The hard part is over.  Using the variable is the easy part.  It can be used anywhere the months needs to be change from all months to the identified months.

In a DataMap/SmartPush

operation.grid.getSmartPush("GP_SmartPush").execute(["Period":useMonthsString])

Same concept but overriding the entire POV (something more realistic in a real world example)

// This creates a variable for the povMap that includes all members in the existing POV
// This is used as a possible scenario but is not directly related to the content of this post
def povMap = operation.grid.pov.each{povMbrs["pov$it.dimName"] = "$it.essbaseMbrName"}
// Add the months to a map for the Period dimension
povMap['Period'] = useMonthsString
operation.grid.getSmartPush("GP_SmartPush").execute(povMap)

In a GridBuilder

builder.addColumn(['Period','Currency'], [ useMonths,['Local','USD'] ])

In an Essbase FIX statement

essCalc << """
FIX($useMonthsString)
"""

Finally

This could be added to a script and included in all the Business Rules that require such a variable.  It creates a variable once that can be used in many classes.  It improve maintainability and reduces replicating the same logic for each class.  If you have any suggestions, or have something you would like to share, please post a comment.