nocin.eu

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

[Fiori] Travel Expense Workflow not triggered

Recently I had to configure the Travel Fiori Apps on a H4S4 System.

My Travel Requests for Business Traveler (MTR)
My Travel and Expenses for Business Traveler (MTE)
My Inbox – Approve Travel Requests
My Inbox – Approve Travel Expenses

This was pretty easy and after enabling the Apps, I activated the default Workflows WS20000050 (Approve Travel Request) and WS20000040 (Approve Trip) as well. I also checked SWE2 for BU2089 and everything looked good.

But when testing, only the Travel Request Workflow started successfully. When creating a Trip Expense, no Workflow started, instead the Trip Expense directly went to status “Trip Approved” instead of “Trip Completed“. Here you can find an overview of the possible statuses: Trip Status Directory

Initially I thought, this is somehow a Workflow issue or the BUS2089 connection does throw the right event. I also found a note related to this: 2792991.
But it turned out to be a customizing problem and the answer was only one click away from the Trip Status Directory in the chapter Trip Status Assignment. Here the Feature TRVPA is mentioned. And when looking into the system and reading the documentation via PE03 I found the following

Turned out, for Feature TRVPA and Entry WRP the value 4 was set. After changing it to 3, the right trip status got set and the workflow got triggered.

As always, the solution is quite simple once you know it, but it took quite a while to figure it out.

[CAP] Handling Mass Selection >1000 Rows in Fiori Elements

Recently I was working on a Fiori Elements application, where users should be able to select large amount of rows. With the default ResponsiveTable this cannot be achieved, but Fiori Elements with Grid Table allows multi-selection of thousands of rows via Multi-selection plug-in. This is what I’ve added to the manifest.json.

Honestly, not 100% sure if every property was necessary, but I couldn’t find a clear documentation what property is valid for a GridTable…

The result looked good and selecting large amounts of rows was possible in the UI! However, actions like “Go” or table refresh after a custom action generated a massive OData $filter:, because each selected row got appended to the filter like this:​

$filter=ID eq 'uuid1' or ID eq 'uuid2' or ... (4000+ ORs)

When sending such a large request, it exceeds that standard Header Limit: “Request Header Fields Too Large

However, this can easily be solved by increasing the request limit.

Now the request went through and reached the backend. But CAP parses the request to CQN, which becomes a deep SQL WHERE tree. SQLite rejects this with “Expression tree is too large (maximum depth 1000)“.

Haven’t tested on HANA, but I wanted to make sure it also works in my local development environment. So I needed to find a way to reduce the Expression tree size.

CAP Solution: Rewrite OR Chain to IN

Intercept READ requests, parse the CQN SELECT.where array, extract IDs, rebuild as ID IN (...) using cds.parse.xpr.

srv/service.js:

const cds = require('@sap/cds');

this.before('READ', 'YourEntity', req => {
  const whereArray = req.query.SELECT?.where;
  if (!Array.isArray(whereArray) || whereArray.length < 50) return;

  const ids = [];
  let i = 0;
  
  while (i < whereArray.length) {
    // Skip 'or' connectors
    if (typeof whereArray[i] === 'string' && whereArray[i] === 'or') {
      i++; 
      continue;
    }
    
    const refObj = whereArray[i];     // {ref: ['ID']}
    const op = whereArray[i + 1];     // '='
    const valObj = whereArray[i + 2]; // {val: 'uuid'}
    
    if (refObj?.ref && op === '=' && valObj?.val) {
      ids.push(`'${valObj.val}'`);
      i += 3;
    } else {
      console.log('Pattern mismatch at', i);
      return;
    }
  }

  if (ids.length < 20) return; // Skip small filters

  // Build IN clause
  const condition = `ID in (${ids.join(',')})`;
  req.query.SELECT.where = cds.parse.xpr(condition);[web:180][web:187]
  
  console.log(`Rewrote ${whereArray.length/3} OR triplets → IN (${ids.length} IDs)`);
});

CQN Structure

Fiori sends: [{ref:['ID']}, '=', {val:'uuid1'}, 'or', {ref:['ID']}, '=', {val:'uuid2'}, ...]

