PPM Financial Planning – Update FIN_PLAN Values using API: How-to guide

In the picture: PPM > Portfolio Management > Financial Planning

My project had a requirement to add up actual values(actual and actual-manual) and copy to forecast values in the Financial Planning of the PPM Module. Even though this sounds very simple, anyone who knows PPM tables knows that it is more than what meets the eye.

I struggled a lot to find an apt way of achieving this. I tried many other processes like Function Module(FM) /RPM/FIN_PLAN_SAVE_DB. The problem with this FM was that all entries in the plan table belonging to a group had to be passed to this FM even when we had to update just one record in that group. I also explored the BADIs /RPM/EX_FIN_B_PLAN_BADI, /RPM/EX_FIN_PLAN, /RPM/FIN_CAP_PLAN . But I wanted something that can be called within in an FM which updates the /RPM/FIN_PLAN table with plan details. Finally while debugging I stumbled upon the API /rpm/cl_fin_cap_planning_api. This API was best suited for my requirement as I only had to pass the changed entry and it internally selects the rest of the plan entries in the group. I spent a lot of researching how to make use of this API specially to know how to pass the context to the API. I am writing this blog post to help my fellow developers.

The steps described in this blog post can also be used to update Capacity Planning tables.

Requirement: There are 2 views to capture actual cost – Actual(comes from ECC) and Actual-Manual(entered manually). The requirement is that the Forecast view should be calculated by adding the two costs – Actual and Actual-Manual. Even though this sounds very simple, anyone who knows PPM tables knows that it is more than what meets the eye.

After doing lot of research and debugging, I found out that the there is an API /rpm/cl_fin_cap_planning_api to do this. I have created an RFC Function Module that takes 1/more external-ID of the item(project) as input and updates the tables(/RPM/FIN_PLAN,  /RPM/FIN_GROUP) and in turn views of the Forecast.

This blog post is a step-by-step how-to guide to achieve this objective.

Steps:

  1. Get guid from table /RPM/ITEM_D for the external_id in the input table(IT_PROJ)
  2. Get required fields into 1 table using join from /rpm/item_d /rpm/fin_cat, /rpm/fin_group /rpm/fin_plan
  3. Add the values of actual and actual-manual costs of corresponding months and categories
  4. Update the forecast costs using API. Use get_plan_info method to pass the context**. Call initialize_planning method and finally fin_groups_modify  to update values. The API is well designed in the sense that only the changed fin_plan entries need to be passed and the API handles the rest.

** very crucial part..took me a while to figure out how to pass the context

  1. Very important to call the cl_inm_ppm_services=>save to commit work.
  2. Capture the messages and display.

FM Interface:

