nocin.eu

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

[ABAP] ALV traffic lights

TYPES: BEGIN OF ty_log,
         status TYPE icon-id,
       END OF ty_log.

DATA gt_log TYPE TABLE OF ty_log.

gt_log = VALUE #( ( status = icon_red_light    )
                  ( status = icon_yellow_light )
                  ( status = icon_green_light  ) ).

TRY.
    cl_salv_table=>factory( IMPORTING r_salv_table = DATA(alv_table)
                            CHANGING  t_table      = gt_log ).

    alv_table->display( ).

  CATCH cx_salv_msg.
ENDTRY.

icon_red, icon_yellow etc. will be automatically loaded from the TYPE-POOL: icon during runtime.

You can check all available icons via the table icon.

SELECT SINGLE id INTO @DATA(icon_red) FROM icon WHERE name = 'ICON_RED_LIGHT'.

[PDF.js] Set filename when downloading pdf

When using a blob or base64 data to open a file in PDF.js, the viewer is not able to get the correct filename. So when downloading the pdf using the download button inside the viewer, it will always download as document.pdf. To manually set a filename, you can use the setTitleUsingUrl function.

<iframe id="pdf-js-viewer" src="/pdf/web/viewer.html?file=" title="webviewer" frameborder="0" width="100%" height="700" allowfullscreen="" webkitallowfullscreen=""/>
let pdfViewerIFrame = document.getElementById("pdf-js-viewer")
pdfViewerIFrame.contentWindow.PDFViewerApplication.setTitleUsingUrl("myFilename")

More info here: https://github.com/mozilla/pdf.js/issues/10435

[ABAP] Download Transport as ZIP

Related Reports:
https://nocin.eu/abap-import-transport-from-zip/
https://nocin.eu/abap-create-toc-for-a-given-transport-release-it-and-download-it-as-zip/

This report can be handy, especially since S/4HANA 2023 seems to have restricted the “classic” import way by using TCode CG3Y and CG3Z. See note 1949906, where it is recommended to create a custom report.

*&---------------------------------------------------------------------*
*& Report ZIP_TRANSPORT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zip_transport.

SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
PARAMETERS p_ttext  TYPE as4text.
SELECTION-SCREEN END OF BLOCK bl01.

SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
SELECTION-SCREEN END OF BLOCK bl02.


START-OF-SELECTION.

  " Check if Transport is released
  DATA ls_request TYPE trwbo_request.
  CALL FUNCTION 'TR_READ_REQUEST'
    EXPORTING
      iv_read_e070     = abap_true
      iv_read_e07t     = abap_true
      iv_trkorr        = p_trkorr
    CHANGING
      cs_request       = ls_request
    EXCEPTIONS
      error_occured    = 1
      no_authorization = 2
      OTHERS           = 3.
  IF ls_request-h-trstatus <> 'R'.
    MESSAGE 'Transport not yet released' TYPE 'E'.
  ENDIF.


  " Read released Transport
  DATA lv_xcontent_k TYPE xstring.
  DATA lv_xcontent_r TYPE xstring.

  DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ p_trkorr+4 }.{ p_trkorr(3) }|.
  DATA(lv_transdir_r) = |{ p_sapdir }data/R{    p_trkorr+4 }.{ p_trkorr(3) }|.


  TRY.

      " K
      OPEN  DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_k INTO lv_xcontent_k.
      CLOSE DATASET lv_transdir_k.

      " R
      OPEN  DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_r INTO lv_xcontent_r.
      CLOSE DATASET lv_transdir_r.

      " Add to ZIP
      DATA(lo_zipper) = NEW cl_abap_zip( ).

      lo_zipper->add( name    = |K{ p_trkorr+4 }.{ p_trkorr(3) }|
                      content = lv_xcontent_k ).

      lo_zipper->add( name    = |R{ p_trkorr+4 }.{ p_trkorr(3) }|
                      content = lv_xcontent_r ).


      " Download ZIP
      DATA(lv_xzip) = lo_zipper->save( ).

      " Convert to raw data
      DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).

      " Set zip filename
      DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ p_trkorr }|
                                                          ELSE |{ p_ttext              }_{ p_trkorr }| ).

      " Replace every character that is not [a-zA-Z0-9_] with '_'.
      REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.

      cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
                                                        filetype = 'BIN'
                                              CHANGING  data_tab = lt_data ).

    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'E'.
  ENDTRY.

  MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.

[CAP] Download MTA deployment logs after deployment

With this simple command, you can download the MTA deployment logs of your recent deployment. Instead of manually having to use cf dmol -i <operation id>, just save the command (replace appId with your Id) as a shell script, make it executable (chmod +x) and run the script after your deployment. It stores the recent logs into the folder logs.

dmol is short for download-mta-op-logs

cf download-mta-op-logs --mta <appId> --last 1 -d logs  

To just have a quick look at the recent logs, use

cf logs <appId>-srv --recent

[CAP] Check if User exists in SuccessFactors

To check if a record exists, you can simply use the HEAD query.

