Prérequis techniques : environnement S/4 Hana On premise ou S/4 Hana – Private Cloud edition.

Dans ce blog, l’objectif est de créer une application Fiori à l’aide du framework RAP, sur la base d’une BAPI existante, tout en respectant les directives SAP pour atteindre l’objectif « Clean Core ».

Cas d’utilisation : Pour cette présentation, nous allons chercher à savoir si les livraisons ont des unités de manutention existantes. Si c’est le cas, nous devons pouvoir supprimer ces unités de manutention.

En ABAP Standard, nous pouvons utiliser la BAPI : BAPI_HU_DELETE_FROM_DEL.

Nous allons maintenant voir comment intégrer cela dans un objet RAP, en suivant les recommandations de SAP pour le Clean Core.

Je reviendrai également sur l’utilisation du cadre RAP avec l’utilisation d’ABAP Standard, sans objectif de clean core ou de cloud compliance, pour vous montrer quelques différences.

ABAP RAP, dans le contexte d’ABAP CLOUD

Jetons un bref coup d’œil au cadre ABAP RAP et à sa structure pour fournir les principales bases de ce blog, le tout dans le contexte du développement ABAP Cloud.

Je vous recommande toutefois de lire le livre SAPPRESS « ABAP RESTful Application Programming Model – A comprehensive guide » (1), qui explique parfaitement tous les détails de ce framework.

Avant de plonger dans la structure du framework, revenons sur la notion de LUW dans le cloud, car elle aura un impact majeur sur notre développement.

Comme évoqué en détail dans ce Blog SAP (2), s’il était auparavant recommandé d’utiliser IN UPDATE TASK lorsqu’on souhaitait écrire dans la base de données SAP en utilisant le standard ABAP, ce n’est plus le cas avec ABAP ( IN UPDATE TASK permettait entre autres d’éviter d’écrire dans les bases de données lorsqu’il y avait des commits implicites et d’augmenter les performances, mais je vous invite à lire l’article cité ci-dessus) . Ce n’est plus le cas avec ABAP cloud, puisque les performances de la base de données HANA permettent de s’en passer. Cependant, cela implique que les écritures dans la base de données ne se fassent qu’à la fin du SAP LUW (lors de la séquence de sauvegarde, voir plus loin dans l’explication du framework RAP), car en cas de commit implicite, des modifications de la base de données seraient effectuées et nous ne respecterions plus les règles ACID (encore une fois, voir l’article cité plus haut).

C’est là qu’intervient la structure du cadre RAP :

  • Phase d’interaction
  • Phase de sauvegarde

Pendant la phase d’interaction, les données sont placées dans une mémoire tampon, où elles peuvent être enrichies et vérifiées.

Lors de la séquence de sauvegarde, les données de la mémoire tampon ne peuvent plus être modifiées et sont sauvegardées dans la base de données (c’est la fin du SAP LUW).

Telles sont les règles suivies par le cadre RAP dans le cas du développement ABAP cloud :

Les violations suivantes entraîneront toujours une erreur d’exécution dans un contexte RAP (même si ABAP CLOUD est utilisé ou STANDARD ABAP) :

  • Utilisation explicite des instructions COMMIT WORK ou ROLLBACK WORK, car la gestion des transactions et le commit de la base de données sont exclusivement gérés par RAP.
  • Appel de la tâche de mise à jour en arrière-plan (bgPF) dans la phase d’interaction ou la phase de sauvegarde anticipée.

Certaines autres opérations n’entraînent des erreurs d’exécution que si la définition du comportement RAP est mise en œuvre dans ABAP for Cloud Development et que le mode strict est appliqué :

  • L’invocation des contrôles d’autorisation dans la phase de sauvegarde anticipée ou tardive.
  • Modification des opérations de base de données dans la phase d’interaction ou la phase de sauvegarde anticipée.
  • Lancement d’événements commerciaux au cours de la phase d’interaction ou de la phase de sauvegarde anticipée.
  • les validations (implicites ou explicites) de la base de données pour la connexion principale à la base de données dans la phase de sauvegarde tardive.

NOTE POUR L’UTILISATION DE ABAP STANDARD : En regardant ces règles, si vous n’êtes pas dans l’optique du clean core, vous pouvez développer en STANDARD ABAP, et effectuer un commit implicite pendant la phase tardive par exemple, ou utiliser l’instruction IN UPDATE TASK, mais dans mon cas je préfère suivre les règles plus strictes de ABAP RAP FRAMEWORK même si je développe en STANDARD ABAP.

