Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
AJAYS
Explorer

Introduction


As the intent to move to new cloud-based platform SAP Analytical Cloud increases, customers are looking to see various approaches to extend, replace or introduce new planning processes.

Building a new planning application such as Product Cost Planning, Project Staff Planning, Sales Planning using business content delivered by SAP is one of salient capability of the SAC Solution which includes contents for various Line of Businesses and Industry Sectors.  However, this still leaves a gap in areas due to various reasons: some of which are mentioned below.

  1. The scope of planning, budgeting, or forecasting not covered fully.

  2. Not following local jurisdiction.

  3. Specific requirements by the customer not covered by content or accelerators.

  4. Setting up own workflow/logic for review and approval process or maybe custom requirements that are not covered by the above scenarios.


 

Overview


As part of this document, I will be taking an example to showcase a simple scenario that implements integration of actuals, enabling of reviewal and approval process with the ability to check status on the entire process, centrally.

Process kick starts with

  1. Central Administration ensuring Basic Pay, FTE and Master Data are loaded. Also, any NI and Pension % are maintained in the system. This is followed by initialising Input Budget Version. There is also an ability for Central Administrator to set/reset Status against a cost centre.

  2. Based on access to Cost centre(s), Staff Planners changes Input Budget Version by

    1. Adjusting Budgeted FTE Utilization of the Post Holder

    2. Making Adjustment to the Proposed Budget for the post



  3. Staff Planners Submits Input Budget Version to Reviewer in Budget Version.

  4. Based on access, Reviewer reviews the Budget and either accepts or rejects the same.

  5. Finally Central Administrator executes an overall report to track status on process. Depending on the status, trigger submission of Occupied to Revenue Budget.


 

Detail


Details of steps and how these can be implemented in SAC Application Designer are shown below.

Step 1:


Initialisation by Central Admin based on Actual Period 




  • This part of the processes relates to initialising of the Budget; a new budget is created by Planning Administrator. Central Administrator via the App visualises the basic wages, recurrent allowances, FTE, pension indicator and employee NI category against a post holder from the Occupied Model for the current Actual Period.  Subsequently an Input Budget is initialised against a Budget Year and Input Budget Version. This initialises the Input Budget Version (public. BUDGET_2022), Budget Year 2022 from Actual Period 202103 in the illustration below.  Initialisation for the above depends on parameters which are set as a properties and maintained against the BUDGET member id of Version Dimension (also shown below).


 

  • These parameters are set by planning administrator prior to initialisation of Budget Input Version.





  • Central Administrator initialises the Input Budget Version via the Application.




Figure 1: Central Admin App, performing initialisation.


This is achieved by creating an instance of Planning Model and reading the member properties into script variables during the onInitialization event as shown below.


         



var member = PlanningModel_1.getMember("Version", "public.Budget");
var prop = member.properties;
var actual_date = prop.ACTUAL_DT;
var budget_year = prop.BudgetYear;
var inp_budget_ver = "public." + prop.INPUT_BUDGET_VER;
Table_8.getDataSource().removeDimensionFilter("Date");
Table_8.getDataSource().setDimensionFilter("Date", "[Date].[YQM].&[" + actual_date + "]");
Table_8.getDataSource().removeDimensionFilter("Version");
Table_8.getDataSource().setDimensionFilter("Version", [inp_budget_ver, "public.Actual"]);
Table_8.getDataSource().removeDimensionFilter("BUDGET_YEAR");
Table_8.getDataSource().setDimensionFilter("BUDGET_YEAR", [budget_year, "#"]);
// Populate Script Variable
ACTUAL_DT = actual_date;
BUDGET_YEAR = budget_year;
INPUT_BUDGET_VERSION = inp_budget_ver;
BUD_VERSION = prop.INPUT_BUDGET_VER;

 

The above logic will set the Actual Date, Budget Year, Input Budget Version to the Script variables. This will also allow restricting the Actual Date, Source (Actuals in this case) and Target Version (Input Budget Version) & Budget Year in the output.


Logic to implement Initialisation of Input Budget Version, onClick event is shown below. A Data action to initialise needs to be created beforehand.


Create Data action object in Application Designer.





	var budget_year = BUDGET_YEAR; //prop.BudgetYear;
var inp_budget_ver = INPUT_BUDGET_VERSION; //prop.INPUT_BUDGET_VER;

