Skip to Content

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.

Two multilevel categorizations on one order.png

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.
/
Two multilevel categorizations on one order.png
5 Comments
You must be Logged on to comment or reply to a post.
  • Hi Jonathan,

    Thank you for your effective and very helpful blog.

    I need your help to solve mine. I followed your code to set category. The values are getting populated but its not saved. I wonder why even after the usage of crm_order_save and bapi_transaction_commit.

    Please help me with this asap.

    Regards,

    Simin

    • Hi Simin,

      So sorry to only be replying now - it seems that SCN didn't notify me of your comment.

      Did you come right with setting the category?

      Regards,

      Jonathan

  • Hi Jonathan,

    Fully agree with your thoughts ... the multi level categorization schema are so big and there is a real lack of documentation or possibility to maintain it in simple way.

    Thanks for source code.

    Cheers Daniyar

  • Hi all, i found some easy way to change it

    *-----------------------------------------------------------------------

    * set subject profile, catalog, code groups and codes

    *-----------------------------------------------------------------------

      CLEAR ls_service_h.

      CALL FUNCTION 'CRM_ORDER_SERVICE_H_SELECT_CB'
        EXPORTING
          iv_process_type = iv_proc_type
        IMPORTING
          es_service_h    = ls_service_h
        EXCEPTIONS
          entry_not_found = 1

          OTHERS          = 2.

      IF sy-subrc <> 0.

        IF NOT sy-msgid IS INITIAL.
          CLEAR ls_msg.
          MOVE-CORRESPONDING sy TO ls_msg.
          APPEND ls_msg TO et_msg.
        ENDIF.

        es_error_occurred = 'X'.

        EXIT.

      ENDIF.

      ls_subject-subject_profile = ls_service_h-subject_profile.
      ls_subject-profile_type    = gc_prof_service_subject.

      IF  iv_catalog-code IS NOT INITIAL
       AND iv_catalog-codegruppe IS NOT INITIAL.

        REFRESH lt_codes[].

        CALL FUNCTION 'SUP_STSUP_CATALOG_GETT'
          EXPORTING
            iv_process_type       = iv_proc_type
            iv_no_authority_check = gc_false
            iv_released_only      = gc_true
          TABLES
            et_codes              = lt_codes[]
          EXCEPTIONS
            OTHERS                = 1.

        IF sy-subrc <> 0.                                       "#EC NEEDED

    *     MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO

    *             WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.

        ENDIF.

        CLEAR ls_codes.

        READ TABLE lt_codes[] INTO ls_codes

          WITH KEY codegruppe = iv_catalog-codegruppe

                   code       = iv_catalog-code.

        IF sy-subrc EQ 0.

          ls_subject-katalogart   = ls_codes-katalogart.

          ls_subject-codegruppe   = ls_codes-codegruppe.

          ls_subject-code         = ls_codes-code.

          ADD 1 TO ls_subject-sort.

          ls_subject-mode         = gc_mode-create.

        ENDIF.

      ENDIF.

      ls_subject-mode   = 'A'.

      ls_subject-asp_id = iv_catalog-asp_id.

      ls_subject-cat_id = iv_catalog-cat_id.

      ls_subject-katalog_type = iv_catalog-katalog_type.

      CALL METHOD lr_1o_sd->set_subject

        EXPORTING

          is_subject = ls_subject

        EXCEPTIONS

          OTHERS     = 1.

      IF sy-subrc NE 0.

        CLEAR ls_msg.

        es_error_occurred = 'X'.

        MOVE-CORRESPONDING sy TO ls_msg.

        APPEND ls_msg TO lt_msg.

      ENDIF.

        CALL METHOD lr_1o_sd->save

          EXPORTING

            iv_unlock                  = 'X'

          CHANGING

            cv_log_handle              = lv_log_handle

          EXCEPTIONS

            error_occurred             = 1

            OTHERS                     = 2

                .

  • Hi Jonathan,

                 Thank you very much for this wonderful blog! I followed through your codes and could successfully update the categorizations for the service request.

    Also did some in-depth analysis on the table relationships which starts from the order header table CRMD_ORDERADM_H and ends in the CRMD_SRV_SUBJECT which stores the 4th level of categorization pertaining to every service requests in the system and could figure out a way to update the categorizations through some simple SQL statements. The code is as given below. Hope this would be helpful to anyone who goes through this blog henceforth and wants to update the categorization levels.

    The below code could be implemented in the method IF_EX_ORDER_SAVE~CHANGE_BEFORE_UPDATE of the ORDER_SAVE BADI. The best way is to write this as a static method in your 'Z' implementation class for this badi, for instance a method named UPDATE_SR_CATEGORIZATION, and call it in the former method.

    Parameters for the method UPDATE_SR_CATEGORIZATION

    SR_GUID TYPE CRMT_OBJECT_GUID (Importing) (GUID of the service request)

    SR_CAT_ID TYPE CRM_ERMS_CAT_CA_ID (Importing) (Category ID which you want to update or insert - This must be the ID of the 4th level of categorization)

      DATA: lt_crmd_subject TYPE TABLE OF crmd_srv_subject,

            wa_crmd_subject LIKE LINE  OF lt_crmd_subject,

            lv_guid TYPE crmt_object_guid,

            lv_cat_id TYPE crm_erms_cat_as_id.

      SELECT SINGLE b~guid

       INTO lv_guid

       FROM crmd_srv_osset AS b

        INNER JOIN crmd_link AS c

        ON b~guid_set = c~guid_set

        INNER JOIN crmd_orderadm_h AS d

        ON c~guid_hi = d~guid

        WHERE c~objtype_set = '29'

          AND d~guid = sr_guid.

      IF sy-subrc EQ 0.

        SELECT *

         INTO TABLE lt_crmd_subject

         FROM crmd_srv_subject

         WHERE guid_ref = lv_guid.

        IF sy-subrc EQ 0.

          SORT lt_crmd_subject BY timestamp DESCENDING.

          CLEAR: wa_crmd_subject.

          READ TABLE lt_crmd_subject INTO wa_crmd_subject INDEX 1.

          IF sy-subrc EQ 0.

            wa_crmd_subject-cat_id = sr_cat_id.

            CLEAR wa_crmd_subject-guid_hierarchy.

            MODIFY lt_crmd_subject FROM wa_crmd_subject INDEX sy-tabix.

          ENDIF.

    * Modify DB table

          MODIFY crmd_srv_subject FROM TABLE lt_crmd_subject.

        ELSE.

    * Create a new categorization

          CLEAR: wa_crmd_subject.

          wa_crmd_subject-guid_ref      = lv_guid.

          wa_crmd_subject-username   = sy-uname.

          wa_crmd_subject-asp_id        = 'Z_YOUR_CAT_SCHEMA'.

          wa_crmd_subject-cat_id         = sr_cat_id.

          wa_crmd_subject-katalog_type  = 'C'.

          wa_crmd_subject-sort          = 1.

          GET TIME STAMP FIELD wa_crmd_subject-timestamp.

          CALL FUNCTION 'CRM_GUID_CREATE'

            IMPORTING

              ev_guid = wa_crmd_subject-guid.

          INSERT crmd_srv_subject FROM wa_crmd_subject.

        ENDIF."IF sy-subrc EQ 0

      ENDIF."IF sy-subrc EQ 0

    Thanks & Regards,

    Sarath.