Note : Une nouvelle version pour créer des application jobs est disponible et sera abordée dans un nouvel article. Cet article utilise d’anciennes interfaces.

Pour rappel, en ABAP Cloud (la seule version d’ABAP disponible pour S/4 HANA Cloud, Public Edition et celle recommandée pour les autres versions S/4 HANA Private Edition et On Premise), les écrans GUI ne sont plus disponibles, et toute UI doit être Fiori.

Qu’en est-il alors des REPORTs que nous avions l’habitude de programmer tous les jours ? toutes les heures ?

Une solution existe : Application Jobs disponible sur S/4 HANA cloud, Public/ Private Edition, On-Premise et BTP, Abap Environment. Comme indiquée dans la documentation, c’est une application Fiori standard pour planifier et surveiller des jobs.

Un ensemble d’applications standards est déjà disponible afin d’être planifié en arrière plan, mais il est également possible de créer ses propres jobs, c’est ce que nous allons décomposer ici.

Remplacer le REPORT

Si vous aviez pour habitude de programmer des Reports sur vos systèmes SAP, ce n’est plus possible avec la version ABAP Cloud.

En remplacement, nous créerons :

  • Une classe qui contiendra toute la logique que nous souhaitons mettre en place lors de ce job (cette classe doit implémenter les interfaces IF_APJ_DT_EXEC_OBJECT et IF_APJ_RT_EXEC_OBJECT)
  • Une fois cette classe créée nous créerons un « Job Catalog Entry », qui comme précisé dans la documentation : contient les informations nécessaires à la planification et à l’exécution d’une « application job ». Cela contient le nom de la classe créée précédemment et les informations sur la manière dont les champs de sélection sont rendus disponibles dans l’application.
  • Enfin, pour que le job soit disponible dans l’application Fiori Application Jobs, il faut créer un « Job Template » qui se réfère au « Job Catalog Entry » et contient des valeurs pour certains ou tous les champs de sélection. Dans la tuile Fiori Application Jobs, les champs de sélection apparaissent et sont pré-remplis avec les valeurs définies dans le template. Elles peuvent être remplacées par l’utilisateur.
  • Il est enfin possible de programmer son job dans la tuile Application Jobs OU d’utiliser l’API CL_APJ_RT_API pour effectuer cette programmation.
  • De manière générale, un business user utilisera toujours la tuile Application Jobs pour programmer des jobs alors qu’un développeur pourra passer par un bout de code pour programmer son job.
  • Il est également possible de créer des jobs lors de l’exécution de différents processus (comme ça pouvait être fait à l’époque avec les modules fonction JOB_OPEN, JOB_CLOSE, etc… mais désormais en passant par l’API CL_APJ_RT_API).

Créer la logique

Comme indiqué précédemment la première chose à faire est de créer une classe qui implémente les interfaces IF_APJ_DT_EXEC_OBJECT et IF_APJ_RT_EXEC_OBJECT

CLASS zjob_test DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_apj_dt_exec_object.
    INTERFACES if_apj_rt_exec_object.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.


CLASS zjob_test IMPLEMENTATION.

  METHOD if_apj_dt_exec_object~get_parameters.

    " Return the supported selection parameters here
    et_parameter_def = VALUE #(
      ( selname = 'S_ID'    kind = if_apj_dt_exec_object=>select_option datatype = 'C' length = 10 param_text = 'My ID'                                      changeable_ind = abap_true )
      ( selname = 'P_DESCR' kind = if_apj_dt_exec_object=>parameter     datatype = 'C' length = 80 param_text = 'My Description'   lowercase_ind = abap_true changeable_ind = abap_true )
      ( selname = 'P_COUNT' kind = if_apj_dt_exec_object=>parameter     datatype = 'I' length = 10 param_text = 'My Count'                                   changeable_ind = abap_true )
      ( selname = 'P_SIMUL' kind = if_apj_dt_exec_object=>parameter     datatype = 'C' length =  1 param_text = 'My Simulate Only' checkbox_ind = abap_true  changeable_ind = abap_true )
    ).

    " Return the default parameters values here
    et_parameter_val = VALUE #(
      ( selname = 'S_ID'    kind = if_apj_dt_exec_object=>select_option sign = 'I' option = 'EQ' low = '4711' )
      ( selname = 'P_DESCR' kind = if_apj_dt_exec_object=>parameter     sign = 'I' option = 'EQ' low = 'My Default Description' )
      ( selname = 'P_COUNT' kind = if_apj_dt_exec_object=>parameter     sign = 'I' option = 'EQ' low = '200' )
      ( selname = 'P_SIMUL' kind = if_apj_dt_exec_object=>parameter     sign = 'I' option = 'EQ' low = abap_true )
    ).

  ENDMETHOD.

  METHOD if_apj_rt_exec_object~execute.

    TYPES ty_id TYPE c LENGTH 10.

    DATA s_id    TYPE RANGE OF ty_id.
    DATA p_descr TYPE c LENGTH 80.
    DATA p_count TYPE i.
    DATA p_simul TYPE abap_boolean.

    DATA: jobname   type cl_apj_rt_api=>TY_JOBNAME.
    DATA: jobcount  type cl_apj_rt_api=>TY_JOBCOUNT.
    DATA: catalog   type cl_apj_rt_api=>TY_CATALOG_NAME.
    DATA: template  type cl_apj_rt_api=>TY_TEMPLATE_NAME.

    " Getting the actual parameter values
    LOOP AT it_parameters INTO DATA(ls_parameter).
      CASE ls_parameter-selname.
        WHEN 'S_ID'.
          APPEND VALUE #( sign   = ls_parameter-sign
                          option = ls_parameter-option
                          low    = ls_parameter-low
                          high   = ls_parameter-high ) TO s_id.
        WHEN 'P_DESCR'. p_descr = ls_parameter-low.
        WHEN 'P_COUNT'. p_count = ls_parameter-low.
        WHEN 'P_SIMUL'. p_simul = ls_parameter-low.
      ENDCASE.
    ENDLOOP.

    try.