Application.showBusyIndicator("Preparing execution...");
DataAction_1.setParameterValue("TargetVersion", inp_budget_ver);
DataAction_1.setParameterValue("SourceVersion", "public.Actual");
DataAction_1.setParameterValue("BudgetYear", budget_year);
var response = DataAction_1.execute();
if (response.status === DataActionExecutionResponseStatus.Success)
{
console.log("The execution of your data action was successful.");
if (Table_8.getPlanning().getPublicVersion(BUD_VERSION))
{
var ver = Table_8.getPlanning().getPublicVersion(BUD_VERSION).getId();
Table_8.getDataSource().removeDimensionFilter("Version");
Table_8.getDataSource().setDimensionFilter("Version", [ver, "public.Actual"]);
Table_8.getDataSource().removeDimensionFilter("BUDGET_YEAR");
console.log(ver);
}
Application.refreshData();
Application.hideBusyIndicator();
}


  • Maintain central assumptions. This could be Pay progression rates or Pension rates depending upon the functionality provided as part of the application. In this case Pension percentage and NI Rates have been introduced which forms part of the calculation for Total Cost of Employment (as explained later).



Figure 2: Central Admin App, entering NI and Pension rates (or assumptions).


 

  • Once the Input Budget Version is initialised, Status flag (ZCLOSE) can be reset as shown. Properties for Submitter (ZSUBM), Reviewer (ZREVW) and Completion Status (ZCLOSE) needs to be added to the Cost Centre beforehand. Process below will re-set the member values for the property ZCLOSE as Original (“O”) or set them to Approve (“A”). The member values for Submitter and Reviewer will be maintained manually.




Figure 3: Central Admin App, setting/resetting overall status.


 

Implemented as below on Button - OnClick



 var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");


if (RadioButtonGroup_1.getSelectedKey() === "SET")
{

for (var counter = 0; counter < allCstMembers.length; counter++)
{
PlanningModel_1.updateMembers("COSTCENTER", { id: allCstMembers[counter].id, properties:{CLOSE : "A"} } );
console.log( allCstMembers[counter].id);
console.log(allCstMembers[counter].properties);
}
}
else if (RadioButtonGroup_1.getSelectedKey() === "RESET")
{
for (var counter_2 = 0; counter_2 < allCstMembers.length; counter_2++)
{
PlanningModel_1.updateMembers("COSTCENTER", { id: allCstMembers[counter_2].id, properties:{CLOSE : "O"} } );
console.log( allCstMembers[counter_2].id);
console.log(allCstMembers[counter_2].properties);
}
}

Application.refreshData([Table_8.getDataSource()]);



Step 2:


Staff Planner makes adjustment to FTE & Proposed Budget.


After initialisation of Input Budget Version, system automatically proposes Total Cost of Employment.  This is calculated as below.


Total Cost of Employment = Salary * FTE  + Pension % + NI %


The system automatically calculates the budget at a post holder level using the total costs of employment calculation where:






    • Salary: Basic Salary and Recurrent Allowances

    • FTE: Budgeted FTE

    • Pension: Percentage based on current post holder’s pension scheme

    • NI: Percentage based on post holder’s NI Category




 

Staff Planner opens the Application (as shown below) and sees the post holders for the cost centres he/she is assigned as a submitter, with the Status Flag (ZCLOSE) as Original (“O) or Rejected (“REJ”) by reviewer. Budgeted FTE and Adjustments can be made at this stage prior to Staff Planner submitting this for Review.



Figure 4: Staff Planner App, adjusting the Input Budget Version.


Below onInitialization function is implemented to filter data for Input Budget Version and Budget Year which are read from corresponding properties of Version member, “public.Budget”.


Further logic to restrict Cost Centre where staff planner is assigned as a submitter, with the Status Flag (ZCLOSE) as Original (“O) or Rejected (“REJ”).



var member = PlanningModel_1.getMember("Version", "public.Budget");
var prop = member.properties;
var actual_date = prop.ACTUAL_DT;
var budget_year = prop.BudgetYear;
var inp_budget_ver = "public." + prop.INPUT_BUDGET_VER;

if (Table_5.getPlanning().getPublicVersion(BUD_VERSION))
{
var ver = Table_5.getPlanning().getPublicVersion(BUD_VERSION).getId();
Table_5.getDataSource().removeDimensionFilter("Version");
Table_5.getDataSource().setDimensionFilter("Version", [ver]);
Table_5.getDataSource().removeDimensionFilter("BUDGET_YEAR");
Table_5.getDataSource().setDimensionFilter("BUDGET_YEAR", [budget_year]);
}

// Set Cost Center filter based on Status
var cctr = ArrayUtils.create(Type.string);
var cctr_rev = ArrayUtils.create(Type.string);
var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");

