Due to very little PPM development documentation available, I want to share a problem I encountered a lot during the implementation phase and my recommendations to achieve the proper functionality.

/wp-content/uploads/2014/03/p4_404568.png

     The requirement is to achieve data transfer between different custom tabs in real time. For example, we have two customer tabs at the project management level:

/wp-content/uploads/2014/03/p2_404552.png
     According to the MVC principles, there shouldn’t be any business logic in the UI, this includes data sharing between components.

     SAP guidelines suggest the usage of a subsystem (ABAP global class implementing IF_DPR_APPL_PLUG_IN_SUBSYSTEM and IF_DPR_APPL_BOOTSTRAP_MEMBER interfaces) as an intermediate layer between the UI and the back-end logic. I found that sometimes, the subsystem is instantiated more than once during the same working session. This is an issue, as the project data is loaded again. So basically we have the following scenario:


     Some data is filled in the first tab, that we want to automatically be updated in the second tab (in real-time) or to trigger some event. As the subsystem instance is deleted and created again when the user switches between tabs, there is no possibility to handle an event or incoming data.


     What I implemented and also advise everyone to do is to create a persistency layer (something similar with the BO, business layer, in PLM 7).


     Basically, store and manipulate the back-end data inside a global class. If you are doing this already, that’s great, so I’ll take it to the next level.

     Here’s a list of properties and design patterns your class should have:

     1. As an interjacent agent in the application model, it is recommended that the back-end class is implemented as a Singleton in order to ensure that consistency. Basically, even if the subsystem is created again, the GET_INSTANCE( ) method of the business class will return the same instance (because of the same work process).

     2. The class acts as a data buffer by minimizing the database interactions. It has setter and getter methods, that can be used by each component in the UI via the subsystem. For a better understanding and standardization, the persistency layer should implement the four basic CRUD functions (Create, Read, Update, Delete).

     Additionally, if the data manipulated is of table type, besides having the same structure as the underlying database table, the buffer will have an extra control field (type DBOP) which stores the last operation performed on a particular entry. The values for operations are referenced by constants: PPTE_INSERT, PPTE_UPDATE, PPTE_DELETE. This will somehow be an ABAP implementation of the active record pattern.

     Every time the user switches from one tab to the other, in the PROCESS_BEFORE_OUTPUT method of the second tab (automatically generated and called method in the component controller, created by implementing the IF_FPM_BUILDING_BLOCK), the READ method from the business class should be called via the subsystem in order to refresh the screen data from the buffer. This way, even if the subsystem is reinstantiated, the buffer data will always be consistent.

     The following steps are a very lightly introduction on how to obtain the results described above:

     We have the requirement to add an additional tab for project escalations, called ‘Escalation Overview’. All escalations created in a project should be stored in the underlying database in a customer table:

/wp-content/uploads/2014/03/p3_404567.png

     The tricky part is, we want to be able to create new escalations from another tab, let’s say, project structure. Via a customer button:

/wp-content/uploads/2014/03/p5_404569.png

     The issue is, after pressing this button and navigation to the ‘Escalation Overview’ tab, the subsystem is reinstantiated. Basically, all data that was not saved is lost.

     Now, by implementing the persistent layer with the business class, there will only be one instance of the buffer per session.

     As described above, we need to create a structure type for the buffer with the additional control field:

/wp-content/uploads/2014/03/p6_404576.png

     In this scenario, there will be multiple escalations for a single project (multiple to one relationship) so the buffer will be represented by an internal table. We also need to create a table type for the structure above. We’ll call it YPPM_EM_TTY_DATA.

       

     Next, it’s time to create the actual bussiness class.
/wp-content/uploads/2014/03/p7_404577.png

     Here, MO_OBJECT is the reference to the business class (necessary for the Singleton). And MT_DATA is the buffer represented by an internal table.

     Among other methods, the class also has:

/wp-content/uploads/2014/03/p8_404591.png

     GET_INSTANCE – in order to retrieve the Singleton.

     CHECK_BEFORE_SAVE and SAVE that will be later called by the subsystem.

     CRUD functionalities:

     When writing the coding for the Create/Read/Update/Delete methods, keep in mind the control field (DBOP).

     Here’s a small example:

    

loop at it_data[] assigning <fs_create>.
   
append initial line to mt_data[] assigning <fs_data>.

    move-corresponding <fs_create> to <fs_data>.

    “generate guid for escalation
   
try.
        <fs_data>
escalation_guid = cl_system_uuid=>create_uuid_x16_static( ).
     
catch cx_uuid_error.
       
“handle error
       
continue.
   
endtry.

    “administrative fields
    <fs_data>
created_by = syuname.
    <fs_data>
created_on = sydatum.

     “set control field

    <fs_data>dbop = ppet_dbopinsert.
endloop.

     The idea behind this control field is a much more improved performance at save.

     All the functionality of the business class should be connected to the UI via the subsystem. The reason behind this is to have some sort of bottle-neck between the front-end and the back-end. To have a clearer overview of the hole process, I advise you to name the subsystem methods the same as the methods in the business class:

/wp-content/uploads/2014/03/p9_404592.png

     An example of the CREATE_DATA method in the subsystem:

data:
    lo_bo
type ref to ycl_yppm_em_bo.

  lo_bo = ycl_yppm_em_bo=>get_instance( iv_project_guid = mv_project_guid ).

  if lo_bo is bound.
    lo_bo
->create_data( it_data = it_data[] ).
 
endif.

     In conclusion, when a user will create a new escalation in the project ‘Structure’ tab, the method CREATE_DATA from the subsystem will be called and a new entry will be inserted into the buffer. Afterwards, when the user will navigate to the ‘Escalation Overview’ tab. The following code will run in the PROCESS_BEFORE_OUTPUT method from the component controller:

/wp-content/uploads/2014/03/p10_404593.png

      This is a really simple example, but it can be use as the basis for more complex scenarios.

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Armin Eberhardt

    Great Article Tudor!

    You are right.
    Unfortunately there is only poor information from SAP on how to use the subsystem concept.

    Thanks for sharing!

    (0) 
    1. Tudor Riscutia Post author

      Hi Armin,

      Thanks! It is truly a shame that cProjects is so poorly documented. Under the hood it has such a good design…

      BR,

      Tudor

      (0) 
      1. Mohit Kumar

        Hi Tudor,

        Thanks for the knowledge share!

        Hope you find some time to go through this comment (in fact a question/discussion item).

        I am currently working on questionnaire update for a project in PPM (questionnaire is updated with decision date and time). My approach to this is->

        1. Get object manager ( CL_RPM_OBJ_MANAGER=>get_instance( ).) and add project guid using LOAD_PROJECTS method

        2. Get rpm services manager /RPM/CL_SERVICES=>get_instance( ).

        3. Call modify method of /RPM/CL_SERVICES instance – Here I pass the new data in lt_modification table as it is passed when processing it from screen

        4. Call  do_action method of /RPM/CL_SERVICES instance with action name as  /rpm/cl_ui_co=>action_save

        5. Call Do_save method of CL_RPM_OBJ_MANAGER instance.

        6. BAPI_CPROJECTS_COMMIT_WORK’ FM call.

        After doing all this I get success message same as I do get for direct processing on FPM screen, however, I don’t see the changed values. If possible, please help me with any link/leads/any guidance.

        Thanks a lot!!

        (0) 

Leave a Reply