How to set multi-level categorization – CRM orders
Recently, I’ve had the requirement to set the multi-level categorization on a CRM service request/order. After going ten rounds with it, I think it worth writing a blog post describing the trials and tribulations and the best approach that can be used. It should save someone out there a lot of wasted effort and frustration to know the principal of how to set these fields.
Some of the information out there is not exactly helpful, especially for programmers looking for BAPI/API calls that can be made to set the categorization. It may even be suggested that one should the use the BOL programming model ( see http://scn.sap.com/thread/1434317 ) – this is not so easily accomplished, especially in the absence of documentation.
So how do you do it? (Spoiler at the end of this paragraph!). Very quickly one might find that the values are stored on tables CRMD_SRV_OSSET and CRMD_SRV_SUBJECT, can be read via function CRM_ORDER_READ (they come back in the SUBJECT table) and that there are many tantalizing low-level functions in package CRM_ERMS_CATEGORIZATION. But, if you were to debug how the web gui sets a categorization on an order you would find that the standard code does this by calling function CRM_ORDER_MAINTAIN. And that is how you do it – by calling that function in the same way, with the same parameters as the standard code does. There are several pages on SCN where such a call is described, but bear in mind that the example code given on these pages (and at the end of this post) are specific to someone else’s system and not your own.
So, the general principle is to call function CRM_ORDER_MAINTAIN. This is easier said than done. If you debug, you’ll find that two tables provided to this function are critical -it_service_os and ct_input_fields. Within the service_os table there is an “osset” table and within each row of this table there is a “subject” table that holds the lowest tier of the multi-level categorization that is being set. At each of these levels, the entity is referred to either by it’s GUID or by a handle (if the entity is still being created). Since in my particular case this is for a pre-existing order to which we are adding categorization then a GUID is used and not a handle. But where do you get the GUIDs from? In my case they are obtained as follows:
– The order GUID (as per table CRMD_ODERADM_H) should be set on the service_os table and input_fields tables.
– The “osset” table GUID can be obtained by calling function CRM_SERVICE_OS
– The lowest level “subject” table GUID is generated yourself (for example by calling function CRM_GUID_CREATE).
A DISCLAIMER is due here: this information was all obtained through trial and error and debugging, it seems that there isn’t an officially supported way to do what is probably quite a common requirement. Our lives would be a lot simpler if there was an official way and documentation, and I would love to be proven wrong and have someone point out that these exist.
One more thing, it is not enough to simply call CRM_ORDER_MAINTAIN. CRM_ORDER_SAVE also needs to be called to save changes to the database. I’ve also found through experience that these functions may need to be called in a seperate process to the work process that creates the order. (For instance, by scheduling a background job using functions JOB_OPEN etc; scheduling a function to run in update task didn’t seem to be suitable for this problem either.)
I’m loath to offer my code here (that I used to solve my particular problem) as people may be inclined to simply paste it onto their systems and expect it to run immediately. However, an example is a good aid too, so it is being offered below. Bear in mind that things will need to be changed in this code. Your profile type fields, and mode values may be different. So first and foremost debug and see how CRM_ORDER_MAINTAIN is being called and adjust this code accordingly:
function zcrm_add_categorization. *"---------------------------------------------------------------------- *"*"Local Interface: *" IMPORTING *" VALUE(IV_CATEGORY) TYPE STRING *" VALUE(IV_ACTIVITY_GUID) TYPE GUID_32 *" VALUE(IV_ACTIVITY_OBJECT_ID) TYPE CRMT_OBJECT_ID *"---------------------------------------------------------------------- * Add a categorization to an existing service request ("activity") * * IV_ACTIVITY_GUID is the order GUID, as per table CRMD_ORDERADM_H * IV_ACTIVITY_OBJECT_ID is the order number * IV_CATEGORY is the cat id as per table CRMD_SRV_SUBJECT *LOCAL include crm_object_names_con. include crm_object_kinds_con. data lr_aspect type ref to if_crm_erms_catego_aspect. data lr_categoryif type ref to if_crm_erms_catego_category. data lr_category type ref to cl_crm_erms_catego_ca_default. data lv_cat type crmt_erms_cat_ca_buf. data ls_conc type zcrmt_erms_cat_code_trip. data lv_obj_guid type crmc_erms_cat_ln-obj_guid. data lv_obj_extkey type crmc_erms_cat_ok-obj_extkey. data lv_process_type type crmt_process_type. data ls_service_h type crmc_service_h. data ls_crmt_srv_osset_wrk type crmt_srv_osset_wrk. data ls_temp_osset type crmt_srv_osset_wrk1. data lv_osset_guid like ls_temp_osset-guid. data lv_cat_id type crm_erms_cat_ca_id. data lt_subject type crmt_srv_subject_comt. data ls_subject type line of crmt_srv_subject_comt. data ls_osset type crmt_srv_osset_com1. data lt_service_os type crmt_srv_osset_comt. data ls_service_os like line of lt_service_os. data lt_input_fields type crmt_input_field_tab. data ls_input_fields like line of lt_input_fields. data ls_input_field like line of lt_input_fields. data ls_input_field_names like line of ls_input_field-field_names. data lt_objects type crmt_object_guid_tab. data lv_service_guid type crmt_object_guid. data ls_log type zcrm_erms_sr_log. * field-symbols <ls_activity> like line of it_activities. constants cv_link_type type char8 value 'IS_CODE'. constants cv_asp_id type crm_erms_cat_as_id value 'Z_YOUR_CAT_SCHEMA'. *Need the external key, so that we know the code group, etc. *...determine cat guid lv_cat_id = iv_category. cl_crm_erms_catego_ma_default=>if_crm_erms_catego_manager~get_aspect( exporting iv_asp_id = cv_asp_id iv_asp_state = if_crm_erms_catego_const=>gc_as_state_executable importing ev_instance = lr_aspect ). call method lr_aspect->get_cat exporting iv_cat_id = lv_cat_id importing ev_instance = lr_categoryif. lr_category ?= lr_categoryif. call method lr_category->if_crm_erms_catego_category~get_details importing ev_cat = lv_cat. *...with the cat guid we can now determine the external key select single obj_guid from crmc_erms_cat_ln into lv_obj_guid where cat_guid = lv_cat-cat_guid and lnk_type = cv_link_type. if sy-subrc = 0. select single obj_extkey from crmc_erms_cat_ok into lv_obj_extkey where obj_guid = lv_obj_guid. if sy-subrc = 0. ls_conc = lv_obj_extkey. endif. endif. *...call functions to get the guids we're going to need to put in the service_os table select single process_type from crmd_orderadm_h into lv_process_type where guid = iv_activity_guid. check sy-subrc = 0. call function 'CRM_ORDER_SERVICE_H_SELECT_CB' exporting iv_process_type = lv_process_type importing es_service_h = ls_service_h exceptions entry_not_found = 1 others = 2. if sy-subrc <> 0. clear ls_service_h. endif. lv_service_guid = iv_activity_guid. call function 'CRM_SERVICE_OS_READ_OB' exporting iv_ref_guid = lv_service_guid iv_ref_kind = 'A' importing es_srv_osset_wrk = ls_crmt_srv_osset_wrk exceptions entry_does_not_exist = 1 error_occured = 2 parameter_error = 3 others = 4. check sy-subrc = 0. *...subject table ls_subject-katalogart = ls_conc-code_cat. ls_subject-codegruppe = ls_conc-code_grp. ls_subject-code = ls_conc-code_id. ls_subject-asp_id = cv_asp_id. ls_subject-cat_id = lv_cat_id. ls_subject-katalog_type = 'D'. ls_subject-mode = 'B'. call function 'CRM_GUID_CREATE' importing ev_guid = ls_subject-ref_guid. insert ls_subject into table lt_subject . *...osset, contains the subject table clear ls_osset. ls_osset-subject = lt_subject. ls_osset-ref_guid = lv_osset_guid. ls_osset-subject_profile = ls_service_h-subject_profile. ".' ' ls_osset-profile_type = ls_temp_osset-profile_type.".'A' *...service os data, constains osset clear ls_service_os. append ls_osset to ls_service_os-osset. ls_service_os-ref_guid = iv_activity_guid. ls_service_os-ref_kind = 'A'. refresh lt_service_os. append ls_service_os to lt_service_os. *...input fields clear ls_input_field. refresh lt_input_fields. ls_input_field-ref_guid = iv_activity_guid. ls_input_field-ref_kind = gc_object_kind-orderadm_h. ls_input_field-objectname = gc_object_name-service_os. ls_input_field_names-fieldname = 'ASP_ID'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'CAT_ID'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'CODE'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'CODEGRUPPE'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'KATALOGART'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'MODE'. insert ls_input_field_names into table ls_input_field-field_names. ls_input_field_names-fieldname = 'REF_GUID'. insert ls_input_field_names into table ls_input_field-field_names. insert ls_input_field into table lt_input_fields. *Effect change by calling functions to maintain and save CRM order call function 'CRM_ORDER_MAINTAIN' exporting it_service_os = lt_service_os changing ct_input_fields = lt_input_fields exceptions error_occurred = 1 document_locked = 2 no_change_allowed = 3 no_authority = 4 others = 5. ls_log-subrc1 = sy-subrc. check sy-subrc = 0. refresh lt_objects. append iv_activity_guid to lt_objects. call function 'CRM_ORDER_SAVE' exporting it_objects_to_save = lt_objects exceptions document_not_saved = 1 others = 2. endfunction.