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

[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.

[HR] Abwesenheiten (IT2001) – Anzeige Kontingentabtragungen

Bei einer Abwesenheit wie z.B. Urlaub, die über eine Monatsgrenze oder über einige Feiertage hinweg geht, kann es hilfreich sein, die konkreten Abtragungen einzusehen, um zu verstehen, welche Tage in dem Zeitraum denn wirklich Urlaub waren.

Dazu einfach die Abwesenheit öffnen in der PA20 und auf Springen → Abtragungen (Shift+F8) gehen.

Man kann die Kontingentabtragung auch für das komplette Kontingent eines Jahres einsehen via IT2006.

Die zugehörige Datenbanktabelle ist PTQUODED. Um die Abträge für eine bestimmte Abwesenheitsart zu selektieren, muss noch ein JOIN auf die PA2001 über die DOCNR gemacht werden. Zumindest ist das der einzige Weg, den ich herausfinden konnte. 🙂

SELECT SUM( a~quode )
    FROM ptquoded AS a
    JOIN pa2001   AS b
      ON a~pernr = b~pernr
     AND a~docnr = b~docnr
    INTO DATA(urlaubsabtrag)
    WHERE a~pernr = pernr-pernr
     AND a~datum BETWEEN pn-begda AND pn-endda
     AND b~subty = 0100. " Abwesenheitsart, welche Selektiert werden soll

[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] OM – Besetzungsplan (Struktur)

Da ich nur selten mit dem OM arbeite, vergessen ich immer wieder, wie man das OM “umdreht”, wenn man eine Person ausgewählt hat. Also die Anzeige von P – S – O umkehrt auf O – S – P.
Dabei ist es total einfach! Die Org. Einheit markieren und links oben in der Toolbar auf den Dropdown Button (Springen) gehen und Besetzungsplan (Struktur) auswählen und man erhält die gewünschte Ansicht.

[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] Read attendance/absence quotas

When searching for a way to select quotas without doing a manual select, I found the following class is the Fiori odata packages.

DATA pernr TYPE pernr_d VALUE 1.

DATA(time_accounts) = cl_hcmfab_att_abs_bl_apis=>get_instance( )->read_time_accounts( iv_pernr = pernr
                                                                                      iv_begda = sy-datum
                                                                                      iv_endda = sy-datum ). "iv_endda is never used!

cl_demo_output=>display( time_accounts ).

But although there is an obligatory iv_endda parameter, it is never used inside of method read_time_accounts. Instead, there is some logic which checks for a T77S0 Parameter, and it will use either highdate or begda as endda value.

IMO, it would have made sense to mark iv_endda as optional, so it would be downward compatible and the parameter could be omitted on newer releases. Now it is a bit misleading.

However, if you look inside the method read_time_accounts you’ll find another class that is used to read the quotas. The names of the result fields are slightly different, but at least the endda is used in this case.

  cl_pt_arq_timeaccounts=>get_instance( )->get_time_accounts( EXPORTING im_pernr        = pernr
                                                                        im_begda        = sy-datum
                                                                        im_endda        = sy-datum
                                                              IMPORTING ex_timeaccounts = DATA(time_accounts) ).

[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.