Table_5.getDataSource().removeDimensionFilter("COSTCENTER");
var userid = cast(Type.string, Application.getUserInfo().id);

for (var counter = 0; counter < allCstMembers.length; counter++)
{
var prop_2 = allCstMembers[counter].properties;
var zsubm = cast(Type.string, prop_2.ZSUBM);
var zrevw = cast(Type.string, prop.ZREVW);
var zclose = cast(Type.string, prop_2.CLOSE);

if ((zsubm === userid) && (zclose === "O") || (zclose === "REJ"))
// Submitter but To be sumbitted
{
cctr.push(allCstMembers[counter].id);
}
}

if (cctr.length === 0)
{
Table_5.getDataSource().setDimensionFilter("COSTCENTER","#");
}
else
{
Table_5.getDataSource().setDimensionFilter("COSTCENTER",cctr);
}


// Setting Script Filter
ACT_PER = actual_date;
INPUT_BUDGET_VER = inp_budget_ver;
BUD_YEAR = budget_year;




Functionality that impact or displays the Status Flag is implemented as below.




  • Display Submission Status.


This would display status of Cost Centre(s), assigned to the Staff Planner as a Submitter.


Cost Centre(s) in Status






    • Original: To be Submitted, “O”

    • Submitted, “S”

    • Rejected by Reviewer, “REJ”




The status is displayed on the popup screen as shown below.



Figure 5: Staff Planner Application, status of Cost Center where planner is assigned as Submitter.


This is implemented (as below) when user clicks “Display Submission Status” button. A popup as shown above displays Cost Centre in different dropdowns depending upon their respective status.




var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");

var userid = cast(Type.string, Application.getUserInfo().id);

for (var counter = 0; counter < allCstMembers.length; counter++)
{
var prop = allCstMembers[counter].properties;
var zsubm = cast(Type.string, prop.ZSUBM);
// var zrevw = cast(Type.string, prop.ZREVW);
var zclose = cast(Type.string, prop.CLOSE);
console.log(userid);
console.log(zsubm);
console.log(zclose);
if (zsubm === userid && zclose === "O")
// Submitter but To be sumbitted
{
Dropdown_1.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}
if ( (zsubm === userid) && (zclose === "S") )
// Submitter but Submitted
{
Dropdown_2.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}
if ( (zsubm === userid) && (zclose === "REJ") )
// Submitter but Rejected
{
Dropdown_3.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}


}

Popup_1.open();

 

Step 3:


Staff Planner creates Budget Version and submits to Reviewer



Create Budget Version from the Input Budget Version for Cost Centre in the view. On Creation of Budget Version, list is regenerated for Cost Centres that are yet to be submitted. Finally display list of Post Holders in Cost Centres that are yet to be submitted, ie. Original (ZCLOSE = “O”) or Rejected (ZCLOSE = “REJ”), achieved as below.


Select the Cost Centres that are restricted/filtered in the current Table into an array of strings.



Application.showBusyIndicator("Creating Budget...");
var V_Selection = Table_5.getDataSource().getDataSelections() ;

var cctr_res = ArrayUtils.create(Type.string);
var prev_cctr = cast(Type.string, " ");
if (V_Selection.length)
{
for (var res_ctr = 0; res_ctr < V_Selection.length; res_ctr++)
{
if ( V_Selection[res_ctr].COSTCENTER === prev_cctr)
{

}
else
{
cctr_res.push(V_Selection[res_ctr].COSTCENTER);
prev_cctr = V_Selection[res_ctr].COSTCENTER;
}
}


  • Pass parameters and execute Data action to create Occupied Budget Version



    • Cost Centres (Array of Strings, as in the selection)

    • Target Version: Budget Version (public.Budget in this case)

    • Source Version: Input Budget Version (public.BUDGET_2022 in this case)




// Set Parameter 1	
DataAction_1.setParameterValue("CCtr", cctr_res);
}

// Set Parameter 2
DataAction_1.setParameterValue("TargetVersion", "public.Budget");

// Set Parameter 3
DataAction_1.setParameterValue("SourceVersion", INPUT_BUDGET_VER);

// Execute Action
var response = DataAction_1.execute();

On Successful execution of Data Action




  • Set Status Flag (ZCLOSE) as “S”: Submitted for Cost Centres restricted/filtered in the current view.