FUNCTION z_forecast_calculate_rfc . TYPES: BEGIN OF type_item_d, guid TYPE /rpm/tv_guid, external_id TYPE /rpm/tv_extid, portfolio_guid TYPE /rpm/tv_guid, END OF type_item_d, BEGIN OF type_fin_planf, guid TYPE rpm_tv_guid, period TYPE /rpm/tv_start_date, amount(16) TYPE p DECIMALS 2, END OF type_fin_planf , BEGIN OF type_fin_plan_tmp, guid TYPE rpm_tv_guid, plan_type TYPE /rpm/tv_fin_view, period TYPE /rpm/tv_start_date, amount(16) TYPE p DECIMALS 2, END OF type_fin_plan_tmp , BEGIN OF type_fin_plan, guid TYPE rpm_tv_guid, period TYPE /rpm/tv_start_date, amount(16) TYPE p DECIMALS 2, END OF type_fin_plan, BEGIN OF type_itemcatgrp, guid TYPE /rpm/tv_guid, external_id TYPE /rpm/tv_extid, portfolio_guid TYPE /rpm/tv_guid, guid_c TYPE /rpm/tv_guid, category TYPE /rpm/tv_cap_catg, guid_g TYPE rpm_tv_guid, group_id TYPE rpm_tv_extid, sponsor TYPE /rpm/tv_sponsor, parent_guid TYPE rpm_tv_guid, changed_by TYPE /rpm/tv_changed_by, changed_on TYPE /rpm/tv_timestamp, active TYPE /rpm/tv_active, original TYPE /rpm/tv_original, END OF type_itemcatgrp, BEGIN OF type_fin_plan_full, guid TYPE rpm_tv_guid, plan_type TYPE /rpm/tv_fin_view, period TYPE /rpm/tv_start_date, changed_on TYPE /rpm/tv_timestamp, amount TYPE /rpm/tv_curr_amount, currency TYPE /rpm/tv_currency, END OF type_fin_plan_full , BEGIN OF type_grpplan_full, guid TYPE /rpm/tv_guid, external_id TYPE /rpm/tv_extid, portfolio_guid TYPE /rpm/tv_guid, guid_c TYPE /rpm/tv_guid, category TYPE /rpm/tv_cap_catg, guid_g TYPE rpm_tv_guid, group_id TYPE rpm_tv_extid, sponsor TYPE /rpm/tv_sponsor, parent_guid TYPE rpm_tv_guid, active TYPE /rpm/tv_active, original TYPE /rpm/tv_original, guid_p TYPE rpm_tv_guid, plan_type TYPE /rpm/tv_fin_view, period TYPE /rpm/tv_start_date, changed_on TYPE /rpm/tv_timestamp, amount TYPE /rpm/tv_curr_amount, currency TYPE /rpm/tv_currency, END OF type_grpplan_full . "internal tables DATA: t_item_d TYPE TABLE OF type_item_d, t_fin_plan_coll TYPE HASHED TABLE OF type_fin_plan WITH UNIQUE KEY guid period, t_fin_plan_full73 TYPE TABLE OF type_fin_plan_full, t_fin_plan_api TYPE /rpm/tt_plan_api, t_fin_group_api TYPE /rpm/tt_fin_group_api, t_fin_plan TYPE TABLE OF type_fin_plan, t_date_range TYPE TABLE OF rsdatrange, t_int_range TYPE TABLE OF rsintrange, t_msgs TYPE /rpm/tt_messages, t_msgs_group TYPE /rpm/tt_messages, t_msgs_item TYPE /rpm/tt_messages, t_grp_plan TYPE TABLE OF type_grpplan_full, t_itemcatgrp TYPE TABLE OF type_itemcatgrp, t_filter_data TYPE /rpm/tt_filter_data_api, t_grpplan_full TYPE TABLE OF type_grpplan_full, t_fin_plan_tmp TYPE TABLE OF type_fin_plan_tmp, "work areas w_fin_plan TYPE type_fin_plan, w_fin_plan_tmp TYPE type_fin_plan_tmp, w_fin_plan_api TYPE /rpm/ts_plan_api, w_fin_group_api TYPE /rpm/ts_fin_group_api, w_grp_plan TYPE type_grpplan_full, w_grp_plan_temp TYPE type_grpplan_full, w_item_d TYPE type_item_d, w_date_range TYPE rsdatrange, w_fin_plan_full73 TYPE type_fin_plan_full, w_fin_plan_coll LIKE LINE OF t_fin_plan_coll, w_context TYPE /rpm/ts_object_hier, w_plan_info TYPE /rpm/ts_plan_info, "range table r_item TYPE RANGE OF /rpm/tv_guid, w_item LIKE LINE OF r_item, "variables v_cat_guid TYPE /rpm/tv_guid, v_subrc TYPE i ##needed, v_high_date TYPE sydatum, v_73 TYPE /rpm/tv_curr_amount, v_coll TYPE /rpm/tv_curr_amount, v_updated TYPE boolean, v_rejected TYPE boole_d ##needed. "constants CONSTANTS: c_hierarchy_type TYPE /rpm/tv_hier_type VALUE 'VCG', c_language TYPE laiso VALUE 'EN', c_parent_type TYPE /rpm/object_type VALUE 'RIH', c_object_type TYPE /rpm/object_type VALUE 'RIH', c_plantype_73 TYPE /rpm/tv_fin_view VALUE '73', c_plantype_75 TYPE /rpm/tv_fin_view VALUE '75', c_plantype_77 TYPE /rpm/tv_fin_view VALUE '77', c_cat_i05 TYPE /rpm/tv_cap_catg VALUE 'IT_I05', c_cat_i10 TYPE /rpm/tv_cap_catg VALUE 'IT_I10', c_fin_cap TYPE /rpm/tv_fin_cap VALUE '1', c_pipe(1) TYPE c VALUE '|', c_msgtype_s TYPE symsgty VALUE 'S', c_zit_bau TYPE /rpm/tv_item_id VALUE 'IT_BAU', c_zit_inf TYPE /rpm/tv_item_id VALUE 'IT_INF', c_zit_opm TYPE /rpm/tv_item_id VALUE 'IT_OPM', c_zit_sap TYPE /rpm/tv_item_id VALUE 'IT_SAP', c_sts_zf02 TYPE /rpm/tv_status_common VALUE 'F02', c_unit TYPE meins VALUE 'H'. "reference(s) DATA: lo_fin_cap_planning_api TYPE REF TO /rpm/cl_fin_cap_planning_api. "field-symbols FIELD-SYMBOLS: <fs_fin_plan> TYPE type_fin_planf, <fs_msg> TYPE /rpm/ts_messages. "select only IT projects in the selection IF it_proj[] IS NOT INITIAL. SELECT guid external_id portfolio_guid FROM /rpm/item_d INTO TABLE t_item_d FOR ALL ENTRIES IN it_proj WHERE external_id = it_proj-extid AND ( item_type = c_zit_bau OR item_type = c_zit_inf OR item_type = c_zit_opm OR item_type = c_zit_sap ). IF sy-subrc <> 0. CLEAR t_item_d[]. ENDIF. ENDIF. "create range table for guid LOOP AT t_item_d INTO w_item_d. CLEAR w_item. w_item-low = w_item_d-guid. w_item-sign = 'I'. w_item-option = 'EQ'. APPEND w_item TO r_item. CLEAR w_item. ENDLOOP. "join cat and group tables IF t_item_d[] IS NOT INITIAL. SELECT i~guid i~external_id i~portfolio_guid c~guid c~category g~guid g~group_id g~sponsor g~parent_guid g~changed_by g~changed_on g~active g~original INTO TABLE t_itemcatgrp FROM /rpm/item_d AS i INNER JOIN /rpm/fin_cat AS c ON i~guid = c~parent_guid INNER JOIN /rpm/fin_group AS g ON c~guid = g~parent_guid FOR ALL ENTRIES IN it_proj WHERE i~external_id = it_proj-extid AND ( c~category = c_cat_i05 OR c~category = c_cat_i10 ) AND c~active = abap_true AND g~active = abap_true. IF sy-subrc <> 0. CLEAR t_itemcatgrp[]. ENDIF. ENDIF. SORT t_itemcatgrp[] BY guid external_id portfolio_guid guid_c category guid_g group_id sponsor parent_guid. "get first day of the previous month CALL FUNCTION 'RS_VARI_V_LAST_MONTH' TABLES p_datetab = t_date_range p_intrange = t_int_range. CLEAR w_date_range. READ TABLE t_date_range INTO w_date_range INDEX 1. IF sy-subrc = 0. CLEAR v_high_date. v_high_date = w_date_range-high. ENDIF. "get group and plan details in 1 table- existing data SELECT i~guid i~external_id i~portfolio_guid c~guid c~category g~guid g~group_id g~sponsor g~parent_guid g~active g~original p~guid p~plan_type p~period p~changed_on p~amount p~currency INTO TABLE t_grp_plan FROM /rpm/item_d AS i INNER JOIN /rpm/fin_cat AS c ON i~guid = c~parent_guid INNER JOIN /rpm/fin_group AS g ON c~guid = g~parent_guid INNER JOIN /rpm/fin_plan AS p ON g~guid = p~guid WHERE i~guid IN r_item AND ( c~category = c_cat_i05 OR c~category = c_cat_i10 ) AND c~active = abap_true AND g~active = abap_true AND ( p~plan_type = c_plantype_75 OR p~plan_type = c_plantype_77 OR p~plan_type = c_plantype_73 ) AND period <= v_high_date. IF sy-subrc <> 0. CLEAR t_grp_plan[]. ENDIF. IF t_itemcatgrp[] IS NOT INITIAL. "get plan SELECT guid plan_type period amount FROM /rpm/fin_plan INTO TABLE t_fin_plan_tmp FOR ALL ENTRIES IN t_itemcatgrp WHERE guid = t_itemcatgrp-guid_g AND ( plan_type = c_plantype_75 OR plan_type = c_plantype_77 ) AND period <= v_high_date. IF sy-subrc <> 0. CLEAR t_fin_plan_tmp[]. ENDIF. ENDIF. SORT t_fin_plan_tmp[] BY guid plan_type period. LOOP AT t_fin_plan_tmp INTO w_fin_plan_tmp. CLEAR w_fin_plan. w_fin_plan-guid = w_fin_plan_tmp-guid. w_fin_plan-period = w_fin_plan_tmp-period. w_fin_plan-amount = w_fin_plan_tmp-amount. APPEND w_fin_plan TO t_fin_plan. CLEAR w_fin_plan. ENDLOOP. SORT t_fin_plan[] BY guid period. "add actual and actual-manual values LOOP AT t_fin_plan ASSIGNING <fs_fin_plan>. COLLECT <fs_fin_plan> INTO t_fin_plan_coll. ENDLOOP. UNASSIGN <fs_fin_plan>. "get all fields to pass to API IF t_fin_plan_coll[] IS NOT INITIAL. SELECT i~guid i~external_id i~portfolio_guid c~guid c~category g~guid g~group_id g~sponsor g~parent_guid g~active g~original p~guid p~plan_type p~period p~changed_on p~amount p~currency INTO TABLE t_grpplan_full FROM /rpm/item_d AS i INNER JOIN /rpm/fin_cat AS c ON i~guid = c~parent_guid INNER JOIN /rpm/fin_group AS g ON c~guid = g~parent_guid INNER JOIN /rpm/fin_plan AS p ON g~guid = p~guid FOR ALL ENTRIES IN t_fin_plan_coll WHERE i~guid IN r_item AND ( c~category = c_cat_i05 OR c~category = c_cat_i10 ) AND c~active = abap_true AND g~active = abap_true AND p~guid = t_fin_plan_coll-guid AND ( p~plan_type = c_plantype_75 OR p~plan_type = c_plantype_77 ) AND p~period = t_fin_plan_coll-period. IF sy-subrc <> 0. CLEAR t_grpplan_full[]. ENDIF. "get all 73 SELECT guid plan_type period changed_on amount currency FROM /rpm/fin_plan INTO TABLE t_fin_plan_full73 FOR ALL ENTRIES IN t_fin_plan_coll WHERE guid = t_fin_plan_coll-guid AND plan_type = c_plantype_73 AND period = t_fin_plan_coll-period. IF sy-subrc <> 0. CLEAR t_fin_plan_full73[]. ENDIF. ENDIF. SORT t_grpplan_full[] BY guid external_id portfolio_guid guid_c category guid_g group_id sponsor parent_guid active original guid_p period. DELETE ADJACENT DUPLICATES FROM t_grpplan_full COMPARING guid external_id portfolio_guid guid_c category guid_g group_id sponsor parent_guid active original guid_p period. "get api reference CALL METHOD /rpm/cl_fin_cap_planning_api=>get_instance RECEIVING rr_instance = lo_fin_cap_planning_api. IF lo_fin_cap_planning_api IS BOUND. "loop projects LOOP AT t_item_d INTO w_item_d. CLEAR: t_fin_group_api[], t_fin_plan_api. "populate context CLEAR w_context. w_context-portfolio_guid = w_item_d-portfolio_guid. w_context-parent_type = c_parent_type. "'RIH'. w_context-parent_guid = w_item_d-guid. w_context-object_type = c_object_type."'RIH'. w_context-object_guid = w_item_d-guid. "get plan info CLEAR: v_subrc, w_plan_info. CALL METHOD lo_fin_cap_planning_api->get_plan_info EXPORTING is_context = w_context
* iv_language = iv_fin_cap = c_fin_cap "1 IMPORTING es_plan_info = w_plan_info et_msgs = t_msgs[] ev_rc = v_subrc. APPEND LINES OF t_msgs TO t_msgs_item. CLEAR t_msgs[]. "pass context CALL METHOD lo_fin_cap_planning_api->initialize_planning EXPORTING is_context = w_context iv_hierarchy_type = c_hierarchy_type iv_fin_cap = c_fin_cap "1
* iv_language = it_filter_data = t_filter_data[] is_plan_info = w_plan_info
* iv_portfolio_type = IMPORTING et_msgs = t_msgs[]
* es_mode = * . APPEND LINES OF t_msgs TO t_msgs_item. CLEAR t_msgs[]. "append project UNASSIGN <fs_msg>. LOOP AT t_msgs_item ASSIGNING <fs_msg> WHERE msgtype <> c_msgtype_s."'S'."non-success msgs only <fs_msg>-objectid = w_item_d-external_id. APPEND <fs_msg> TO et_msgs. ENDLOOP. UNASSIGN <fs_msg>. CLEAR t_msgs_item[]. "this is to eliminate duplicate 75/77/73 entries SORT t_grp_plan[] BY guid external_id portfolio_guid guid_c category guid_g group_id sponsor parent_guid active original guid_p period . DELETE ADJACENT DUPLICATES FROM t_grp_plan COMPARING guid external_id portfolio_guid guid_c category guid_g group_id sponsor parent_guid active original guid_p period . "this is for overwriting existing 73 entries CLEAR: t_fin_group_api[], t_fin_plan_api[]. LOOP AT t_grp_plan INTO w_grp_plan_temp WHERE guid = w_item_d-guid ##loop_at_ok. "separate group and plan based on guid CLEAR w_grp_plan. w_grp_plan = w_grp_plan_temp. "grp AT NEW guid_g. CLEAR v_cat_guid. v_cat_guid = w_grp_plan-guid_c. CLEAR w_fin_group_api. w_fin_group_api-guid = w_grp_plan-guid_g. w_fin_group_api-external_id = w_grp_plan-group_id. w_fin_group_api-sponsor = w_grp_plan-sponsor. w_fin_group_api-parent_guid = w_grp_plan-parent_guid. w_fin_group_api-changed_by = sy-uname. w_fin_group_api-active = w_grp_plan-active. w_fin_group_api-original = w_grp_plan-original. APPEND w_fin_group_api TO t_fin_group_api. ENDAT. "plan "dont append if already present CLEAR w_fin_plan_api. READ TABLE t_fin_plan_api INTO w_fin_plan_api WITH KEY guid = w_grp_plan-guid_p plan_type = c_plantype_73 period = w_grp_plan-period. "#EC WARNOK IF sy-subrc <> 0. CLEAR w_fin_plan_coll. READ TABLE t_fin_plan_coll INTO w_fin_plan_coll WITH KEY guid = w_grp_plan-guid_p period = w_grp_plan-period. "#EC WARNOK IF sy-subrc = 0. CLEAR v_coll. v_coll = w_fin_plan_coll-amount. ELSE. "if coll is zero, zero the 73 CLEAR: w_fin_plan_api, v_updated. w_fin_plan_api-guid = w_grp_plan-guid_p. w_fin_plan_api-plan_type = c_plantype_73. w_fin_plan_api-period = w_grp_plan-period. w_fin_plan_api-amount = 0. w_fin_plan_api-currency = w_grp_plan-currency. w_fin_plan_api-unit = c_unit."'H'. w_fin_plan_api-external_id = w_grp_plan-group_id . APPEND w_fin_plan_api TO t_fin_plan_api. CLEAR w_fin_plan_api. v_updated = abap_true. ENDIF. IF v_updated = abap_false. CLEAR w_fin_plan_full73. READ TABLE t_fin_plan_full73 INTO w_fin_plan_full73 WITH KEY guid = w_grp_plan-guid_p plan_type = c_plantype_73 period = w_grp_plan-period. "#EC WARNOK IF sy-subrc = 0. CLEAR v_73. v_73 = w_fin_plan_full73-amount. "compare values IF v_coll <> v_73. CLEAR w_fin_plan_api. w_fin_plan_api-guid = w_grp_plan-guid_p. w_fin_plan_api-plan_type = c_plantype_73. w_fin_plan_api-period = w_grp_plan-period. w_fin_plan_api-amount = w_fin_plan_coll-amount. w_fin_plan_api-currency = w_grp_plan-currency. w_fin_plan_api-unit = c_unit."'H'. w_fin_plan_api-external_id = w_grp_plan-group_id . APPEND w_fin_plan_api TO t_fin_plan_api. CLEAR w_fin_plan_api. ENDIF. ELSE. "if not found, insert CLEAR w_fin_plan_api. w_fin_plan_api-guid = w_grp_plan-guid_p. w_fin_plan_api-plan_type = c_plantype_73. w_fin_plan_api-period = w_grp_plan-period. w_fin_plan_api-amount = w_fin_plan_coll-amount. w_fin_plan_api-currency = w_grp_plan-currency. w_fin_plan_api-unit = c_unit."'H'. w_fin_plan_api-external_id = w_grp_plan-group_id . APPEND w_fin_plan_api TO t_fin_plan_api. CLEAR w_fin_plan_api. ENDIF. ENDIF. CLEAR: v_73, v_coll, v_updated. ENDIF. CLEAR: v_73, v_coll, v_updated. "for every group AT END OF guid_g ##loop_at_ok. "only if plan table is filled "update fin plan IF t_fin_plan_api[] IS NOT INITIAL. "call api CALL METHOD lo_fin_cap_planning_api->fin_groups_modify EXPORTING iv_category_guid = v_cat_guid it_fin_groups = t_fin_group_api[] it_fin_plan = t_fin_plan_api[] iv_hierarchy_type = c_hierarchy_type iv_language = c_language is_plan_info = w_plan_info IMPORTING ev_rc = v_subrc et_msgs = t_msgs[]. APPEND LINES OF t_msgs TO t_msgs_group. CLEAR t_msgs[]. "save CALL METHOD cl_inm_ppm_services=>save( EXPORTING iv_check_only = /rpm/cl_co=>sc_false IMPORTING et_messages = t_msgs[] ev_rejected = v_rejected ). APPEND LINES OF t_msgs TO t_msgs_group. CLEAR t_msgs[]. "append project and group external ID UNASSIGN <fs_msg>. LOOP AT t_msgs_group ASSIGNING <fs_msg>. <fs_msg>-objectid = w_item_d-external_id. ENDLOOP. UNASSIGN <fs_msg>. APPEND LINES OF t_msgs_group TO et_msgs. CLEAR t_msgs_group[]. ENDIF. CLEAR: t_fin_group_api[], t_fin_plan_api[], w_fin_group_api. ENDAT. ENDLOOP. ENDLOOP. ENDIF. "delete duplicate messages SORT et_msgs[]. DELETE ADJACENT DUPLICATES FROM et_msgs COMPARING ALL FIELDS. "clear variables CLEAR: t_item_d, t_fin_plan_coll, t_grpplan_full, t_fin_plan_full73, t_fin_plan_api, t_fin_group_api, t_fin_plan, t_date_range, t_int_range, t_msgs, t_grp_plan, t_itemcatgrp, t_filter_data, "work areas w_fin_plan_api, w_fin_group_api, w_grp_plan, w_grp_plan_temp, w_item_d, w_date_range, w_fin_plan_full73, w_fin_plan_coll, w_context, w_plan_info, "variables v_cat_guid, v_subrc, v_high_date, v_rejected. ENDFUNCTION.

To summarize, you learnt how to make use of std. SAP API /rpm/cl_fin_cap_planning_api to update /RPM/FIN_PLAN and /RPM/FIN_GROUP tables. Did this blog post help you, or answer at least some of your questions. Please share your feedback or thoughts in a comment 😊.