CRM Rapid Applications – An Advanced Tutorial (Part 1)
Introduction
Recently I started to play around with the CRM Rapid Applications (Rapid Applications – SAP Library). I wanted to use it to create a little application with some basic functionality. The test application should prefill some data by e.g. using some number range object. Furthermore, it should perform some data validation. As I had attended sitMUC (SAP Inside Track Munich 2014 – Community Events – SCN Wiki) just a few weeks ago where Daniel Ridder and Tobias Trapp gave a presentation on BRFplus (SAP Business Rules Management) I wanted to implement the validations using BRFplus rules.
As with most of the interesting SAP technologies there are basic tutorials available for both, CRM Rapid Applications (e.g. SAP CRM 7.0 EhP1 – Rapid Applications (Part 1) and SAP CRM 7.0 EhP1 – Rapid Applications (Part 2) by Tzanko Stefanov) and BRFplus. However, more advanced tutorials (like e.g. AXT Rapid Applications Currency/Quantity fields by Accenture Zenit) are hard to find.
Therefore, I took me quite a while to implement my sample application. I only succeeded by asking questions in the BRFplus forum and by extensive debugging in the CRM Rapid Application framework. Consequently, I decided to collect the knowledge gained in the process in this mini blog series in order to provide an advanced tutorial for CRM Rapid Applications as well as BRFplus. The first part of the blog series is focused on the Rapid Application part and the integration of BRFplus, the second part (CRM Rapid Applications – An Advanced Tutorial (Part 2)) will provide details on the design of the the BRFplus rules.
The Application Design
The applications shown in the existing rapid application tutorials are based on existing database tables. In contrast to that I will show how to create an application from scratch including the data base tables using only the rapid application functionality. For the sample application I defined the following requirements which are quite common in business applications. The application should be able to store:
- Header data and corresponding attributes
- The header should consist of an ID, a reference to a business parter, a creation and change date and user
- The attributes should be simple key value pairs.
In addition to that
- It should be possible to add but not to delete records
- The ID should be taken from a number range object
- The creation date and user and change date and user should be set automatically by the system
- The validity of the attributes should be check according to the following rules
- “Attribute 1” is mandatory, “Attribute 2” is optional.
- Allowed values for “Attribute 1” are “A”, “B” and “C”,
- Depending on the value of “Attribute 1” the following values are allowed for “Attribute 2”
Attribute 1 Attribute 2 A 1 – 99 B 100 – 299 C 300 – 999
According to these requirements the following table show valid records for the sample application:
ID | Business Partner | Created At | Created By | Changed At | Changed By |
---|---|---|---|---|---|
0000000001 | 10000900 | 1.11.2014 | DRUMM | 2.11.2014 | DRUMM |
0000000002 | 14000123 | 3.11.2014 | DRUMM | 3.11.2014 | DRUMM |
Header ID | Attribute | Value |
---|---|---|
0000000001 | Attribute1 | A |
0000000002 | Attribute2 | B |
0000000002 | Attribute2 | 125 |
These requirements seemed simple enough for an introductory tutorial when I defined them in the first place. However, it turned out, that there are quite some difficulties hidden in them, both for CRM Rapid Applications and the BRFplus implementation.
Creating the Rapid Application
Rapid Applications are created using a wizard provided by the Rapid Application framework. The wizard is started by creating a new Rapid Application. As the example application for this blog should be based on database table select “Create from DB Table” to create the new application.
The Rapid Application wizard consists of the following steps:
1. Defining name and package for the application
2. Defining the database model
3. Defining the UI model
4. Optionally assigning the application to some UI profiles.
During each steps it is possible to generate and test the application. For this blog I created the application ZCD_RAPIDAPP_EX in the $TMP package.
Defining the Database Model
As mentioned above the goal of this blog is to show how to create a Rapid Application from scratch. Therefore, the next step is to define the database model. First the table to store the header data needs to be created. In order to create a new database table click on the “New” in the “DB Model” step of the wizard. This creates a new database table with an empty field list. Initially the new database table has a auto-generated name (ZCTAB000000N in the screen shot below). It is possible to change this name by clicking on “Details” and providing a custom name for the table (ZCD_RAE_HEADER in the screen shot below).
Although the field list initially appears to be empty the Rapid Application framework automatically generates some “technical” database fields. These field are CLIENT, RECORD_ID, PARENT_ID and OBJECT_ID, with CLIENT and RECORD_ID being the key fields of the database. The auto-generated fields are used by the framework to e.g. link entries in different tables.
The next step is to define the table fields required by the application. The following screen shot shows the table fields I generated for the table ZCD_RAE_HEADER. Note that the field ZZ_OBJECT_ID is defined as a logical key. The definition of a logical key is optional. However, in order to being able to define dependant tables a logical key is required in the superordinate table. Furthermore note, that some of the fields (e.g. ZZ_PARTNER or ZZ_CREATED_BY) have the field type “Application Reference”. Fields of type Application Reference are used to store references to other business objects like e.g. account or employee. Application references are used during the execution of the application to provide links in the UI to the respective objects. Finally, the fields available as search criteria need to be defined. This is done in the details view for each field.
To complete the database model for the application I created the dependent table ZCD_RAE_ATTRIBS to store the attributes. The following screen shot shows the details of this table. Note, that for the field ZZ_ATTRIBUTE I created a list of possible values. The values will be rendered as a drop down list when the application is executed.
Defining the UI Model
The next step is to define the UI model. The definition of the UI model consists of two steps, generating the views and adjusting the UI customizing. The first step is to automatically generate the UI model. This will create a search page, an overview page and a embedded search page. After the views have been generated the next step is to adjust the view. The only necessary adjustment for the example application is to remove the delete function from the overview page (cf. the following screen shot).
I didn’t add the the Rapid Application to a UI profile for this blog. Consequently, the final step is to save and generate the application. After the application has been generated it can be tested using the “Test” button. Note however, that not the complete functionality of the application is available in the test mode. For example, navigating to different business objects is not supported in the test mode.
The final step in defining the UI is to adjust the UI configuration. As with all SAP CRM applications this can either be done using the configuration mode or transaction BSP_WD_CMPWB_NEW. I change the standard configuration of the header details view as shown in the screen shot below. The business partner field is defined as mandatory, all other fields are display only. The other field should be automatically filled when a new record is created.
Extending the Rapid Application
In order to automatically populate the read only fields as well as to check the attributes according to the rules defined above it is necessary to extend the rapid application. In order to execute custom code during creating, changing and checking of a record the BAdI AXT_RT_TABLES_API is provided by SAP. The BAdI provides the methods ON_CREATE, ON_CHANGE, CHECK and QUERY. In this blog I will use the methods ON_CREATE, ON_CHANGE and CHECK to implement the requirements defined in the application design.
Automatically Populating Fields
The application design defines the following requirements:
- The Record ID should be taken from a number range object
- The creation date and user and change date and user should be set automatically by the system.
In order to achieve this I created the implementation ZCD_AXT_TABLES_RAE_HEADER_IMPL of the BAdI AXT_RT_TABLES_API. The execution of the BAdI is controlled by a filter on the table name. Therefore, I created the filter value ZCD_RAE_HEADER = TABLE_ID (see screen shot below).
The code snipped below shows the implementation of the ON_CREATE and ON_CHANGE methods of the BAdI implementation class. In the ON_CREATE method a new ZZ_OBJECT_ID is taken from a number range (lines 7 – 21). Besides that, only the fields ZZ_CREATED_BY, ZZ_CREATED_AT, ZZ_CHANGED_BY and ZZ_CHANGED_AT are initialized. The method GET_PARTNER_GUID_FOR_UNAME used in line 29 and 43 is a simple functional wrapper around the function module BP_CENTRALPERSON_GET.
METHOD if_ex_axt_rt_tables_api~on_create.
DATA: header_data TYPE zcd_rae_header_work,
partner_guid TYPE bu_partner_guid.
header_data = cs_work_structure.
IF header_data-zz_object_id IS INITIAL.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZCD_RAPID'
IMPORTING
number = header_data-zz_object_id
EXCEPTIONS
interval_not_found = 1
number_range_not_intern = 2
object_not_found = 3
quantity_is_0 = 4
quantity_is_not_1 = 5
interval_overflow = 6
buffer_overflow = 7
OTHERS = 8.
IF sy-subrc <> 0.
RETURN.
ENDIF.
ENDIF.
IF header_data-zz_created_by IS INITIAL.
partner_guid = me->get_partner_guid_from_uname( ).
header_data-zz_created_by = partner_guid.
header_data-zz_created_at = sy-datum.
header_data-zz_changed_by = partner_guid.
header_data-zz_changed_at = sy-datum.
ENDIF.
cs_work_structure = header_data.
ENDMETHOD.
METHOD if_ex_axt_rt_tables_api~on_change.
DATA: header_data TYPE zcd_rae_header_work.
header_data = cs_work_structure.
header_data-zz_changed_by = me->get_partner_guid_from_uname( ).
header_data-zz_changed_at = sy-datum.
ENDMETHOD.
Issue Invoking the BAdI
The problem with the current status of the example application is that the BAdI implementation ZCD_AXT_TABLES_RAE_HEADER_IMPL will never get called. The reason is, that the Rapid Application framework checks if a logical key (the field ZZ_OBJECT_ID in the example application) before calling the BAdI. If the logical key is initial, an error message is raised an the BAdI is not called. Consequently it is impossible to implement the requirement to get the object ID from a number range object using only the BAdI AXT_RT_TABLES_API.
The only solution I found to circumvent this problem was to overwrite the DO_CREATE_ROOT method in the implementation class of the overview page ZCD_RAE_HEADER_OVP.
In general I don’t like the idea of having to enhance the overview page to enable the automatic creation of the object ID. Because the Rapid Application framework forces me to do this I have the logic to create an object is distributed across different parts of the application. However, I found a rather elegant solution for overwriting the method DO_CREATE_ROOT, which is shown in the following code snippet.
In order to create the root entity I simply invoke ON_CREATE method of the BAdI implementation in line 8. The loop in lines 17-22 then maps the resulting data to the creation parameters of the root entity. The root entity is then create in line 24.
METHOD do_create_root.
DATA: header_data TYPE zcd_rae_header_work.
DATA(bol_core) = cl_crm_bol_core=>get_instance( ).
DATA(entity_factory) = bol_core->get_entity_factory( mv_main_entity ).
DATA(creation_params) = entity_factory->get_parameter_table( ).
NEW zcl_axt_tables_rae_header_impl( )->if_ex_axt_rt_tables_api~on_create(
EXPORTING
iv_object_id = ''
iv_parent_id = ''
iv_record_id = ''
CHANGING
cs_work_structure = header_data
).
LOOP AT creation_params ASSIGNING FIELD-SYMBOL(<creation_param>).
ASSIGN COMPONENT <creation_param>-name OF STRUCTURE header_data TO FIELD-SYMBOL(<comp>).
IF <comp> IS ASSIGNED.
<creation_param>-value = <comp>.
ENDIF.
ENDLOOP.
DATA(bol_col) = bol_core->root_create( iv_object_name = mv_main_entity
iv_create_param = creation_params
iv_number = 1 ).
eo_entity = bol_col->get_first( ).
ENDMETHOD.
Checking the Attribute Validity
In order to check the attribute validity using BRFplus I created another implementation ZCD_AXT_TABLES_RAE_ATTRIB_IMPL of the BAdI AXT_RT_TABLES_API. For this implementation, however, the filter value ZCD_RAE_ATTRIBS = TABLE_ID is used (see screen shot below).
In the implementation class of the BAdI only the CHECK method is used. The code snippet below shows the implementation of the method. In order to check the attributes validity according to the defined rules, it is important to get all attributes of a header record. This is done by first getting the current attribute entity using the RECORD_ID and PARENT_ID in lines 8-13. From the current attribute entity the header entity is read in lines 19. In lines 24-34 all attribute entities related to the header entity are read and the attribute table for the invocation of the BRFplus function is prepared. The simplest way to find the correct entity names and relations is, as with other CRM applications, to use the BOL Model Browser in the transaction BSP_WD_CMPWB_NEW. Finally, the BRFplus function is invoked in line 36.
METHOD if_ex_axt_rt_tables_api~check.
DATA: bol_key TYPE axts_ca_bol_key,
attrib_properties TYPE zcd_rae_attribs_attr,
attrib_properties_tab TYPE STANDARD TABLE OF zcd_rae_attribs_attr.
DATA(bol_core) = cl_crm_bol_core=>get_instance( ).
ASSIGN COMPONENT 'RECORD_ID' OF STRUCTURE is_work_structure TO FIELD-SYMBOL(<record_id>).
ASSIGN COMPONENT 'PARENT_ID' OF STRUCTURE is_work_structure TO FIELD-SYMBOL(<parent_id>).
bol_key-parent_id = <parent_id>.
bol_key-record_id = <record_id>.
DATA(current_entity) = bol_core->get_entity(
EXPORTING
iv_object_name = 'ZAET_CA_CTAB000014 '
iv_object_id = cl_crm_genil_container_tools=>build_object_id( is_object_key = bol_key ) ).
CHECK current_entity IS BOUND.
DATA(header_entity) = current_entity->get_parent( ).
CHECK header_entity IS BOUND.
DATA(attrib_entities) = header_entity->get_related_entities( iv_relation_name = 'ZAET_CA_TO_CTAB000014' ).
DATA(iterator) = attrib_entities->get_iterator( ).
DATA(attrib_entity) = iterator->get_first( ).
WHILE attrib_entity IS BOUND.
attrib_entity->get_properties(
IMPORTING
es_attributes = attrib_properties
).
APPEND attrib_properties TO attrib_properties_tab.
attrib_entity = iterator->get_next( ).
ENDWHILE.
ct_messages = me->execute_checks_using_brfplus( attrib_properties_tab ).
ENDMETHOD.
Summary
The implementation of the second BAdI concludes the first part of the application. With only a few lines of code an mostly only configuration of the Rapid Application the result is a running example application that already fulfils most of the requirements stated in the application design. In the second part of the blog I’ll show how implement the validity checks for the attributes (ie. the implementation of the method EXECUTE_CHECKS_USING_BRFPLUS as well as the underlying BRFplus function.
Christian
Unfortunately the code snippets look different in the published blog then in the editor. I'll try to fix this.
Christian
You did a awesome job, very easy and straight forward, thanks for mention my blog by the way 🙂
I also had the same issue with the BADI on creation, and I went to the same path, it's a shame there's no BADI invocation on the DO_CREATE_ROOT, right know I have two ideas to improve that:
-You can create a public static method on the BADI class and call it from the WebUI redefinition, is a patch, but at least you have all the code on the same object.
-Even better, you can create your own BADI and chain the standard BADI and your custom BADI with a composite enhancement point, to be honest I placed my code on the DO_CREATE_ROOT but I believe this workaround can work pretty well.
Did I heard BRF+ for CRM benginers? yes please
Cheers!
Luis
Hi , good blog. i have created a custom table using Rapid applications.
in the table I have added button" delete" in OCA, when i click on that it is directly deleting the reord DB, it is not reverting the record even though I click on Cancel on overview page.
Could you please help, what is the code I have to wrote to delete the record from buffer instead of DB directly.
regards,rama
Hi Rama,
are you sure about this behaviour. I just checked in one of my rapid apps and clicking delete doesn't delete the data on the DB. Only when I save the transaction the data is deleted on the DB. If I click cancel everything remains unchanged.
Anyway, I'd suggest moving this discussion to the forum as this is the place where such issues should be discussed.
Christian
Hi Christian, very glad to see your quick reply.
Yes, It is deleting the record directly from DB, not reverting back when I click on cancel.
Already i posted the issue in forums as well.
Steps: How I create Rapid application.
1. Login as administrator, create a RAPID application along with table.
2. Brought the Rapid application BSP component into Contacts (BP_CONT) using the Component usage .
3. Wrote the logic in component controller method( BP_CONT) WD_USAGE_INITIALIZE.
4. Saving the data into table working fine.
5. Not working to revert the record when we click on Cancel.
Below is the code of OCA to delete the record.
me->typed_context->result->collection_wrapper->find( iv_index = lv_index ).
lo_current = me->typed_context->result->collection_wrapper->get_current( ).
TRY.
lo_entity ?= lo_current.
CATCH cx_sy_move_cast_error.
ENDTRY.
* Get the action clicked on
SPLIT htmlb_event_ex->event_defined AT '.' INTO lv_event lv_indx.
CASE lv_event.
* Delete the current entity
WHEN 'delete'.
IF lo_entity IS BOUND.
* Lock the entity if locking fails nothing happens
lo_messages_bd = lo_entity->get_message_container( ).
lv_success = lo_entity->switch_to_change_mode( ).
* IF lo_entity->is_locked( ) = abap_true.
IF lv_success = abap_true.
lo_entity->delete( ).
lo_core = cl_crm_bol_core=>get_instance( ).
lo_core->modify( ).
ENDIF.
regards,rama