if (response.status === DataActionExecutionResponseStatus.Success) {
console.log("The execution of your data action was successful.");

// Update Status to Success
for (var sub_ctr = 0; sub_ctr < cctr_res.length; sub_ctr++)
{
PlanningModel_1.updateMembers("COSTCENTER", { id: cctr_res[sub_ctr], properties:{CLOSE : "S"} } );
}


  • Regenerate list of Cost Centres that are yet to be submitted, ie. Original (ZCLOSE = “O”) or Rejected (ZCLOSE = “REJ”)


// Set Cost Center filter based on Status
var cctr = ArrayUtils.create(Type.string);
var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");

var userid = cast(Type.string, Application.getUserInfo().id);

for (var counter = 0; counter < allCstMembers.length; counter++)
{
var prop_2 = allCstMembers[counter].properties;
var zsubm = cast(Type.string, prop_2.ZSUBM);
var zclose = cast(Type.string, prop_2.CLOSE);
console.log(userid);
console.log(zsubm);
console.log(zclose);
if ((zsubm === userid))
// User is a Submitter but CCenter is yet to be sumbitted
{

if (zclose === "O")
{
console.log(zclose);
cctr.push(allCstMembers[counter].id);
}

if (zclose === "REJ")
{
console.log(zclose);
cctr.push(allCstMembers[counter].id);
}
}

}


  • Finally set the Dimension Filter with Cost Centres that are yet to be Submitted and Trigger the Data Refresh on the Application.


	      Table_5.getDataSource().removeDimensionFilter("COSTCENTER");
Table_5.getDataSource().setDimensionFilter("COSTCENTER",cctr);

Application.refreshData();
Application.hideBusyIndicator();

}
else
{ // response.status === DataActionExecutionResponseStatus.Error
console.log("Sorry, the execution of your data action failed.");
}


if (Table_5.getPlanning().getPublicVersion(BUD_VERSION).isDirty() )
{
Table_5.getPlanning().getPublicVersion(BUD_VERSION).publish();
}

Step 4:


Reviewer reviews Budget 


 Based on access, Reviewer reviews the Budget and either accepts or rejects the same.  Application allows for entering the reason for rejection, which can be viewed by Submitter   from his/her view of the application.


Application opens by displaying Cost Centres for which reviewer has access to, this displays cost centres and the position holders with the total Budget. By default, it displays cost centres which are either in Submitted, Rejected or Approved Status.


 


 

Function onInitialization is implemented as below.


 
// Set Cost Center filter based on Status
var cctr_rev = ArrayUtils.create(Type.string);
var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");
var userid = cast(Type.string, Application.getUserInfo().id);

for (var counter = 0; counter < allCstMembers.length; counter++)
{
var prop_2 = allCstMembers[counter].properties;
var zrevw = cast(Type.string, prop_2.ZREVW);
var zclose = cast(Type.string, prop_2.CLOSE);
console.log(zrevw);
console.log(zclose);

if ((zrevw === userid) && (zclose === "S" || zclose ==="REJ" || zclose === "A") )
// Reviewer, CC in Submitted, Rejected or Approved State.
{
cctr_rev.push(allCstMembers[counter].id);
}

}

Table_3.getDataSource().removeDimensionFilter("COSTCENTER");

if (cctr_rev.length === 0)
{
Table_3.getDataSource().setDimensionFilter("COSTCENTER","#");
}
else
{
Table_3.getDataSource().setDimensionFilter("COSTCENTER",cctr_rev);
}


As part of the review process, reviewer can Accept, Reject, Display Status, Switch between Plan & View Mode, Adjust, Publish Budget and Finally Submit to Revenue Budget.    Some of the above functionalities described are implemented as below.


 

  • Reject Cost Centre(s)


A reviewer can select Cost Centre(s) and Reject them. During the rejection, reviewer can enter comment giving reason for rejection. Once rejected, this cost centre can be visible from submitters view for re-entering the data.



User can reject one or multiple CC(s) and can provide reason for rejection all at once or on Cost Centre by Cost Centre basis. The implementation for the onClick function is detailed below.



var V_Selection = Table_3.getDataSource().getDataSelections() ; 
console.log("v_selection");
console.log(V_Selection);