Les 2 scénarios possibles en utilisant le framework RAP

Il existe deux types de scénarios pour la mise en œuvre du processus : managed ou unmanaged.

Dans le scénario managed, le système SAP génère automatiquement les méthodes et l’objet de gestion. Dans le scénario unmanaged, tout doit être développé à la main.

  • Le cas d’utilisation du scénario managed est le suivant : Création d’une nouvelle application à partir de zéro (approche greenfield) – typiquement, les données seront stockées de manière persistante dans de nouvelles tables Z. Il faut ensuite améliorer la norme générée par le biais de validations, de déterminations et d’actions.

Ce n’est pas le cas ici, car nous voulons stocker/modifier nos données dans des tables standard à l’aide d’objets hérités (une BAPI).

  • Le cas d’utilisation du scénario unmanaged est le suivant : Refonte d’une application existante (approche brownfield). L’application existante fournit une API qui peut et doit être utilisée pour l’implémentation de la phase d’interaction et de la séquence de sauvegarde en raison de ses caractéristiques (structure, découplage de la logique métier et des aspects techniques).

Dans ce cas, nous devrions tout développer, de la phase d’interaction à la séquence de sauvegarde. Nous ne sommes pas intéressés par cette implémentation, car nous ne disposons pas d’une API pour gérer la mise en mémoire tampon pour nos besoins, et nous aimerions utiliser le standard SAP pour la partie interaction.

  • Dans ce cas, il existe un troisième scénario : Scénario managed with unmanaged save : Utilisation de parties d’une application existante (par exemple, une BAPI) dans la séquence de sauvegarde UNIQUEMENT.

NOTE : il existe également un scénario managed with additional save : l’ajout d’une logique de mise à jour supplémentaire à la séquence de sauvegarde (par exemple, dans le journal de l’application) qui n’affecte pas l’objet commercial actuel. Mais nous ne l’utiliserons pas.

Comment utiliser la BAPI dans le contexte du ABAP Cloud ?

SAP fournit les objets qu’il souhaite exposer pour le développement dans le cloud en les whitlistant. Si ces objets ne sont pas exposés, SAP peut également proposer leurs successeurs (par exemple, dans le cas des BAPI, si elles ne sont pas exposées, un comportement RAP peut être proposé à la place).

Ceci peut être vérifié soit directement dans l’ADT (3), soit sur GitHub (4).

Dans notre cas, la BAPI souhaitée n’est ni exposée ni remplacée.

Conformément aux directives SAP (5) (6), nous allons créer un wrapper autour de cette BAPI, et whitlister ce wrapper. Lorsque SAP proposera un successeur à cette BAPI, il sera possible de supprimer le wrapper et d’utiliser l’objet successeur à la place.

Pour créer un wrapper autour d’une BAPI, suivez ce tutoriel (7).

Ici, nous avons créé la classe ZCL_WRAP_DEL_HU_FAC (et ZCL_WRAP_DEL_HU en utilisant ZIF_WRAP_BAP_HU_DEL), qui sera utilisée pour supprimer les unités de manutention.

INTERFACE zif_wrap_bapi_hu_del

  PUBLIC .

  "Delivery Number

  TYPES de_number             TYPE BAPIDELICIOUSDELIVERY-DELIVERY_NUMBER.

  "HU

  TYPES de_hu                 TYPE BAPIHUKEY-HU_EXID.

  "Return table

  TYPES de_returns      TYPE bapirettab.



METHODS delete

IMPORTING iv_number type de_number

          iv_hu type de_hu

RETURNING VALUE(rt_results) TYPE de_returns.



ENDINTERFACE.
CLASS zcl_wrap_del_hu DEFINITION

  PUBLIC

  FINAL

  CREATE PUBLIC .



  PUBLIC SECTION.

    INTERFACES zif_wrap_bapi_hu_del.

      PROTECTED SECTION.

  PRIVATE SECTION.

ENDCLASS.







CLASS zcl_wrap_del_hu IMPLEMENTATION.



 METHOD zif_wrap_bapi_hu_del~delete.

    CALL FUNCTION 'BAPI_HU_DELETE_FROM_DEL'

    EXPORTING

    delivery = iv_number

    hukey = iv_hu

    TABLES

    return = rt_results.

    .

 ENDMETHOD.