“HEAD is almost identical to GET, but without the response body.” (https://www.w3schools.com/tags/ref_httpmethods.asp)

Unfortunately, there is no shortcut in CAP for a HEAD query, so simply use send with method HEAD.

    sfsfSrv = await cds.connect.to('sfsf')

    try {
        await sfsfSrv.send('HEAD', `/User('${userId}')`)
        return true  // status 200
    } catch (err) {
        return false // status 404
    }

[Home Assistent] AWSH Müllabfuhrtermine einbinden

HACS Integration: https://github.com/mampfes/hacs_waste_collection_schedule

Die Integration bietet eine custom component für die AWSH. Nach Angabe von Ort und Straße bekommt man hier für alle Abfallbehälterarten die entsprechenden Termine zurück. Man muss daher noch die relevanten für sich heraussuchen. Diese kann man unter customize dann angeben und zusätzlich mit einem alias und einem icon versehen.

# Waste Collection Schedule
waste_collection_schedule:
  sources:
    # Integrated Service Provider for AWSH
    - name: awsh_de
      args:
        city: Ammersbek
        street: Frahmredder
      customize:
        # my waste types I ordered
        - type: Restabfall 40L-240L(2-wöchentlich)
          alias: restabfall
          icon: mdi:trash-can          
        - type: Bioabfall(2-wöchentlich)
          alias: bioabfall
          icon: mdi:leaf-circle
        - type: Wertstoff/LVP(2-wöchentlich)
          alias: wertstoff
          icon: mdi:recycle
        - type: Papiertonne(monatlich)
          alias: papier
          icon: mdi:trash-can

Alternativ bekommt man hier den Link zu einer .ics Kalenderdatei. Man kann vorher die für sich relevanten Abfallbehälter auswählen. Die .ics kann ebenfalls direkt eingebunden werden (siehe hier)

# Waste Collection Schedule
waste_collection_schedule:
  sources:
    # ICS
    - name: ics
      args:
        url: https://www.awsh.de/api_v2/collection_dates/1/ort/XXX/strasse/XXX/hausnummern/0/abfallarten/R02-B02-D02-P04/kalender.ics

Nach einem Reboot kann man nun im Home Assistant Kalender seine Abholtermine einsehen.

Im nächsten Schritt definiert man sich noch seine Sensoren je Abfallart.

sensor:
  - platform: waste_collection_schedule
    name: Restabfall
    value_template: 'in {{value.daysTo}} Tag(en)'
    types:
      - restabfall
  - platform: waste_collection_schedule
    name: Bio
    value_template: 'in {{value.daysTo}} Tag(en)'
    types:
      - bioabfall
  - platform: waste_collection_schedule
    name: Wertstoff
    value_template: 'in {{value.daysTo}} Tag(en)'
    types:
      - wertstoff
  - platform: waste_collection_schedule
    name: Papier
    value_template: 'in {{value.daysTo}} Tag(en)'
    types:
      - papier  

Die Sensoren kann man dann zu seinem Dashboard hinzufügen und bei Bedarf auch noch umbenennen. So sieht es bei mir dann aus:

Button-card

Für eine Button-card habe ich mir noch einen weiteren Sensor angelegt. Diesem Sensor einfach alle Abfallarten zuordnen.

  # Used with custom:button-card
  - platform: waste_collection_schedule
    name: wasteButton
    count: 4
    value_template: '{{value.types|join(", ")}}|{{value.daysTo}}|{{value.date.strftime("%d.%m.%Y")}}|{{value.date.strftime("%a")}}'
    types:
      - Restabfall
      - Bioabfall
      - Wertstoff
      - Papier

Die Button-card dann noch befüllen mit:

type: custom:button-card
entity: sensor.wastebutton
layout: icon_name_state2nd
show_label: true
label: |
  [[[
    var days_to = entity.state.split("|")[1]
    if (days_to == 0)
    { return "War heute" }
    else if (days_to == 1)
    { return "Heute Abend raus" }
    else
    { return "in " + days_to + " Tagen" }
  ]]]
show_name: true
name: |
  [[[
    return entity.state.split("|")[0]
  ]]]
state:
  - color: red
    operator: template
    value: '[[[ return entity.state.split("|")[1] == 0 ]]]'
  - color: orange
    operator: template
    value: '[[[ return entity.state.split("|")[1] == 1 ]]]'
  - value: default

Benachrichtigung

Um immer die Anzahl der Tage bis zum nächsten Abholtermin zu kennen, am besten noch einen weiteren Sensor anlegen und diesem wieder alle Abfallarten zuordnen.

  # Sensor for upcoming waste. Used in my reminder automation
  - platform: waste_collection_schedule
    name: upcomingWaste
    value_template: "{{value.daysTo}}"
    types:
      - Restabfall
      - Bioabfall
      - Wertstoff
      - Papier

Dieser kann dann in einer Erinnerungsautomatisierung verwendet werden, welche z.b. eine Notification am Vortag um 19Uhr verschickt.

alias: Abfall Erinnerung
description: ""
trigger:
  - platform: time
    at: "19:00:00"
condition:
  - condition: state
    state: "1"
    entity_id: sensor.upcomingwaste
action:
  - service: notify.family
    data:
      message: >-
        Müll rausbringen: {{ states.sensor.upcomingwaste.attributes.values() |
        first | 
        replace("restabfall","Restmüll") |
        replace("wertstoff","Wertstoff") |  
        replace("bio","Bio") |
        replace("papier","Papier") }}
      data:
        actions:
          - action: YES_TRASHCAN_IS_OUTSIDE
            title: Ja, ist rausgestellt!
        tag: trashcan_done
mode: single

Die Notification beinhaltet eine Bestätigungsmöglichkeit. Hat jemand die Tonne herausgestellt, kann dieser darüber dies einfach kurz bestätigen und mit einer zweiten Automatisierung kann man die Benachrichtigung dann bei anderen verschwinden lassen. Mehr dazu hier.

alias: Abfall Erinnerung - cleared
description: ""
trigger:
  - platform: event
    event_type: mobile_app_notification_action
    event_data:
      action: YES_TRASHCAN_IS_OUTSIDE
condition: []
action:
  - service: notify.family
    data:
      message: clear_notification
      data:
        tag: trashcan_done
mode: single

Karte für das Dashboard

Ein tolles Beispiel für eine schöne Dashboard-Karte findet man hier.

Dafür müssen jedoch noch ein paar weitere Sensoren hinzugefügt werden, wie es hier ebenfalls beschrieben ist.