var cctr_res = ArrayUtils.create(Type.string);
var prev_cctr = cast(Type.string, " ");
if (V_Selection.length)
{
for (var res_ctr = 0; res_ctr < V_Selection.length; res_ctr++)
{
if ( V_Selection[res_ctr].COSTCENTER === prev_cctr)
{

}
else
{
console.log("V_Selection" + V_Selection[res_ctr].COSTCENTER);
console.log("Previous Ctr" + prev_cctr);
cctr_res.push(V_Selection[res_ctr].COSTCENTER);
prev_cctr = V_Selection[res_ctr].COSTCENTER;
PlanningModel_1.updateMembers("COSTCENTER", { id: V_Selection[res_ctr].COSTCENTER, properties:{CLOSE : "REJ"} } );
PlanningModel_1.updateMembers("COSTCENTER", { id: V_Selection[res_ctr].COSTCENTER, properties:{ZCOMMENT : InputField_1.getValue()} } );
}
}

}

InputField_1.setValue("");
Application.showBusyIndicator("Rejecting ...");
Application.refreshData();
Application.hideBusyIndicator();


  • Approve Cost Centre(s)


Likewise, reviewer can approve Budget Occupied Data for the Cost Centre(s)



The implementation for the onClick function is detailed below.



var V_Selection = Table_3.getDataSource().getDataSelections() ; 
console.log("v_selection");
console.log(V_Selection);

var cctr_res = ArrayUtils.create(Type.string);
var prev_cctr = cast(Type.string, " ");
if (V_Selection.length)
{
for (var res_ctr = 0; res_ctr < V_Selection.length; res_ctr++)
{
if ( V_Selection[res_ctr].COSTCENTER === prev_cctr)
{

}
else
{
console.log("V_Selection" + V_Selection[res_ctr].COSTCENTER);
console.log("Previous Ctr" + prev_cctr);
cctr_res.push(V_Selection[res_ctr].COSTCENTER);
prev_cctr = V_Selection[res_ctr].COSTCENTER;
PlanningModel_1.updateMembers("COSTCENTER", { id: V_Selection[res_ctr].COSTCENTER, properties:{CLOSE : "A"} } );
PlanningModel_1.updateMembers("COSTCENTER", { id: V_Selection[res_ctr].COSTCENTER, properties:{ZCOMMENT : " "} } );
}
}

}
Application.showBusyIndicator("Approving ...");
Application.refreshData();
Application.hideBusyIndicator();


  • Display Submission Status


Reviewer can display status of Cost Centre(s) assigned to him/her, using the functionality.




Figure 7: Budget Reviewer Application, status of Cost Center where user is assigned as Reviewer.


This is implemented as below.



var allCstMembers = PlanningModel_1.getMembers("COSTCENTER");

var userid = cast(Type.string, Application.getUserInfo().id);

for (var counter = 0; counter < allCstMembers.length; counter++)
{
var prop = allCstMembers[counter].properties;
var zsubm = cast(Type.string, prop.ZSUBM);
var zrevw = cast(Type.string, prop.ZREVW);
var zclose = cast(Type.string, prop.CLOSE);
console.log(userid);
console.log(zsubm);
console.log(zclose);
if (zrevw === userid && zclose === "O")
// Reviewer but To be sumbitted
{
Dropdown_4.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}
if ( (zrevw === userid) && (zclose === "S") )
// Reviewer but Submitted
{
Dropdown_5.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}
if ( (zsubm === userid) && (zclose === "REJ") )
// Reviewer but Rejected
{
Dropdown_6.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}
if ( (zsubm === userid) && (zclose === "A") )
// Reviewer but Approved
{
Dropdown_7.addItem(allCstMembers[counter].id);
console.log( allCstMembers[counter].id);
}

}

Popup_2.open();

Step 5: 

Central Administrator reviews overall status


Finally Central administrator reviews the overall status of the Occupied Budget process by reviewing the Status Tracking Report.  This would indicate areas that are behind schedule or need progress.



Figure 8: Central Admin Application, reviews status of all Cost Centers 



Conclusion


 

This completes an example of how APIs can be used to automate the Planning Process right from Initialising, Submitting, Review and finally approving Budget.  This has all been possible due to recent release from SAP Analytics Cloud Planning which allows following Data action components.

 

While using the above components triggered Data Actions automatically, meaning there was no chance of user entering invalid parameters. Also, there is now an ability to performing subsequent steps based on status of Data action execution, meaning multiple data actions could now be triggered sequentially without much user interaction. This also gives the ability to automate process by making use of Planning Model methods to insert, update or delete members from dimensions. The scenario above is an explanation of how these functions can be used.  This can be changed to suit the customer requirements, making use of API Functions. There is a lot more that can be achieved!!

 

References


For complete list of objects, functions, properties, methods, and events available that can be used while writing the script, follow the latest link

https://saphanajourney.com/wp-content/uploads/2021/03/DeveloperHandbookSACAnalyticsDesigner.pdf
Labels in this area