* read own runtime info catalog
       cl_apj_rt_api=>GET_JOB_RUNTIME_INFO(
                        importing
                          ev_jobname        = jobname
                          ev_jobcount       = jobcount
                          ev_catalog_name   = catalog
                          ev_template_name  = template ).

       catch cx_apj_rt.

    endtry.


    " Implement the job execution

  ENDMETHOD.

ENDCLASS.

Dans la méthode if_apj_dt_exec_object~get_parameters, on indique les paramètres disponibles pour se Job et leur valeur par défaut s’il y en a.

Dans la méthode if_apj_rt_exec_object~execute, c’est toute la logique qui s’exécute. Ici ça ne fait rien de spéciale sauf retrourner les informations du JOB en question, mais il aurait été possible de mettre toute la logique souhaitée.

Créer le Job Catalog Entry

Créer le Job Catalog Entry et sélectionner la classe créée précédemment qui sera exécutée

Créer le Job Template

Vous y retrouverez les paramètres et les valeurs par défaut configurables :

Planification du Job

  • Via API

Créer une classe pouvant être executée directement en implementant l’interface if_oo_adt_classrun.

CLASS zcl_job_api_tst DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_job_api_tst IMPLEMENTATION.

  " <SIGNATURE>---------------------------------------------------------------------------------------+
  " | Instance Public Method Z_CL_RT_API_DEMO->IF_OO_ADT_CLASSRUN~MAIN
  " +-------------------------------------------------------------------------------------------------+
  " | [--->] OUT                            TYPE REF TO IF_OO_ADT_CLASSRUN_OUT
  " +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD if_oo_adt_classrun~main.

    DATA lv_job_text TYPE cl_apj_rt_api=>ty_job_text VALUE 'Demo_Job'.

    DATA lv_template_name TYPE cl_apj_rt_api=>ty_template_name.

    DATA ls_start_info TYPE cl_apj_rt_api=>ty_start_info.
    DATA ls_scheduling_info TYPE cl_apj_rt_api=>ty_scheduling_info.
    DATA ls_end_info TYPE cl_apj_rt_api=>ty_end_info.

    DATA lt_job_parameters TYPE cl_apj_rt_api=>tt_job_parameter_value.
    DATA ls_job_parameters TYPE cl_apj_rt_api=>ty_job_parameter_value.
    DATA ls_value TYPE cl_apj_rt_api=>ty_value_range.

    DATA lv_jobname TYPE cl_apj_rt_api=>ty_jobname.
    DATA lv_jobcount TYPE cl_apj_rt_api=>ty_jobcount.

    DATA lv_status TYPE cl_apj_rt_api=>ty_job_status.
    DATA lv_statustext TYPE cl_apj_rt_api=>ty_job_status_text.

    DATA lv_txt TYPE string.
    DATA ls_ret TYPE bapiret2.

    " Choose the name of the existing job template.
    lv_template_name = 'ZJOB_TEMPLATE_TEST'.

    " The immediate start can't be used when being called from within a RAP business object
    " because the underlying API performs an implicit COMMIT WORK.
    "   ls_start_info-start_immediately = 'X'.

    " Start the job using a timestamp instead. This will start the job immediately but can have a delay depending on the current workload.
    GET TIME STAMP FIELD DATA(ls_ts1).
    " Add 1 hour
    DATA(ls_ts2) = cl_abap_tstmp=>add( tstmp = ls_ts1
                                       secs  = 3600 ).

    ls_start_info-timestamp = ls_ts2.

    " Periodicity

    ls_scheduling_info-periodic_granularity = 'D'.
    ls_scheduling_info-periodic_value = 1.
    ls_scheduling_info-test_mode = abap_false.
    ls_scheduling_info-timezone = 'CET'.

    ls_end_info-type = 'NUM'.
    ls_end_info-max_iterations = 3.

    " Fill parameter table:
    " Fill the table only if you want to overrule the parameter values
    " which are stored in the template.
    " The field names in this program must match the field names of the template.

    ls_job_parameters-name = 'P_TEST1'.

    ls_value-sign = 'I'.
    ls_value-option = 'EQ'.
    ls_value-low = 'Test 1'.
    APPEND ls_value TO ls_job_parameters-t_value.

    APPEND ls_job_parameters TO lt_job_parameters.
    CLEAR ls_job_parameters.

    ls_job_parameters-name = 'P_TEST2'.

    ls_value-sign = 'I'.
    ls_value-option = 'BT'.
    ls_value-low = 'ATEST'.
    ls_value-high = 'ZTEST'.
    APPEND ls_value TO ls_job_parameters-t_value.

    ls_job_parameters-name = 'P_TEST2'.

    ls_value-sign = 'I'.
    ls_value-option = 'BT'.
    ls_value-low = '11111'.
    ls_value-high = '99999'.
    APPEND ls_value TO ls_job_parameters-t_value.

    APPEND ls_job_parameters TO lt_job_parameters.
    CLEAR ls_job_parameters.

    ls_job_parameters-name = 'P_TEST3'.

    ls_value-sign = 'I'.
    ls_value-option = 'EQ'.
    ls_value-low = '220'.
    APPEND ls_value TO ls_job_parameters-t_value.

    APPEND ls_job_parameters TO lt_job_parameters.
    CLEAR ls_job_parameters.

    TRY.

        " Some scenarios require that the job key ( = jobname, jobcount) is already known
        " before the job is created. The method generate_jobkey creates a valid job key.
        " This key can then be passed later on to the method schedule_job, and a job with
        " exactly this key is created.

        " Optional. You need this call only if you have to know the job key in advance.
        "        cl_apj_rt_api=>generate_jobkey(
        "          importing
        "            ev_jobname  = lv_jobname
        "            ev_jobcount = lv_jobcount ).

        " If you pass the table lt_job_parameters , then the parameters
        " contained in this table are used.
        " If you don't pass the table, the parameters contained in the
        " job template are used.

        cl_apj_rt_api=>schedule_job(
          EXPORTING
            iv_job_template_name   = lv_template_name
            iv_job_text            = lv_job_text
            is_start_info          = ls_start_info
            is_scheduling_info     = ls_scheduling_info
            is_end_info            = ls_end_info
            it_job_parameter_value = lt_job_parameters
" The following two parameters are optional. If you pass them, they must have been generated
" with the optional call generate_jobkey above.
"           iv_jobname             = lv_jobname
"           iv_jobcount            = lv_jobcount
          IMPORTING
            ev_jobname             = lv_jobname
            ev_jobcount            = lv_jobcount
        ).

        out->write( lv_jobname ).
        out->write( lv_jobcount ).

        cl_apj_rt_api=>get_job_status(
          EXPORTING
            iv_jobname         = lv_jobname
            iv_jobcount        = lv_jobcount
          IMPORTING
            ev_job_status      = lv_status
            ev_job_status_text = lv_statustext
        ).

        out->write( lv_status ).
        out->write( lv_statustext ).

        " Via the following method you can cancel the job
        " in the application job context 'cancel' means (as in the Fiori app):
        " 1. if the job is running, it will be canceled
        " 2. if the job has not yet started, it will be deleted.
        " In case the job is periodic, the whole periodicity chain is deleted.