The handler rebuilds this to ID in ('uuid1','uuid2',...) → compact SQL without deep tree.

This worked, and now the SQLite could process the query! So to summarize what needed to be done:

  1. Grid Table (not Responsive Table) for large datasets.
  2. Multi-selection plug-in (limit: 0 or high value).​
  3. Increase max_batch_header_size in your reverse proxy/Gateway – otherwise $filter gets truncated before reaching CAP.
  4. Custom Handler to transform the where condition

Quite complex – at least with the classic ALV, that wouldn’t have been an issue at all…

Update 27.03.2026: Here is a blog tackling a very similar situation, but with RAP instead of CAP: Fiori List Report: The “Process All” Dilemma — Parsing OData $filter in ABAP
But with the provided solution it is not possible to select/unselect certain lines, since only the filter criteria are respected and no manual selection/deselection of specific rows is possible.

[Workflow] Unbearbeitete Inbox Workitems eines Users ermitteln

Regelmäßig bekomme ich die Anfrage, wie unbearbeitete Inbox Items (offene Dialog Workitems) ermittelt und weitergeleitet werden können. Das kann zum Beispiel erforderlich sein, wenn eine Führungskraft ein Unternehmen verlässt und vorher nicht alle Workitems in der Inbox abgearbeitet hat. Diese sollen daher dann meistens der neuen Führungskraft zugewiesen werden. Es gibt verschiedene Wege diese Workitems zu ermitteln und weiterzuleiten.

Mit dem Fuba SAP_WAPI_CREATE_WORKLIST kann man sich den Inbox-Content zu einem User anzeigen lassen (siehe hier), meist scheitert das aber an den Berechtigungen auf der Produktion. Ist aber ein praktisches Mittel, um sich einen schnellen Überblick über die Inbox eines Users zu machen.

Geht es z.B. speziell nur um Abwesenheitsantrage, könnte man in der PTARQ schauen. Mit dem Report “Belege anzeigen” kann man dann auf die Führungskraft filtern, dazu einfach den Radiobutton “Nächsten Bearbeiter” auswählen und die Personalnummer der Führungskraft eingeben. Hier gibt es einen direkten Absprung in das Workflow-Log und man kann sich die Workitem ID des Dialogschritts über Springen → Technische Workitem-Anzeige holen, um damit in der SWIA weiterleiten zu können.

Ich nutze für diese Aufgabe aber vorwiegend die SWI5. Typ US auswählen + die User ID und weiter unten auf “Zu erledigende Workitems” stellen. Optional noch auf einen speziellen Aufgabentyp filtern.

Als Ergebnis erhält man eine Liste mit Aufgaben IDs und zugehörigen Workitem IDs. Und da es direkt die Workitem ID des Dialogschritts ist, kann man damit direkt in der SWIA selektieren und das Workitem weiterleiten.

[Home Assistant] YAML Anker und Aliase

In YAML werden Anker (&) und Aliase (*) verwendet, um Redundanzen zu vermeiden, indem Konfigurationsblöcke einmal definiert und an anderer Stelle wiederverwendet werden. Super praktisch!

https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors

