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

[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

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.

[ABAP] Get the last day of the previous month

I recently had to find the last day of the previous month. I found the Method get_last_day_prev_month of class cl_bs_period_toolset_basics doing the job. The actual logic is super simple:

  rv_date      = iv_date.
  rv_date+6(2) = '01'.
  rv_date      = rv_date - 1.

That’s why I thought about whether I needed a separate method at all, or whether I could do it directly in a one-liner. My first idea was to use the LET operator in combination with CONV (Option 2). This works, but does not look very intuitive, and I noticed it can be done even simpler without using LET (Option 3). And if you already have a typed variable, you can do it even shorter with a single CONV (Option 4).

* Option 1
DATA(date_1) = cl_bs_period_toolset_basics=>get_last_day_prev_month( sy-datum ).

*Option 2
DATA(date_2) = CONV d( LET x = CONV d( sy-datum(6) && '01' ) IN x - 1 ).

*Option 3
DATA(date_3) = CONV d( CONV d( sy-datum(6) && '01' ) - 1 ).

* Option 4
DATA date_4 TYPE datum.
date_4 = CONV d( sy-datum(6) && '01' ) - 1.

cl_demo_output=>write_data( date_1 ).
cl_demo_output=>write_data( date_2 ).
cl_demo_output=>write_data( date_3 ).
cl_demo_output=>write_data( date_4 ).
cl_demo_output=>display( ).

All in all, I have to say that using the get_last_day_prev_month method is still the best solution as it is much more readable.

[HR] A1-Meldeverfahren

Übersicht

Gute Zusammenfassung: https://www.iprocon.de/abbildung-des-a1-meldeverfahrens-in-sap-hcm/

2682093 – Informationen zur Umsetzung des A1-Meldeverfahrens im SAP-System (siehe auch das PDF im Hinweis)

2730927 – A1-Verfahren: Hilfestellung zum Customizing

2833850 – A1-Verfahren: Archivierung der A1-Bescheinigung und Erweiterung für das BAdI HRPAYDE_A1_EMAIL

2841779 – A1-Verfahren: ESS-Szenario (Web-Dynpro ABAP) (Paket PAOC_ESS_A1_DE)

Die Datenerfassung der Antragsdaten erfolgt im Infotyp: 0700 (Elektronischer Datenaustausch)
Subtypen:

  • DXA1 (A1: Antrag Entsendebescheinigung)
  • DXAV (A1: Antrag Ausnahmevereinbarung)

Reports zur Erstellung der Meldungen und der Meldedateien:

  • Personal → Personalabrechnung → Europa → Deutschland → Folgeaktivitäten → Periodenunabhängig → Abrechnungszusatz → A1-Meldeverfahren → Ausgangsmeldungen

Reports zur Verarbeitung der Eingangsmeldungen:

  • Personal → Personalabrechnung → Europa → Deutschland → Folgeaktivitäten → Periodenunabhängig → Abrechnungszusatz → A1-Meldeverfahren → Eingangsmeldungen

Behördenkommunikation (B2A): Transaktion PB2A

  • Personal → Personalabrechnung → Europa → Deutschland → Folgeaktivitäten → Periodenunabhängig → Behördenkommunikation (B2A) → B2A-Manager

Hilfreiche Entwicklungsobjekte

Paket: P01S

Reports:

Klassen:

Für mich hilfreich waren folgenden Klassen:

  • CL_HRPAYDE_A1_NOTIF (Klasse für A1-Meldungen)
  • CL_HRPAYDE_A1_NOTIF_DISPLAYER (ALV-Ausgabe für A1-Meldungen)
  • CL_HRPAYDE_A1_ALV_EVNT_HANDLER (Ereignisbehandler für A1-spezifische Ereignisse)

PDFs der A1-Meldungen von Datenbank lesen

*&---------------------------------------------------------------------*
*& Tabellen A1-Meldeverfahren:
*& P01A1_STAT    - A1-Verfahren: Verwaltungstabelle
*& P01A1_RAWDATA - A1-Verfahren: Rohdaten einer Meldung
*&---------------------------------------------------------------------*

GET peras.

  SELECT * INTO TABLE @DATA(lt_p01a1_stat) FROM p01a1_stat
    WHERE pernr  = @pernr-pernr
      AND mdtyp  = '2'
      AND status = '23'
      AND mzbeg <= @pn-endda
      AND mzbeg >= @pn-begda.

  LOOP AT lt_p01a1_stat INTO DATA(ls_p01a1_stat).

    SELECT SINGLE * INTO @DATA(ls_p01a1_rawdata) FROM p01a1_rawdata
      WHERE guid = @ls_p01a1_stat-guid
        AND lfdnr = ( SELECT MAX( lfdnr ) FROM p01a1_rawdata WHERE guid = @ls_p01a1_stat-guid ). "höchste lfdnr nehmen

    DATA(pdf_xstring) = ls_p01a1_rawdata-rawdata.

  ENDLOOP.

[ABAP] REDUCE as an alternative to line_exists for the use of other comparison operators such as >, <, >=, <=, CS, CN

By using line_exists you can check if a line exists or not exists, but it is not possible to use other comparison operators. A workaround can be REDUCE in that case.

In the following snippet, we want to check if there is a flight that costs more than $1000,

SELECT * FROM sflight INTO TABLE @DATA(flights).

* Check if flight with price > 1000 exist
IF REDUCE abap_bool( INIT price_gt_1000 = abap_false
                     FOR  line IN flights WHERE ( price > 1000 )
                     NEXT price_gt_1000 = abap_true ) EQ abap_true.
  " flight with price > 1000 exists
ENDIF.

[ABAP] LOOP AT VALUE

Easy way to loop over multiple tables of the same type, without having to create an additional table to join the data before the loop.

SELECT FROM spfli FIELDS * WHERE carrid EQ 'AA' INTO TABLE @DATA(lt_spfli_1).

SELECT FROM spfli FIELDS * WHERE carrid EQ 'AZ' INTO TABLE @DATA(lt_spfli_2).

LOOP AT VALUE spfli_tab( ( LINES OF lt_spfli_1 )
                         ( LINES OF lt_spfli_2 )
                         ( carrid = 'AV' ) ) ASSIGNING FIELD-SYMBOL(<spfli>).
 WRITE <spfli>-carrid.
ENDLOOP.

Got this snippet from George Drakos talk from the ABAPConf 2024. Check his Github.

[ABAP] CATS – Difference between two times (CATSHOURS)

I had to calculate the difference between two times, even if the end time goes beyond the 0 o’clock day limit, e.g. 20:00 to 02:00 should be 6h. I found the following two function modules doing the job:

* Option 1
CALL FUNCTION 'CATS_COMPUTE_HOURS'
  EXPORTING
    pernr                    = pernr
    date                     = date
*   NO_BREAK_DEDUCTION       = ' '
    row                      = row
  TABLES
    return                   = return
  CHANGING
    catshours                = catshours
    beguz                    = beguz
    enduz                    = enduz
    vtken                    = vtken.

* Option 2 (I think it's only available on S/4 HANA)
CALL FUNCTION 'CATS_DETERMINE_HOURS'
  CHANGING
    catshours                = catshours
    beguz                    = beguz
    enduz                    = enduz.

[HR] Anzeige Zeitauswertungsergebnisse (Cluster B2) – Zeitsalden kumuliert (SALDO)

PT_CLSTB2 → SALDO

Alternative Report: RPCLSTB2 – Anzeige Zeitauswertungsergebnisse (Cluster B2)

DATA pernr TYPE pernr_d.
DATA saldo TYPE TABLE OF pc2b5.

CALL FUNCTION 'HR_TIME_RESULTS_GET'
  EXPORTING
    get_pernr             = pernr
    get_pabrj             = CONV pabrj( sy-datum(4) )
    get_pabrp             = CONV pabrp( sy-datum(6) )
  TABLES
    get_saldo             = saldo
  EXCEPTIONS
    no_period_specified   = 1
    wrong_cluster_version = 2
    no_read_authority     = 3
    cluster_archived      = 4
    technical_error       = 5
    OTHERS                = 6.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.