ENDCLASS.
CLASS zcl_wrap_del_hu_fac DEFINITION

  PUBLIC

  FINAL

  CREATE PRIVATE .



  PUBLIC SECTION.

      CLASS-METHODS create_instance

      RETURNING VALUE(result) TYPE REF TO zif_wrap_bapi_hu_del.

  PROTECTED SECTION.

  PRIVATE SECTION.

    METHODS constructor.



ENDCLASS.







CLASS zcl_wrap_del_hu_fac IMPLEMENTATION.



  METHOD create_instance.



    result = NEW zcl_wrap_del_hu(  ).

  ENDMETHOD.



  METHOD constructor.

  ENDMETHOD.



ENDCLASS.

Nous avons publié le wrapper et son interface en tant qu’API whitlisté.

Il peut donc être utilisé dès à présent dans ABAP pour le développement du cloud.

Définition et implémentation de l’objet RAP

Maintenant que nous avons whitlisté notre objet à utiliser dans notre objet rap, nous allons commencer à créer notre objet métier.

Nous avons créé un package qui utilise « ABAP for cloud development ».

1) Création des CDS

  • Nous créons une vue racine CDS qui stocke le numéro de livraison et indique si au moins une unité de manutention est liée à cette livraison. (Nous avons créé la vue CDS privée ZP_HU pour trouver la première ligne de chaque unité de manutention pour une livraison, puis nous joignons cette vue à l’entité racine CDS pour vérifier si au moins une unité de manutention existe).

Notez ici que nous utilisons des CDS publiées par SAP, qui remplace les tables standards de type vekp).

@AbapCatalog.viewEnhancementCategory: [#NONE]

@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: 'CDS internal use HU'

@Metadata.ignorePropagatedAnnotations: true

@ObjectModel.usageType:{

    serviceQuality: #X,

    sizeCategory: #S,

    dataClass: #MIXED

}

define view entity ZP_HU 

as select from I_DeliveryDocument 

left outer join I_HandlingUnitHeader on I_HandlingUnitHeader.HandlingUnitPackingObjectKey = I_DeliveryDocument.DeliveryDocument 

{

    key I_DeliveryDocument.DeliveryDocument as deliveryDocument,

    min(I_HandlingUnitHeader.HandlingUnitInternalID) as hu,

    min(I_HandlingUnitHeader.HandlingUnitPackingObjectKey) as objectkey

}

group by

  I_DeliveryDocument.DeliveryDocument

having

  count(*) = 1
@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: 'Handling Units for Delivery'

define root view entity ZI_DELIV_HU as select from I_DeliveryDocument

left outer join ZP_HU on ZP_HU.deliveryDocument = I_DeliveryDocument.DeliveryDocument

{

    key I_DeliveryDocument.DeliveryDocument as DeliveryDocument,

    case

        when $projection.DeliveryDocument = ZP_HU.objectkey

          then cast ( 'X' as boole_d ) 

        else cast ( ' ' as boole_d ) 

        end                               as HUAssigned

}
  • Nous créons la vue de projection associée, qui sera utilisée pour créer le service web. La vue de projection réduit l’objet métier à l’usage simple qui en sera fait dans l’application Fiori, alors que le cds ZI_DELIV_HU pourrait être réutilisé par d’autres applications.

(Ici, il n’y a pas de différences entre les 2 cds, mais il aurait pu y en avoir).

@EndUserText.label: 'Projection view ZI_DELIV_HU'

@AccessControl.authorizationCheck: #NOT_REQUIRED

@Metadata.allowExtensions: true

define root view entity ZC_DELIV_HU as projection on ZI_DELIV_HU

{

    key DeliveryDocument,

    HUAssigned

}

NOTE : nous aurions pu créer des cd pour une valeur ajoutée supplémentaire.

2) Nous créons la définition du comportement

  • Nous spécifions d’abord le mode strict pour toutes les vérifications syntaxiques nécessaires, et la méthode d’autorisation utilisée (vérification de l’autorisation).
  • Ensuite, nous spécifions les opérations qui peuvent être utilisées : dans ce cas, nous souhaitons seulement mettre à jour notre objet de gestion pour supprimer le drapeau d’affectation HU, donc nous spécifions « update ». (nous reviendrons sur l’interne).
  • Ensuite, nous enrichissons la norme générée automatiquement en ajoutant une validation dans laquelle nous utilisons la BAPI en mode test pour vérifier que tout se passe bien.
  • Nous enrichissons également la norme d’une action « unassign » qui supprimera le drapeau HUs attribué au CDS. Cette action utilisera l’opération UPDATE en interne pour modifier l’entité, et l’utilisateur ne devrait pas être en mesure de modifier lui-même l’indicateur : il doit utiliser le bouton « unassign » (l’explication de la raison pour laquelle interne est spécifiée dans la définition : UPDATE ne pourrait être utilisé qu’en interne, dans l’implémentation de l’objet d’entreprise RAP).
  • enfin, nous spécifions Unmanaged save pour utiliser la BAPI dans la séquence de sauvegarde et sauvegarder les données dans la base de données.
