nocin.eu

Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[Workflow] AC Rule

Recently, I had the task of assigning a workflow decision step to some users that were customized in a Z-Table. Normally I would have done this, by creating a container element “Actors”, a task that calls a method to return these Actors and use these Actors in the decision step. But this time I tried a custom AC Rule for the first time and I must say it was much easier than expected.

To begin, simply create a function module. The function module only needs two table parameters, AC_CONTAINER and ACTOR_TAB. They can be copied from RH_GET_ACTORS or GM_GET_RESP_FROM_INTERNAL. There you can also check, how to access the AC_CONTAINER table, if you need any input values from the WF. In my case, I just had to read the Z-Table and return the result via ACTOR_TAB.

SELECT * FROM ztable INTO TABLE @DATA(lt_table).

LOOP at lt_table INTO DATA(ls_table).
  "you can also check if the provided user exists by using function module 'SUSR_USER_CHECK_EXISTENCE'
  APPEND VALUE #( otype = 'US' objid = ls_table-userid ) TO actor_tab.
ENDLOOP.

IF lines( actor_tab) = 0.
  RAISE nobody_found.
ENDIF.

Then simply go to tcode PFAC, create a rule, provide the function module and check the flag box at the end.

In the PFAC transaction, you can also simulate the rule resolution.

If everything works fine, add the new created rule to the decision step.

And done. Really straight forward.

[ABAP] Get Filename and Mimetype from uploaded file

    DATA data_tab TYPE solix_tab.
    DATA filename TYPE string.
    DATA path     TYPE string DEFAULT 'C:\Users\path\to\my\file.pdf'.

    cl_gui_frontend_services=>gui_upload( EXPORTING filename   = path )
                                                    filetype   = 'BIN'
                                          CHANGING  data_tab   = data_tab ).

    CALL FUNCTION 'SO_SPLIT_FILE_AND_PATH'
      EXPORTING
        full_name     = path 
      IMPORTING
        stripped_name = filename
      EXCEPTIONS
        x_error       = 1
        OTHERS        = 2.

    DATA(file_extension) = /iwwrk/cl_mgw_workflow_rt_util=>get_extention_from_file_name( filename ).
    DATA(mimetype)       = /iwwrk/cl_mgw_workflow_rt_util=>get_mime_type_from_extension( file_extension ).

Instead of SO_SPLIT_FILE_AND_PATH you can also use PC_SPLIT_COMPLETE_FILENAME.

[Home Assistant] Editor shortcuts

All VS Code shortcuts will also work in Home Assistant. I mostly need the following:

TabMove lines to right
Ctrl + Tab (on Linux Mint it’s Shit + Tab)Move lines to left
Ctrl + Alt + Mouse selectionMark area over multiple lines (works only in YAML editor)
Ctrl + Shift + KDelete row
Alt + Arrow key up or downMove row(s) up or down
Alt + Shift + Arrow downDuplicate selected rows
Ctrl + Shit + /Comment line/area

Also, you can simply expand the window you are working in, by clicking on the window title.

[ABAP] Validate date

CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
  EXPORTING
    date                      = '20250231' " invalid date
  EXCEPTIONS
    plausibility_check_failed = 1
    OTHERS                    = 2.
IF sy-subrc <> 0.
  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.

If you’re not interested in a specific error message, you can also use the following method to get the result as boolean.

DATA(date_is_valid) = cl_rs_time_service=>is_valid_date( '20250231' ).

[ABAP] BASE CORRESPONDING

I recently had to rewrite a Report where I saw code that looked similar to this.

TYPES:

  BEGIN OF ty_employee,
    vorna TYPE vorna,
    nachn TYPE nachn,
  END OF ty_employee,
  tt_employee TYPE TABLE OF ty_employee WITH DEFAULT KEY,

  BEGIN OF ty_source,
    col1 TYPE c LENGTH 2,
    col2 TYPE c LENGTH 2,
    col3 TYPE c LENGTH 2,
    col4 TYPE c LENGTH 2,
  END OF ty_source,
  tt_source TYPE TABLE OF ty_source WITH DEFAULT KEY,

  BEGIN OF ty_target,
    col1       TYPE c LENGTH 2,
    col2       TYPE c LENGTH 2,
    col3       TYPE c LENGTH 2,
    col4_vorna TYPE vorna,
    col5_nachn TYPE nachn,
  END OF ty_target,
  tt_target TYPE TABLE OF ty_target WITH DEFAULT KEY.

* Test employees
DATA(employees) = VALUE tt_employee( ( vorna = 'Max'   nachn = 'Mustermann' )
                                     ( vorna = 'Erika' nachn = 'Mustermann' ) ).

* In reallity, this table changed per employee
DATA(source_tab) = VALUE tt_source( ( col1 = 'a1' col2 = 'b1' col3 = 'c1' col4 = 'd1' )
                                    ( col1 = 'a2' col2 = 'b2' col3 = 'c2' col4 = 'd2' )
                                    ( col1 = 'a3' col2 = 'b3' col3 = 'c3' col4 = 'd3' ) ).

* Actual logic
DATA target_line TYPE ty_target.
DATA target_tab1 TYPE tt_target.

LOOP AT employees INTO DATA(employee).
  LOOP AT source_tab INTO DATA(source_line).
    CLEAR target_line.
    target_line-col4_vorna = employee-vorna.
    target_line-col5_nachn = employee-nachn.
    MOVE-CORRESPONDING source_line TO target_line.
    APPEND target_line TO target_tab1.
  ENDLOOP.
