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

[Fiori] MyInbox – Leave Request Approval: Genehmigung erfolgreich, aber mit Fehler für Workflow 21500001

Beim Einrichten der 1-Stufigen Genehmigung von Urlaub mit dem Standard Workflow 21500001 bin ich in folgenden Fehler gelaufen in der Inbox nach Drücken des Genehmigungsbuttons.

Merkwürdigerweise wurde der Urlaubsantrag erfolgreich genehmigt und trotzdem gab es eine Fehlermeldung. Nach kurzem debuggen war auch klar warum. Der Antrag wurde erst erfolgreich genehmigt und dann wurde das gleiche Coding nochmal durchlaufen und führte zu einer Kollisionsmeldung.

Daraufhin habe ich mir den BAdI /IWWRK/BADI_WF_BEFORE_UPD_IB angeschaut. Dort konnte ich zwei Implementierungen zum Thema Leave Approval finden und in beiden war der Workflow 21500001 als Filterkriterium eingetragen. Das erklärte, warum die Genehmigungslogik zweimal durchlaufen wurde.

Daraufhin habe ich nach einem Hinweis gesucht und folgenden gefunden: 2658528. Der beschreibt zwar eine andere Fehlermeldung, aber die Lösung war genau die Aussage, die ich gesucht habe.

Die Implementierung HRMSS_LEAVE_APPROVAL musste also deaktiviert werden. Ich habe das über die SE19 gemacht, auch wenn das bedeutete, dass ich nun als letzter Änderer eines SAP-Objekts drin stehe. Anscheinend geht es auch über den Funktionsbaustein ENH_BADI_IMG_ACTIVITY_TOGGLE wie hier beschrieben.

[CAP] Fiori Elements – Add i18n label for bound action

It took me some time to figure out, how I can provide a label for an action parameter.

I try to separate all my translations in label.cds file. This means, the annotations are not placed where the entities or actions are defined. If you have the action definition and the label annotations in one file, you could simply do the following for an action:

