Custom Area of Responsibility
INTRODUCTION
In process-controlled workflow SAP introduced decision sets (DS) and area of responsibility (AOR) so that responsibility for the items of a document can be distributed among several agents (items are grouped according to the AOR). Mainly, at each approval step, document items are grouped into sets each of which refers to a specific AOR. Item grouping, AOR item assignment and agent resolution tasks are carried out by implementations of /SAPSRM/BD_WF_RESP_RESOLVER BAdI (responsibility resolvers). Standard responsibility resolvers are RR_MANAGER, RR_EMPLOYEE ecc.
HOW IT WORKS
AOR data are stored in /SAPSRM/D_WF_012 table and managed using Persistent Object Service (POS). So, an AOR is represented by an id, a leading object id and an area entity. The persistent class responsible for reading/writing data is /SAPSRM/CL_WF_AREA_BASE. The main AOR class, superclass of all AOR classes /SAPSRM/CL_WF_AREA*, is /SAPSRM/CL_WF_AREA class implementing the /SAPSRM/IF_WF_AREA interface. AOR instances are created and are next retrieved by using method implementations of responsibility resolver BAdI /SAPSRM/BD_WF_RESP_RESOLVER (using persistent classes). More precisely AOR instances are:
- created in /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_AREA_TO_ITEM_MAP method, next document item guids are assigned to involved AOR
- retrieved in /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_APPROVERS_BY_AREA_GUID method and asked for agent resolution
Although in /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_APPROVERS_BY_AREA_GUID method you can return approvers simply filling RT_APPROVER output and ignoring AORs, this is not a good practice. There you must retrieve the AOR instance passed as mandatory input in IS_AREA parameter and ask it for responsible approvers. Even the document key IS_DOCUMENT received as optional input could be useless for agent resolution if document items are grouped into multiple DS belonging to different AORs (Item Based Decision). The purpose of the method is to ask AOR received as input for responsible approvers. AOR instances are built calling the static CREATE_INSTANCE method specifying:
- IV_AREA_TYPE: AOR class name
- IV_LEADING_OBJECT_ID: the leading object of your AOR instance
- IR_EVALUATION_CONTEXT: a generic type ref to data
As said before AOR data are managed using the Persistent Object Service in /SAPSRM/D_WF_12 table; although IV_LEADING_OBJECT_ID is typed as ANY in CREATE_INSTANCE method, it is a 32 char [as you can see in /SAPSRM/D_WF_012 table field and in the INITIALIZE method of /SAPSRM/CL_WF_AREA class]. What about IR_EVALUATION parameter in CREATE_INSTANCE method? Following abap code single-step inside [/SAPSRM/CL_WF_AREA] you can see taht IR_EVALUATION_CONTEXT “is lost” when calling SAVE method. As explained AOR are first retrieved and next asked for agent resolution; in standard AOR implementations [/SAPSRM/CL_WF_AREA* classes] the LEADING_OBJECT_ID code is enough for carrying out agent resolution task (probably this is the reason why the IR_EVALUATION_CONTEXT parameter is ignored). What if an AOR needs more than just a single LEADING_OBJECT_ID code for agent resolution?
CUSTOM AOR DEVELOPMENT
I show you how to take advantage of IR_EVALUATION_CONTEXT parameter for handling additional data for agent resolution in AOR used by standard process controlled workflows. The IR_EVALUATION_CONTEXT parameter, being a type ref to data, can be used to inject additional data of any kind in the AOR instance. Technically the IR_EVALUATION_CONTEXT passed must be:
- mapped to specific custom attributes
- stored using persistent object service in specific database tables
Below, to simplify, i decided to handle a structure (so, a line). First of all we need to create a database table for storing our custom data; the only thing to take care of is to start our table primary key with the client and the AOR id (the same of /SAPSRM/D_WF_012 table). Assume that our evaluation context table is this one, the ZIREVACONTEXT table:
Next we have to create a persistent class for our evaluation context table. This is quite simple; everything’s carried out by the SAPGUI. Simply create a new class specifying the persistence option (Img. 1); next click the persistence button and specify the database table for your evaluation context (Img. 2). Add all table fields as persistent attribute to your persistence class (Img. 3) and configure generator settings as defined for /SAPSRM/CL_WF_AREA_BASE class (Img. 4). You can also keep default configuration simply adding the “Generate Methods for Query” tick (this is important).
Image 4 |
Image 2 |
Image 3 |
Image 1 |
Save, activate. The SAPGUI automatically ask you for activation of actor and base classes. If you want to explore deeper the POS just have a look at SAP Help.The last step is to build our AOR class so that it can manage our evaluation context; you could do it two ways:
- as subclass of base responsible area class /SAPSRM/CL_WF_AREA
- implementing the /SAPSRM/IF_WF_AREA interface modeling your code as the one on class /SAPSRM/CL_WF_AREA
I prefer to show you the second one choice so that i have full control of implementation; the first one is a little bit tricky because of some constraints on method redefinition. I created a protected instance generation class named ZCL_WF_AREA_DEMO implementing the /SAPSRM/IF_WF_AREA interface and defined attributes and methods exactly as defined in base class /SAPSRM/CL_WF_AREA. Now my class is able to manage only data of standard table /SAPSRM/D_WF_012. To manage data of my custom table i added two private methods:
- LOAD_EVALUATION_CONTEXT: responsible for reading persistent instance data
- SAVE_EVALUATION_CONTEXT: responsible for saving attribute & persistent data (receive as input the IR_EVALUATION_CONTEXT)
Lastly:
- i added a call to my LOAD_EVALUATION_CONTEXT method at the end of the basic LOAD method
- i added a call to my SAVE_EVALUATION_CONTEXT method at the end of basic SAVE method transferring to it the IR_EVALUATION_CONTEXT parameter
- i implemented /SAPSRM/IF_WF_AREA~GET_RESPONSIBLE_APPROVERS method (in general there you must implement the agent resolution logic based on instance attribute values; in that case to simplify i supposed that each field of my structure contains the agent user id)
Let’s go a little bit inside the code. That’s the LOAD_EVALUATION_CONTEXT method:
data: lo_query_manager type ref to if_os_query_manager,
lo_query type ref to if_os_query,
lt_area_entity_ref type osreftab,
lt_area_entity_query type osreftab,
lo_area_entity_ref like line of lt_area_entity_ref,
lo_area_entity type ref to /sapsrm/if_wf_area_entity,
lv_area_entity_id type /sapsrm/wf_area_entity_id,
lo_ps_context type ref to zcl_ps_eval_context.
*load evaluation context data from db-persistence layer
*-> simply an adjustment of standard load code for our evealuation context
*get the newly created objects form the object service and filter those
lt_area_entity_ref = zca_ps_eval_context=>agent->if_os_ca_instance~get_created( ).
loop at lt_area_entity_ref into lo_area_entity_ref.
cast zcl_ps_eval_context( lo_area_entity_ref ).
lo_ps_context = cast #( lo_area_entity_ref ).
if lo_ps_context->get_id( ) ne me->/sapsrm/if_wf_area~get_guid( ).
delete lt_area_entity_ref index sy-tabix.
endif.
endloop.
*get the persistent objects by query and append them to list
lo_query_manager = cl_os_system=>get_query_manager( ).
lo_query = lo_query_manager->create_query( i_filter = `ID = PAR1` ).
lt_area_entity_query =
zca_ps_eval_context=>agent->if_os_ca_persistency~get_persistent_by_query(
i_query = lo_query
i_par1 = me->/sapsrm/if_wf_area~get_guid( ) ).
append lines of lt_area_entity_query to lt_area_entity_ref.
*assign values to local attributes (IR_EVALUATION_CONTEXT)
loop at lt_area_entity_ref into lo_area_entity_ref.
* cast object
cast zcl_ps_eval_context( lo_area_entity_ref ).
lo_ps_context = cast #( lo_area_entity_ref ).
* prepare lines
me->ir_evaluation_context-mandt = sy-mandt.
me->ir_evaluation_context-id = lo_ps_context->get_id( ).
me->ir_evaluation_context-field1 = lo_ps_context->get_field1( ).
me->ir_evaluation_context-field2 = lo_ps_context->get_field2( ).
me->ir_evaluation_context-field3 = lo_ps_context->get_field3( ).
endloop.
as you can see it is simply an adapted copy/paste of standard LOAD method based on our persistent-class. Same approach for SAVE_EVALUATION_CONTEXT method:
data: lo_context type ref to zcl_ps_eval_context.
field-symbols: <ls_context> type zirevacontext.
*evaluation context management
if ir_evaluation_context is bound.
* cast it in your structure
assign ir_evaluation_context->* to <ls_context>.
* store attribute using db persistence layer
lo_context = zca_ps_eval_context=>agent->create_persistent( me->/sapsrm/if_wf_area~get_guid( ) ).
lo_context->set_field1( <ls_context>-field1 ).
lo_context->set_field2( <ls_context>-field2 ).
lo_context->set_field3( <ls_context>-field3 ).
* initialize attribute
<ls_context>-mandt = sy-mandt.
<ls_context>-id = me->/sapsrm/if_wf_area~get_guid( ).
me->ir_evaluation_context = <ls_context>.
endif.
QUICK TEST
To simulate the behavior when used inside a BAdI implementation i wrote a simple report which consist of a few dozen lines.
report zrespareawithevcntx.
data: lt_approver type /sapsrm/t_wf_approver,
ls_approver type /sapsrm/s_wf_approver,
lo_areac type ref to zcl_wf_area_demo, "area created
lo_areal type ref to zcl_wf_area_demo, "area loaded
lo_context type ref to zirevacontext.
parameters: p_f1 type fieldname obligatory,
p_f2 type fieldname obligatory,
p_f3 type fieldname obligatory.
start-of-selection.
create data lo_context.
* set context values
lo_context->mandt = sy-mandt.
lo_context->field1 = p_f1.
lo_context->field2 = p_f2.
lo_context->field3 = p_f3.
* build responsible area
* must be done in method /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_AREA_TO_ITEM_MAP
* of your responsible resolver
lo_areac ?= zcl_wf_area_demo=>/sapsrm/if_wf_area~create_instance(
iv_area_type = zcl_wf_area_demo=>gc_area_type
iv_leading_object_id = 'LEAD'
ir_evaluation_context = lo_context ).
* retrieve instance & ask for approvers
* must be done in method /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_APPROVERS_BY_AREA_GUID
* of your responsible resolver
lo_areal ?= zcl_wf_area_demo=>/sapsrm/if_wf_area~get_instance_by_guid(
iv_area_type = zcl_wf_area_demo=>gc_area_type
iv_area_guid = lo_areac->/sapsrm/if_wf_area~get_guid( ) ).
lt_approver = lo_areal->/sapsrm/if_wf_area~get_responsible_approvers( ).
loop at lt_approver into ls_approver.
write: / ls_approver-approver_ot, ls_approver-approver_id.
endloop.
Comments inside the code should be enough to understand the code. As explained the report simulate calls just for quick testing purposes; if you want to test it during a document approval you must implement your own responsibility resolver [Badi /SAPSRM/BD_WF_RESP_RESOLVER].
CONCLUSIONS
I showed how to build up a custom AOR class so that it can be used in agent resolution in standard process controlled workflows. Mainly there are 4 steps to perform:
- Define tables to store AOR custom data
- Create persistent class(es) to handle your data using POS
- Create your AOR class implementing /SAPSRM/IF_WF_AREA interface modeled as /SAPSRM/CL_WF_AREA
- Make appropriate changes to your AOR class so that it can manage in LOAD and SAVE method your custom data
Obviously (this is why in point 1 and 2 i talked about tables and classes) you can also define your AOR with many attributes (structures or internal tables). You just need to handle attributes-database read/write using POS.
You can download code sample there: sap_custom_oar.
Hi Manuel,
very nice and clear article. But I would never use such a concept (with customer own classes for AOR) for real projects.
Actually, SAP provides standard area handler /SAPSRM/CL_WF_AREA_EMPLO_LIST and it's more than enought for workflow. You should actually already know you approver list in the /SAPSRM/IF_EX_WF_RESP_RESOLVER~GET_AREA_TO_ITEM_MAP, and generate guids for /SAPSRM/CL_WF_AREA_EMPLO_LIST with the list of approvers.
With the customer AOR you can have an stupid situations, for example, if you have your AORs based on cost center responsible, and you have the same responsible for 2 differebt cost centers. With custom AOR based on cost center, you can have 2 workitems to approve 2 positions by the same responsible (he 'll need to approve twice).
I always prefer to group AORs by the user (list of users) and, as I already mentioned, use the standard /SAPSRM/CL_WF_AREA_EMPLO_LIST for that.
Nevertheless, thanks again for the article. I find it really helpfull.
Regards
Konstantin
Thanks Konstantin for your appreciation. In general (custom or standard) everytime there are approvers responsible for two areas could happens what you said, so two tasks and two decisions. However it is reasonable to assume these as exceptions; in general different areas should have different approvers. The way you suggest is certainly more than enough to achieve agent resolution and also to avoid the "cross-responsible" case but in my opinion, in a sense, this means bringing to match the concept of AOR with the approver list (as if the AOR became an intermediate entity meaningless or only with the meaning of a user list). At least, this is my point of view. Anyway my purpose was simply to show how to build a self-consistent AOR fully represented by its own attributes and able to perform agent resolution task by itself.Thanks again for your feedback.
Hi Manuel,
I am currently experiencing some difficulty implementing this solution, and hopeful you (or anyone) can provide some guidance. I'm sure I've missed something basic. We are on 740 SP 12, and I've implemented all of the steps as you outline above. The main problem I can see is that my Z-Table is getting created with a unique ID (GUID) that is not the same as either /SAPSRM/D_WF_012 fields ID or AREA_GUID (this one is what I thought should be used in my Z table. Thus, during the GET_RESPONSIBLE_APPROVERS method execution, I've no way to identify a linkage b/t the area guid from SAP's table and my Z table entries.
I can provide code snipets or more details as required, and greatly appreciate any help you can provide.
Hi Kyle, in my example you don't have to explicitly query the database to get approvers; they are read from the instance context loaded with persistent query manager. Anyway, just drop me an email at manwell77@gmail.com with some details and i'll see what i can do to be helpful.
Manuel,
Thanks for the response. I was able to work out my issue. What I've done is to allow my Z table to create with it's own unique ID (GUID). I added an attribute field to my table called simply AREA_GUID where I pass the corresponding AREA_GUID valued when my relevant /SAPSRM/D_WF_012 entry is created. That serves as the link b/t my Z table and the SAP table.
Everything is humming along great now! However, if you can think of any reason I should not have made the correction mentioned above, feel free to let me know.
Cheers!
KM