managed with unmanaged save implementation in class zbp_i_deliv_hu unique;

strict ( 2 );



define behavior for ZI_DELIV_HU //alias <alias_name>

//persistent table <???>

lock master

authorization master ( instance )

//etag master <field_name>

{

  //  create;

  internal update;

  //  delete;

  action unassign;

  validation validateDelivery on save { create; update; }

}

3) Créer l’implémentation

  • Création de l’action

L’EML est utilisé pour mettre à jour l’objet en supprimant l’indicateur de validité. Si l’utilisateur tente de supprimer le témoin d’un objet déjà dépourvu de témoin (sans UM), un message d’erreur s’affiche.

  • Création de la validation (au moment de la sauvegarde)

Exécutez l’interface BAPI en mode test pour vérifier que tout va bien. Si ce n’est pas le cas, un message d’erreur s’affiche.

  • Création d’une séquence de sauvegarde :

La BAPI est appelée dans la dernière phase, à la fin du SAP LUW. Une fois la séquence de sauvegarde terminée, le cadre RAP se charge de la valider.

CLASS lhc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_handler.

  PRIVATE SECTION.



    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION

      IMPORTING keys REQUEST requested_authorizations FOR zi_deliv_hu RESULT result.



    METHODS unassign FOR MODIFY

      IMPORTING keys FOR ACTION zi_deliv_hu~unassign.



    METHODS validateDelivery FOR VALIDATE ON SAVE

      IMPORTING keys FOR zi_deliv_hu~validateDelivery.



ENDCLASS.



CLASS lhc_ZI_DELIV_HU IMPLEMENTATION.



  METHOD get_instance_authorizations.

* Here you check authorizations

  ENDMETHOD.



  METHOD unassign.

* We read the current value of the key instances

    READ ENTITIES OF zi_deliv_hu IN LOCAL MODE

      ENTITY zi_deliv_hu

      ALL FIELDS

      WITH CORRESPONDING #( keys )

      RESULT DATA(lt_sh_for_unassign).

* We loop at the finding values to check if instances have HUs qssigned or no, If yes we report an error

    LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign_err>) WHERE huassigned = ' '.

      reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu

                                                            ( %tky = <ls_sh_for_unassign_err>-%tky

                                                              %msg = me->new_message( severity = if_abap_behv_message=>severity-error

                                                                                      id       = 'ZHU_MSG'

                                                                                      number   = '002'

                                                                                      v2 = |{ <ls_sh_for_unassign_err>-deliverydocument ALPHA = OUT }| ) ) ).



    ENDLOOP.

    DELETE lt_sh_for_unassign WHERE huassigned = ' '.

* We modify entities to delete the flag and un-assign the HUs linked to the delivery

    LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign>).

      MODIFY ENTITIES OF zi_deliv_hu IN LOCAL MODE

          ENTITY zi_deliv_hu

          UPDATE FROM

              VALUE #( FOR sh IN lt_sh_for_unassign (

                %key = sh-%key

                deliverydocument = sh-deliverydocument

                huassigned = ' '

                %control-huassigned = if_abap_behv=>mk-on ) ).

    ENDLOOP.



  ENDMETHOD.



  METHOD validateDelivery.

* Validate here the data

* For example, if the BAPI have a test mode it is be possible to execute it in test mode and in case of error report errors

* However here it's not the case for BAPI_HU_DELETE_FROM_DEL

* It's still possible to add our own validation

  ENDMETHOD.



ENDCLASS.



CLASS lsc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_saver.

  PROTECTED SECTION.



    METHODS save_modified REDEFINITION.



    METHODS cleanup_finalize REDEFINITION.



ENDCLASS.



CLASS lsc_ZI_DELIV_HU IMPLEMENTATION.



  METHOD save_modified.