* To comment if you want to keep the job
        cl_apj_rt_api=>cancel_job(
          exporting
            iv_jobname  = lv_jobname "change with the job name you want to cancel
            iv_jobcount = lv_jobcount "change with the job count you want to cancel
        ).
      catch cx_apj_rt into data(exc).
        lv_txt = exc->get_longtext( ).
        ls_ret = exc->get_bapiret2( ).
        out->write( 'ERROR:' ).
        out->write( lv_txt ).
        out->write( 'msg type =' ).
        out->write( ls_ret-type ).
        out->write( 'msg id =' ).
        out->write( ls_ret-id ).
        out->write( 'msg number =' ).
        out->write( ls_ret-number ).
        out->write( 'msg message =' ).
        out->write( ls_ret-message ).

        COMMIT WORK. "NOT NECESARY IN CASE OF RAP APPLICATION AS THE COMMIT IS HANDLED WITH THE RAP FRAMEWORK
    ENDTRY.

  ENDMETHOD.

ENDCLASS.
  • Via la tuile Fiori Application Jobs

Si vous êtes sur un système On Premise ou Private Cloud, on peut même vérifier en SM37 que ça a été correctement programmé.

A noter, sur BTP, ABAP environment et S/4 Cloud Public edition, il y a la création d’une application IAM nécessaire pour l’attribution des autorisations. Vous pouvez retrouver la gestion des autorisations pour On-Premise et S/4 Cloud Private Edition ici.

Application Fiori : https://fioriappslibrary.hana.ondemand.com/sap/fix/externalViewer/#/detail/Apps(%27F1240%27)/S16OP