Quick and Dirty (QAD) – 03 Implementing custom TM Conditions (Part 2 of 3)
The Process Controller
Following up on our three-part series around implementing a custom TM Condition (Part 1: http://scn.sap.com/community/scm/transportation-management/blog/2013/01/26/quick-and-dirty-qad–02-implementing-custom-tm-conditions-part-1-of-3), in this part we will configure a new service for the Process Controller. As with the Validation, I followed the steps described in the TM Enhancement Guide (page 60 and following). We really could have used just the Validation to implement the call to the TM Condition, but I wanted also to use a strategy, I thought it would be useful for educational purposes (It will also give us a nice customizing functionality, as we will see). Furthermore, we could have used an existing strategy, but it was more interesting to do it from scratch.
So this will be all about creating our own process controller strategy. It has more coding than I would have liked to have for this QAD post, but it is worth it to go through the steps. If you do decide to do all the steps in your system, this will definitely not be quick (my apologies for the misleading title) It will be dirty though, so that makes up for it (titlewise). For the next part there will be much less coding, and much more TM Condition. And I promise, the post after that will not have a single line of code, when we look at the PPF Adapter for Output Management using BRFplus. Ok, let’s begin here with the service definition of our Process Controller:
A. Setup of the strategy: Defining a service
1. Open the IMG (transaction SPRO) and open the path “SAP Transportation Management” -> “SCM Basis” -> “Process Controller” and execute the activity “Define Service”.
2. We are shown a maintenance view of the available services. Click on “New Entries”:
3. Make an entry for the new service and save (use your own naming convention for this and all other objects if you like, but take care to stick to it in the later steps):
The service is now created. Why do we need to define a service at all? The enhancement guide tells us that a service “is the definition of strategies and methods working on the same process. It is used to clearly separate the maintenance.” (Page 54). This means that a service is just a label to group related strategies together. Now, we can define our strategy.
B. Setup of strategy: Defining the strategy
1. Go back to the Implementation Guide for the Process Controller, and click “Define Strategy”:
2. Click on “New Entries” to enter the new strategy:
3. Enter a name for the strategy, and enter the service we created earlier (and the strategy description). Save the new entry:
The strategy executes logic that is implemented in methods. These are processed sequentially. To make sure that this can be done, the process flow is handled with the help of so-called requests. These requests can carry data from the application to the methods, between methods, and from the methods back to the application. Like a little round trip train. In order to store the data, we need to create some table types and structures before we can continue with the strategy creation. We will create a very simple data model for the requests (If you follow the TM Enhancement Guide, you will notice that I simplified the data model somewhat. This is why we call this quick and dirty)
C. Setup of the Strategy: Creating the Request and Result structures
When our Validation executes the strategy, we can fill some input data in the request before we send it to the Process Controller. Usually just one request is sent, but it would be possible to send many requests at once. In our example, we just need one. We want to store the root key of our TOR object in this request, so we can pass it on later to the TM Condition (the condition will do the rest). The data model of our request will look somewhat like this (you can see this also in the screenshots shortly):
– Table: Requests (ZENH_T_FHP_REQUEST)
– Structure: Request (ZENH_S_FHP_REQUEST)
– Component: Strategy name (STRATEGY)
– Table: Requests, internal view (ZENH_T_FHP_REQUEST_INT)
– Structure: Request, internal view (ZENH_S_FHP_REQUEST_INT)
– Structure: Request structure (ZENH_S_FHP_REQUEST_STR)
– Table: TOR keys (/BOBF/T_FRW_KEY)
We also need to store the results of our strategy somwhere. The results will be messages that we want to show on the UI. For this we will use the following tables and structures:
– Table: Result (ZENH_T_FHP_RESULT)
– Structure: Result (ZENH_S_FHP_RESULT)
– Component: Root key of document (ROOT_KEY)
– Component: Message (LO_MESSAGE)
I know, this all might seem confusing, but it is just how the various DDIC objects are nested. Basically the only thing that we are doing is passing a key to identify the BO that we are currently working on, and receiving messages for this BO. The reason for the internal and external view tables/structures is to separate the way the process controller deals with meta-data about requests from different strategies, to how the strategy itself treats the business relevant data that is handled internally in the request. This provides the possibility to process several BOs in one go (for example for mass processing), and we provide the possibility to execute several different strategies, and several requests. So, it can get messy really quick, but we anyway only intend to pass one request, execute one strategy, and process one BO, for which we retrieve messages (this is my understanding when trying to figure out this somewhat confusing data model, process controller experts are welcome to chime in and set me straight 🙂 )
Now we will look at the created DDIC objects (transaction SE11. When you create them, remember to first create the structures, and then the table types. The element DDIC types are standard types):
1. Table of requests:
2. Structure of request:
3. Table of requests, internal view:
4. Structure of request, internal view:
1. Table of results:
2. Structure of result:
D. Setup of the Strategy: Creating the required Objects
Now that we have the required DDIC objects, we can create the objects needed by the Process Controller to implement the strategy. As per the TM Enhancement Guide, we need the following objects:
– Request object class
– Controller class
– Method pool class
The first one is the request object class (page 61 of the enhancement guide). This class is used to handle the request(s) of a strategy, passing on the business relevant data:
1. Go to the Class Builder (SE24), enter the class name and press on Create:
2. Go to the Tab Properties, and enter as superclass the class /SCTM/CL_REQUEST. We need this to inherit important stuff. Don’t forget to activate (goes for all steps):
3. Go to the Tab Attributes and add the following public attributes. Here we use the DDIC table types we created earlier:
4. Go to the Tab Methods and add the CONSTRUCTOR method. This will get called automatically when the request object is first created:
5. Define the following parameters for the constructor method (click on Parameter). These contain the parameters that we pass when we start to execute our strategy (i.e. the strategy and the root key of our TOR object):
6. Open the code for the method and copy the following code:
here is a screenshot of the code (uhhh line 5 could use a line break, now that I see it). In this method we assign the input parameters to our object attributes for further processing (strategy and TOR root key(s) in internal request structure)
Here again for copying and pasting:
* call constructor of super class
super->constructor( iv_request_id ).
* assign constructor parameters to member variables
mv_strategy = iv_strategy.
mt_requests = it_requests.
The next object is the controller class (page 62). This class is responsible for actually creating our request objects, potentially belonging to different strategies. It manages the external view of the request(s):
1. Create the class in the Class Builder (SE24):
2. As with the request class, enter a superclass, this time /SCTM/CL_CONTROLLER. Again, we need this to inherit important stuff:
3. Create a private instance method CREATE_REQUEST_OBJECTS (as described in page 62 of the enhancement guide):
4. Create the following parameters for this method. Here we are passing the external view of our request(s).
5. And here is the code. This method is used to create the request objects, filling the internal request data that is coming from the process controller call.
method CREATE_REQUEST_OBJECTS. DATA: lv_message TYPE string, "#EC NEEDED lo_request TYPE REF TO zenh_cl_fhp_request, lt_strategy_ids TYPE /sctm/tt_strategy_id, lv_exists TYPE boole_d, lv_request_id TYPE /sctm/de_request_id. FIELD-SYMBOLS: <fs_request> TYPE zenh_s_fhp_request. CLEAR et_strategy_requests. * Fill complete strategy, method sequence, parameter and detail * buffer CLEAR lt_strategy_ids. LOOP AT it_request_data ASSIGNING <fs_request>. INSERT <fs_request>-strategy INTO TABLE lt_strategy_ids. ENDLOOP. fill_strategy_buffer( lt_strategy_ids ). LOOP AT it_request_data ASSIGNING <fs_request>. lv_exists = check_strategy( <fs_request>-strategy ). IF lv_exists = abap_false. * Given Strategy does not exist; all requests assigned can * not be handled * MESSAGE e033(/sctm/rg) WITH <fs_request>- * request_id INTO lv_message. CONTINUE. ENDIF. *############################################### * Insert your data mapping here if required!!! *############################################### lv_request_id = lv_request_id + 1. CREATE OBJECT lo_request EXPORTING iv_request_id = lv_request_id iv_strategy = <fs_request>-strategy it_requests = <fs_request>-requests. assign_request_to_strategy( EXPORTING io_request = lo_request iv_strategy = <fs_request>-strategy CHANGING ct_strategy_requests = et_strategy_requests ). ENDLOOP. endmethod.
6. Create a public instance method EXECUTE_DETERMINATION (as described in page 63) (actually, it should be called EXECUTE_VALIDATION, because we are triggering everything from a validation… this works just as well, though):
7. These are the parameters needed for this method.
8. And here the code. This code calls our method to create the request objects (see above) and processes them. Afterwards, it collects the results from the requests:
method EXECUTE_DETERMINATION. DATA: lt_strategy_requests TYPE /sctm/tt_con_request_strategy. DATA: lo_request TYPE REF TO /sctm/cl_request, lo_fhp_request TYPE REF TO zenh_cl_fhp_request, lt_bapiret2 TYPE bapirettab. FIELD-SYMBOLS: <fs_strategy_request> TYPE /sctm/s_con_request_strategy, <fs_result> TYPE zenh_s_fhp_result. CLEAR et_result. * Transform the supplied request data into request objects CLEAR lt_strategy_requests. create_request_objects( EXPORTING it_request_data = it_request_data IMPORTING et_strategy_requests = lt_strategy_requests CHANGING mr_message = co_message_handler ). * Fill created request objects into controller clear_refill_attributes( it_input_methpar = it_input_methpar it_strategy_requests = lt_strategy_requests ). CLEAR lt_bapiret2. start_perform_requests( IMPORTING et_bapiret2 = lt_bapiret2 ). * Get results / messages from every single request LOOP AT mt_strategy_requests ASSIGNING <fs_strategy_request>. LOOP AT <fs_strategy_request>-t_request INTO lo_request. lo_fhp_request = zenh_cl_fhp_methods=>cast_request( lo_request ). CHECK lo_fhp_request IS BOUND. * Take over results LOOP AT lo_fhp_request->mt_results ASSIGNING <fs_result>. INSERT <fs_result> INTO TABLE et_result. ENDLOOP. * Take over request specific messages ENDLOOP. ENDLOOP. endmethod.
The last object is the class for the method pools. The method pool class is where we will implement the methods that we will be assigning to our strategies later on. In our case, it is primarily the method to call the TM Condition:
1. Create the class ZENG_CL_FHP_METHODS in the Class Builder (SE24):
2. Create a public static method CAST_REQUEST (as described in page 64 and 65), we need this to make sure that we can actually process the type of request that is coming in:
3. Create the following parameters:
4. And the code. If we would try to call this strategy with incompatible request objects, this would be caught here:
method CAST_REQUEST. TRY. ro_request ?= io_request. CATCH cx_sy_move_cast_error. CLEAR ro_request. ENDTRY. endmethod.
Now we can implement methods that will execute the logic of the strategy. In our case we will just implement a method that will call the TM Condition. While the specific logic for this method will be discussed in the next part, here we can already make some preparations.
1. Create the public instance method CALL_TM_CONDITION:
2. The signature for these methods should be the following:
We will see the code in the next part, and the significance of these parameters. For now we have created the necessary objects to finish the customizing of our strategy. We will complete the customizing as follows:
E. Setup of the Strategy: The finishing touches
Now we can finish the customizing, by defining the methods and assigning them to the strategy. We have created the classes and methods, now we just have to let the process controller framework know how to find them.
1. Go back to the implementation guide (SPRO) for the Process Controller, and execute the following node: “Define Methods”
2. Make a new entry for the method we created:
3. Go back to the implementation guide (SPRO), and execute the following node: ” Assign Methods to a Strategy”:
4. Make a new entry to assign our method to our strategy (if we had more methods in our strategy, we could further indicate a sequence):
F. Calling our strategy from our validation
Ok, so how do we actually call this strategy that we created, from the Consistency Validation that we created in part 1? For this we need to return to our validation class and implement the method that we left empty for this purpose.
1. Open class ZCL_FHP_V_VAL in the Class Builder (SE24)
2. Open the source code of method /BOBF/IF_FRW_VALIDATION~EXECUTE and include the following code:
method /BOBF/IF_FRW_VALIDATION~EXECUTE. * declarations DATA: lo_fhp_controller TYPE REF TO zenh_cl_fhp_controller, ls_req TYPE zenh_s_fhp_request, lt_req TYPE zenh_t_fhp_request, lt_fhp_result TYPE zenh_t_fhp_result, ls_data TYPE zenh_s_fhp_request_int, lt_data TYPE zenh_t_fhp_request_int, lt_tor_key TYPE /BOBF/T_FRW_KEY, ls_tor_key TYPE LINE OF /BOBF/T_FRW_KEY, ls_failed_key LIKE LINE OF ET_FAILED_KEY. FIELD-SYMBOLS: <fs_result> TYPE zenh_s_fhp_result. * create an instance of the demo controller CREATE OBJECT lo_fhp_controller. * define strategy to be executed (prepare in customizing!) CLEAR ls_req. * PLACE YOUR CONFIGURED TEST STRATEGY HERE! ls_req-strategy = 'ZFHP_TST'. * assemble some example input data for the strategy CLEAR lt_data. ls_data-mt_tor_key = IT_KEY. INSERT ls_data INTO TABLE lt_data. ls_req-requests = lt_data. INSERT ls_req INTO TABLE lt_req. * execute the lo_fhp_controller->execute_determination( EXPORTING it_request_data = lt_req IMPORTING et_result = lt_fhp_result ). LOOP AT lt_fhp_result ASSIGNING <fs_result>. /scmtms/cl_common_helper=>msg_helper_add_mo( EXPORTING io_new_message = <fs_result>-lo_message CHANGING co_message = eo_message ). IF <fs_result>-LO_MESSAGE->CHECK( ) EQ abap_true. LS_FAILED_KEY-KEY = <FS_RESULT>-ROOT_KEY. APPEND ls_failed_key TO ET_FAILED_KEY. ENDIF. ENDLOOP. endmethod.
The code does the following:
1. It creates our controller object
2. It defines our strategy (hardcoded.. you might do this in a fancier way, for example with customizing)
3. It fills our internal request view with our TOR key(s) so they can get passed along later
4. It calls our controller method to execute our strategy (line 34)
5. It extracts the messages we received back, taking care of returning failed keys if one of the messages is an error message
So, this concludes this part. In our next and final part we will look at the call of a TM condition, and show you some pretty things you can do afterwards with it, jumping into BRFplus.
Take care and until next time!