* We loop at the updated instances with deleted flag

    LOOP AT update-zi_deliv_hu INTO DATA(ls_update).



* In this loop we find all the HU linked to the delivery

      SELECT HandlingUnitExternalID

      FROM I_HandlingUnitHeader

      WHERE HandlingUnitPackingObjectKey = @ls_update-DeliveryDocument

      INTO TABLE @DATA(lt_hu_to_delete).



* Loop to all HUs assigned to delivery

      LOOP AT lt_hu_to_delete INTO DATA(ls_hu_to_delete).

* Call the bapi to delete the HU using the wrapper for the cloud

        DATA(lt_hu_delete) = zcl_wrap_del_hu_fac=>create_instance( )->delete(

          EXPORTING

          iv_number = ls_update-DeliveryDocument

          iv_hu = ls_hu_to_delete-HandlingUnitExternalID ).



* At this stage we can't report errors, it should has been verify that everything is fine before the save sequence

* But in case of error we still give an information message

        reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu

        FOR r IN lt_hu_delete WHERE ( type = 'E' )

        ( deliverydocument = ls_update-deliverydocument

        %msg = me->new_message(

        id = r-id

        number = r-number

        severity = if_abap_behv_message=>severity-information

        v1 = r-message_v1

        v2 = r-message_v2

        v3 = r-message_v3

        v4 = r-message_v4 ) ) ).



        IF reported IS INITIAL.

          reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu

                                                                ( %key = ls_update-%key

                                                                  %msg = me->new_message( severity = if_abap_behv_message=>severity-success

                                                                                          id       = 'ZHU_MSG'

                                                                                          number   = '002'

                                                                                          v1 = |{ ls_update-deliverydocument ALPHA = OUT }| ) ) ).

        ENDIF.

      ENDLOOP.

    ENDLOOP.

  ENDMETHOD.



  METHOD cleanup_finalize.

* Clean after save

  ENDMETHOD.



ENDCLASS.

4) Nous créons une définition de consommation de comportement pour réduire les opérations CRUD à l’application si nécessaire (ici je crée la consommation mais elle est identique à ZI_DELIV_HU ).

projection;

strict ( 2 );



define behavior for ZC_DELIV_HU //alias <alias_name>

{



  use action unassign;

}

5) Nous créons les metadata, en ajoutant le bouton supplémentaire pour les unités de manutention non attribuées.

@Metadata.layer: #CORE

annotate entity ZC_DELIV_HU

    with 

{

  @UI: { lineItem: [{ position: 10, importance: #HIGH },

                    { type: #FOR_ACTION, dataAction: 'unassign', label: 'Unassign' }],

         selectionField: [{ position: 10 }] }

  DeliveryDocument;

  @UI.lineItem: [{ position: 20 },

                 { label : 'HU(s) assigned ?' }]

  @UI.selectionField: [{ position: 20 }]               

  @UI.identification:[ { label: 'HU(s) assigned ?' } ]

  @EndUserText.label: 'HU(s) assigned ?'

  HUAssigned;

}

6) Créer le service de définition

@EndUserText.label: 'Service def deliv hu'

define service ZUI_DELIV_HU {

  expose ZC_DELIV_HU;

}

7) créer le service binding

8) Il ne reste plus qu’à créer l’application Fiori avec VS Code ou SAP BAS (8) en utilisant les éléments SAP Fiori.

Conclusion

Voici comment je crée des applications Fiori à l’aide du framework Rap, en tenant compte des directives SAP pour le cloud.

(1) Learn the ABAP RESTful Application Programming Model | – by SAP PRESS (sap-press.com)

(2) https://blogs.sap.com/2022/12/05/the-sap-luw-in-abap-cloud/

(3) https://blogs.sap.com/2023/08/15/smooth-transition-to-abap-for-cloud-developmentcheat-sheet/comment-…

(4) https://raw.githubusercontent.com/SAP/abap-atc-cr-cv-s4hc/main/src/objectReleaseInfo_PCE2022.json

(5) https://www.sap.com/documents/2023/05/b0bd8ae6-747e-0010-bca6-c68f7e60039b.html

(6) https://blogs.sap.com/2022/10/25/how-to-use-embedded-steampunk-in-sap-s-4hana-cloud-private-edition-…

(7) https://developers.sap.com/tutorials/abap-s4hanacloud-purchasereq-create-wrapper.html

(8) https://blogs.sap.com/2022/10/31/fiori-development-with-vscode-and-nodejs/