ENDLOOP.

cl_demo_output=>write( employees ).
cl_demo_output=>write( source_tab  ).
cl_demo_output=>write( target_tab1  ).
cl_demo_output=>display( ).

I spend some time thinking, how I could improve the actual logic by using some “new” ABAP syntax elements. I came up with the following result.

DATA target_tab2 TYPE tt_target.

LOOP AT employees INTO employee.
  target_tab2 = VALUE #( BASE CORRESPONDING #( target_tab2 )
                         FOR line IN source_tab (
                          VALUE #( BASE CORRESPONDING #( line )
                           col4_vorna = employee-vorna
                           col5_nachn = employee-nachn ) ) ).
ENDLOOP.

But I must say, that in this case I prefer using nested LOOP’s like this

DATA target_tab3 TYPE tt_target.

LOOP AT employees INTO employee.
  LOOP AT source_tab INTO source_line.
    DATA(t_line)      = CORRESPONDING ty_target( source_line ).
    t_line-col4_vorna = employee-vorna.
    t_line-col5_nachn = employee-nachn.
    APPEND t_line TO target_tab3.
  ENDLOOP.
ENDLOOP.

or this

DATA target_tab4 TYPE tt_target.

LOOP AT employees INTO employee.
  LOOP AT source_tab INTO source_line.
    APPEND VALUE #( BASE CORRESPONDING #( source_line )
                    col4_vorna = employee-vorna
                    col5_nachn = employee-nachn ) TO target_tab4.
  ENDLOOP.
ENDLOOP.

[ABAP] E-Mail-Templates in S/4HANA

Recently I had to create a mail process where I used the first time the “new” E-Mail-Templates. The following Blogs helped me do this:

https://weberpatrick.de/e-mail-templates/
https://weberpatrick.de/e-mail-templates-einfaches-beispiel/
https://weberpatrick.de/e-mail-template-daten-per-code-statt-cds/

https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/e-mail-templates-in-s-4-hana/ba-p/13397719
https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/e-mail-templates-in-s-4-hana-translations/ba-p/13440792
https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/e-mail-templates-in-s-4-hana-display-table-in-email-template/ba-p/13546304

https://www.solviads.com/e-mail-template-in-sap-s-4-hana

In my case it was not possible to fetch the data via CDS View, I therefore had to manually fill the variables. Following a simplified version of my code.

You must create a CDS View, as this is the structure in which the variables defined in the mail text are mapped to, even if you do this manually.

define view entity ZMAIL_MY_TEMPLATE as select from pa0001 {
    key pa0001.pernr,
    pa0001.ename,
    cast ( '' as abap.char( 20 ) ) as custom_field // this way you can add a field, that does not exist in the selected table
}

This is just one way, how you can manually fill the variables. Other ways are possible using the class cl_smtg_email_api. I choose the method render_w_data because it returned the mail text, and you manually can do additional changes to the mail text, like adding HTML-Table content, which otherwise gets “escaped” when filled via a replacement variable.

    " dynamicaly build table for mail variables
    DATA(lt_comp) = VALUE abap_component_tab( ( name = 'ename' type = CAST #( cl_abap_typedescr=>describe_by_name( 'EMNAM'   ) ) )
                                              ( name = 'pernr' type = CAST #( cl_abap_typedescr=>describe_by_name( 'PERNR_D' ) ) ) ) .

    " fill table with concrete data
    DATA(lo_struct) = cl_abap_structdescr=>get( lt_comp ).
    CREATE DATA mr_data TYPE HANDLE lo_struct.
    ASSIGN mr_data->* TO FIELD-SYMBOL(<ls_data>).
    <ls_data>-(1) = ename.
    <ls_data>-(2) = pernr.

    " create instance of eMail API, provide name of Mail template
    DATA(lo_email_api) = cl_smtg_email_api=>get_instance( iv_template_id = 'ZMAIL_MY_TEMPLATE' ).

    " render E-Mail for CL_BCS class. This replaces all placeholders with real data
    lo_email_api->render_w_data( EXPORTING iv_language  = sy-langu
                                           ir_data      = mr_data
                                 IMPORTING ev_subject   = DATA(subject)
                                           ev_body_html = DATA(body_html) ).

    TRY.
        " create Sender & Receiver
        DATA(lo_sender)    = cl_cam_address_bcs=>create_internet_address( i_address_string = 'noreply@test.de'
                                                                          i_address_name   = 'Test' ).
        DATA(lo_recipient) = cl_cam_address_bcs=>create_internet_address( i_address_string = 'receiver@test.de').

        " create mail document
        DATA(lo_mail_document) = cl_document_bcs=>create_document( i_type    = 'HTM'
                                                                   i_subject = CONV #( subject )
                                                                   i_text    = cl_bcs_convert=>string_to_soli( body_html ) ).

        " create Business Communication Service
        DATA(lo_bcs) = cl_bcs=>create_persistent( ).
        lo_bcs->set_document( lo_mail_document ).
        lo_bcs->set_sender( lo_sender ).
        lo_bcs->add_recipient( lo_recipient ).
        lo_bcs->send( ).
        COMMIT WORK.                                       
      CATCH cx_smtg_email_common INTO DATA(ls_cx).
        DATA(lv_message) = ls_cx->get_text( ).
        MESSAGE s899(id) WITH 'Unable to send message:'(004) lv_message.
    ENDTRY.