definitions: 
  steps:
    - step: &build-test
        name: Build and test
        script:
          - mvn package
        artifacts:
          - target/**

pipelines:
  branches:
    develop:
      - step: *build-test
    main:
      - step: *build-test

Damit kann man die ein oder andere Home Assistant Konfiguration etwas verkürzen.

[ABAP] ALV – Enable Excel Export

The “Excel export” functionality can easily be enabled using get_functions( )->set_all( abap_true )

SELECT * FROM sflight INTO TABLE @DATA(flights) UP TO 100 ROWS.

TRY.
    cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_salv)
                            CHANGING  t_table      = flights ).

    lo_salv->get_functions( )->set_all( abap_true ).
    lo_salv->get_columns( )->set_optimize( abap_true ).
    lo_salv->get_display_settings( )->set_list_header( 'Flights' ).
    lo_salv->get_display_settings( )->set_striped_pattern( abap_true ).
    lo_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).

    lo_salv->display( ).

  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

[Fiori] MyInbox – Custom App reloads infinitely

I recently had the situation, that after a standalone Gateway System got patched, a custom app in the My Inbox app did not load anymore, when clicking on a workitem. When opening the network tab in the dev tools, you could see three requests send over and over again. It called the manifest.json, a batch request to the OData Service and some i18n.properties calls. You could also see the app files in the sources tab, which means that app had already loaded correctly but could not be displayed because it kept restarting for some reason.

When searching via Perplexity, I found the following post in the SCN: https://community.sap.com/t5/technology-q-a/sapui5-custom-app-navigation-from-myinbox-calls-app-in-infinite-loop/qaq-p/690650
But moving the “parent” property did not help, although the described issue sound similar.

Since everything worked before the system patch and the custom app wasn’t changed, it must have been related to an updated My Inbox app or to the new UI5 version, which affected the custom app.

When scrolling through my bookmarks, I found this note: 2305401 – Integration of SAPUI5 Fiori Applications into My Inbox 2.0
When checking the attached PDF, I found the following in chapter 4:

This “repeatedly loading” was exactly my problem! And since the Gateway got patched, I guess a new My Inbox version came with it. When checking the manifest.json in the custom app, there was no async property at all.
After adding "async": true to the mainfest.json, the custom app started to load successful again. Nice!

[HR] Personalisierungsdaten pflegen

Paket: FPB_PERSONALIZATION
Fuba: FPB_PERS_POST_FOR_DIALOG
Tabelle: FPB_PERSPARM

https://launchpad.support.sap.com/#/notes/0001622954

Beispiel: Reisebeauftragter (RBA) für das Travelmanagement via Dialog CO-CCA-TR

Anlegen:
Tcode FPB_MAINTAIN_PERS_S
Dialog DIA_CO-CCA-TR

Löschen:
Tcode FPB_DELETE_PERS_DATA
Dialog CO-CCA-TR

Anzeigen:
Tcode: FPB_SHOW_PERS_DATA

Siehe weitere Transaktionen im Paket.

[Firefox] Handy shortcuts I’ve learned recently

Over the past few months, I’ve picked up a few handy Firefox tricks.

Address Bar

  • Find open tabs: Press Ctrl + L (or Ctrl + T) to focus the address bar, then type @tabs or %, hit space, and start typing the tab’s name.
  • Search browsing history: Press Ctrl + L, type @history, hit space followed by your search term.
  • Duplicate the current tab: Focus the address bar with Ctrl + L, then press Ctrl + Shift + Enter. (It opens the same page in a new tab, but without keeping the original tab’s history.)

Tab Management

  • Quickly duplicate a tab: Hold Ctrl and drag a tab to another spot on the tab bar, or just middle‑click the refresh button.
  • Select multiple tabs: Use Ctrl + click to select specific tabs, or Shift + click to select a whole range.
  • Move selected tabs to a new window: After selecting tabs, simply drag them out of the tab bar.

[SAPUI5] Create app with full screen width / full screen layout

https://experience.sap.com/fiori-design-web/full-screen/

https://stackoverflow.com/questions/55832369/how-to-disable-or-enable-letterboxing-and-adjust-ui5-for-the-widescreen/56137602

Simply add appWidthLimited:false in your index.html

		<script>
			sap.ui.getCore().attachInit(function() {
					new sap.m.Shell({
						appWidthLimited:false,
						app: new sap.ui.core.ComponentContainer({
							height : "100%",
							name : "my.demo.app"
						})
					}).placeAt("content");
				);
			});
		</script>

[ABAP] Debugging as other user

Imagine the following situation:

USER_A: Allowed to run a specific report, but not allowed to debug
USER_B: Is allowed to debug

In such a case, you can do the following:

  • USER_B: Sets external breakpoint for USER_A
  • USER_A: Enters the following in the command field: /hext user=USER_B
  • USER_A: Starts his report
  • USER_B: Debugger will be opened

If you check SY-UNAME in the debugger, it will have the value USER_A