trip-service.cds

    entity Trip as projection on db.Trip
        actions {
            action approval();
            action rejection(reason: String @title: '{i18n>reason});
        };

But if you separate both, you need to access somehow the defined action to add the annotation. Following a short snippet, how it works. The important part is, that you have to write a complete new annotation block for your actions.

trip-service.cds without label

service tripService @(requires: 'authenticated-user') {

    entity Trip as projection on db.Trip
        actions {
            action approval();
            action rejection(reason: String);
        };

}

label.cds

using my.namespace as db from '../db/index';
using tripService from '../srv/trip-service';

annotate db.Trip with @title: '{i18n>trips}' {
    description   @title      : '{i18n>description}';
    status        @title      : '{i18n>status}';
    // etc.
}

// solution
annotate tripService .Trip actions {
    rejection(reason @titlel: '{i18n>reason}' )
}

[ABAP] Quick way to get last year’s BEGDA and ENDDA

Using method get_current_year of class cl_hcmfab_reporting_utility you can get BEGDA and ENDDA of the current year, e.g. 01.01.2025 and 31.12.2025

  CALL METHOD cl_hcmfab_reporting_utility=>get_current_year
    EXPORTING
      iv_date  = sy-datlo
    IMPORTING
      ev_begda = DATA(begda)
      ev_endda = DATA(endda).

I couldn’t find a similar method to get the same for the last year. But I could solve the problem with three simple lines.

DATA(last_year) = conv d( sy-datlo - 365 ).
DATA(begda)     = last_year(4) && '0101'.
DATA(endda)     = last_year(4) && '1231'.

After that, I asked Perplexity and it gave me a similar result.

DATA(lv_last_year) = sy-datum+0(4) - 1.
DATA(lv_begda)     = |{ lv_last_year }0101|.
DATA(lv_endda)     = |{ lv_last_year }1231|.

Using this method, you could even reduce it to two lines, and it would still be easy to read.

DATA(begda)  = |{ sy-datum(4) - 1 }0101|.
DATA(endda)  = |{ sy-datum(4) - 1 }1231|.

[CAP] Fiori Elements – SideEffects for actions

If you add a custom button in Fiori Elements to trigger an action (like described here), it can be necessary to reload after execution.

When having the following cds model (including a bound and an unbound action)

entity MyEntity as projection on my.Entity
        actions {
            action approval();
        };

action createMyEntity(description: String) returns Entity;

you can either trigger the whole entity to reload

# unbound
annotate myService.createMyEntity with
  @Common.SideEffects: {
    TargetEntities: ['/myService.EntityContainer/MyEntity']
  };

or just some specific fields

# bound
annotate myService.MyEntity.actions {
    approval @(Common.SideEffects: {TargetProperties: [
        'in/status',
        'in/modifiedBy',
        'in/modfiedOn',
    ]})
};

[CAP] CQL Expand two levels deep

I’ve already made a post about expanding compositions or associations (see here), but this time I had to extend two levels deep.

My entity structure looked something to this:

entity Header: cuid, managed {
    Items           : Composition of many Header.Items
                          on Items.purgeRun = $self;
}

entity Header.Items : cuid, managed {
    file            : Association to Files @mandatory @assert.target;
    header: Association to Header;
}

entity Files : cuid, managed {          
    content         : LargeBinary @Core.MediaType: mediaType  @Core.ContentDisposition.Filename: fileName  @Core.ContentDisposition.Type: 'inline';
    mediaType       : String      @Core.IsMediaType: true;
    fileName        : String      @mandatory;
    size            : Integer;
}   

It is possible to query all data in a single HTTP call using $expand twice.

### Header + Items + File
GET {{server}}/odata/v4/service/MainEntity/{{ID}}
?$expand=Items($expand=file)
Authorization: Basic {{username}} {{password}}

However, if you want to fetch the same data using CQL, the following query is required:

        const data = await SELECT(Header, ID)
            .columns(h => {
                h('*')
                h.Items(i => {
                    i('*')
                    i.file(f => f('*'))
                })
            })

This syntax is really hard to remember…

[ABAP] Ermittle Sachkonto zu symbolischen Konto aus FI System

DATA(service_manager) = NEW cl_hrpp_ws_service_manager( ).

SELECT * INTO TABLE @DATA(lt_t52el) FROM t52el  WHERE molga = '01' AND symko <> @space AND endda >= @pn-begps. " Verknüpfung Lohnart zu symbolischen Konto
SELECT * INTO TABLE @DATA(lt_t52ek) FROM t52ek.                                                                " Symbolische Konten
SELECT * INTO TABLE @DATA(lt_t52ep) FROM t52ep.                                                                " Kontierungsarten

 LOOP AT lt_t52el INTO DATA(ls_t52el). " Ermittle je Lohnart die symbolischen Konten (1:n)

      TRY.
          DATA(ls_t52ek) = lt_t52ek[ symko = ls_t52el-symko ].   " Ermittele Kontierungsart
          DATA(ls_t52ep) = lt_t52ep[ koart = ls_t52ek-koart ].   " Ermittele Typ des symbolischen Kontos
          DATA(process)  = CONV ktosl( 'HR' && ls_t52ep-kttyp ).
        CATCH cx_sy_itab_line_not_found.
          CONTINUE.
      ENDTRY.

      service_manager->hrpp_fi_acct_det_hr( EXPORTING companycode        = pernr-bukrs
                                                      process            = process              " Vorgangsschlüssel
                                                      symb_acct          = ls_t52el-symko
                                                      eg_acct_det        = CONV #( '1' )        " Überleitung FI/CO: Mitarbeitergruppierung Kontenfindung: alle Mitarbeiter
                                            IMPORTING gl_account_debit   = DATA(account_debit)
                                                      gl_account_credit  = DATA(account_credit)
                                                      return_tab         = DATA(return_tab) ).
      IF line_exists( return_tab[ type = 'E' ] ).
        CONTINUE.
      ENDIF.

      " Lese Sachkonto Text
      service_manager->hrpp_gl_acc_getdetail( EXPORTING companycode    = pernr-bukrs
                                                        glacct         = account_debit
                                                        language       = sy-langu
                                                        text_only      = abap_true
                                              IMPORTING account_detail = DATA(account_detail) ).

 ENDLOOP.

[SAP] System upgrade – Find related notes

Brief overview of how to find all notes contained in the installed SPs during a system patch.

First, look up the new SP level for a specific component. For example, if the old SP level was 0026 and the patch it is 0028, you need to check the notes for SP 0027 and 0028.

Then open the SAP Software center: https://me.sap.com/softwarecenter (or via https://support.sap.com/en/index.html and click on Software Downloads)

Search for component UIHR002

Click on “Maintenance Software Component”

Click on “SUPPORT PACKAGES”

Choose a package you want to view the notes for and click on “Content Info”

Now it will list all notes which are included

[SAP] Initiale Dinge, die ich für jeden neuen SAP Gui Zugang erledige

Das System in KeePass hinzufügen, für die 1-Klick Anmeldung.

Meine Favoriten importieren. (Man kann übrigens einfach Favoriten über das Kommandofeld hinzufügen via %_GCADDF, technisch liegen die Favoriten in der Tabelle SMEN_BUFFC)

Unter Zusätze → Einstellungen (Umschalt + F9) die Einstellungen öffnen und “Technische Namen anzeigen” anhaken

Unter Optionen das Theme von Belize auf Blue Crystal abändern und eine Systemfarbe einstellen

sowie Schlüssel in Dropdown-Listen anzeigen

In der SE11 über Hilfsmittel → Benutzerdefinierte Einstellungen → Data Browser → Ausgabenliste die ALV-Grid-Darstellung aktivieren

In der SE80 über Hilfsmittel → ABAP Editor → Pretty Printer die folgenden Einstellungen setzen