Category > Articles

Sending a report or spool job by email

images/thumbnail.jpg - Thumbnail

This post inaugurates a new category in Abapinho: Code. Posts in this category will present useful programs that are ready to execute.

This one is used for sending the result of any report or spool order by e-mail. This program makes use of the new BCS (Business Communication Services) through the CL_BCS class instead of the tired and old SO_DOCUMENT_SEND_API1, may its soul rest in peace.

The content can be sent in the body of the email, as a TXT attachment or as an HTML attachment, with the latter being sent nice and neat with all the colours. Enjoy it. Blindly copy it and use it or dissect it and learn how to use CL_BCS. On the way, you can still learn, if you feel like it, how to use local classes and exception classes.

A local class is simpler to show here in Abapinho but if you want you can transform it into a global class through SE24 as Abapinho already explained some time ago so that you can use it anywhere.

*&---------------------------------------------------------------------*
*& Report ZZZ_NFG_SEND_REPORT_BY_EMAIL
*&
*&---------------------------------------------------------------------*
*& Author: Nuno Godinho
*& Date:   03 January 2012
*& Description: Sends an abap list (generated by a submitted program or
*&              read from an existing spool) by email. The list can be
*&              sent either in the email body, as a text attachment or
*&              as an HTML attachment.
*&---------------------------------------------------------------------*
REPORT zzz_nfg_send_list_by_mail MESSAGE-ID so.

***************************************************************************

* Exception classes
CLASS zcx_zs_no_receivers
  DEFINITION FINAL INHERITING FROM cx_static_check.
ENDCLASS.
CLASS zcx_zs_spool_error
  DEFINITION FINAL INHERITING FROM cx_static_check.
ENDCLASS.
CLASS zcx_zs_invalid_parameters
  DEFINITION FINAL INHERITING FROM cx_static_check.
ENDCLASS.
CLASS zcx_zs_objectlist_error
  DEFINITION FINAL INHERITING FROM cx_static_check.
ENDCLASS.

*----------------------------------------------------------------------*
*       CLASS cl_send_list_by_mail DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS cl_send_list_by_mail DEFINITION.
  PUBLIC SECTION.
    TYPES: ty_send_mode TYPE char4,
           ty_source    TYPE char6.

    CONSTANTS:
      k_send_mode_text        TYPE ty_send_mode VALUE 'TEXT',
      k_send_mode_text_attach TYPE ty_send_mode VALUE 'TXTA',
      k_send_mode_html_attach TYPE ty_send_mode VALUE 'HTMA',
      k_send_mode_attachment  TYPE ty_send_mode VALUE space,

      k_source_submit         TYPE ty_source    VALUE 'SUBMIT',
      k_source_spool          TYPE ty_source    VALUE 'SPOOL'.

    METHODS:
      constructor
        RAISING
          cx_send_req_bcs,

      add_recipient
        IMPORTING
          i_smtp_address TYPE ad_smtpadr
        RAISING
          cx_send_req_bcs
          cx_address_bcs,

      submit_and_send_mail
        IMPORTING
          i_report       TYPE program
          i_variant      TYPE raldb_vari
          i_send_mode    TYPE ty_send_mode
          i_subject      TYPE so_obj_des
          i_smtp_address TYPE ad_smtpadr OPTIONAL
        RAISING
          zcx_zs_no_receivers
          cx_bcs
          zcx_zs_invalid_parameters
          zcx_zs_objectlist_error
          zcx_zs_spool_error,

      read_spool_and_send_mail
        IMPORTING
          i_spool_number TYPE rspoid
          i_send_mode    TYPE ty_send_mode
          i_subject      TYPE so_obj_des
          i_smtp_address TYPE ad_smtpadr OPTIONAL
        RAISING
          zcx_zs_no_receivers
          zcx_zs_invalid_parameters
          zcx_zs_objectlist_error
          cx_send_req_bcs
          cx_bcs
          zcx_zs_spool_error.

  PRIVATE SECTION.
    DATA: go_send_request       TYPE REF TO cl_bcs.

    METHODS:
      get_objectlist_and_send_mail
        IMPORTING
          i_source       TYPE ty_source
          i_report       TYPE program OPTIONAL
          i_variant      TYPE raldb_vari OPTIONAL
          i_spool_number TYPE rspoid OPTIONAL
          i_send_mode    TYPE ty_send_mode
          i_subject      TYPE so_obj_des
          i_smtp_address TYPE ad_smtpadr OPTIONAL
        RAISING
          zcx_zs_no_receivers
          zcx_zs_invalid_parameters
          zcx_zs_objectlist_error
          cx_bcs
          zcx_zs_spool_error,

      submit_report_to_memory
        IMPORTING
          value(i_report)     TYPE program
          value(i_variant)    TYPE raldb_vari
        RETURNING
          value(ot_listobject) TYPE table_abaplist
        RAISING
          zcx_zs_objectlist_error,

      read_spool_to_memory
        IMPORTING
          i_spool_number     TYPE rspoid
        RETURNING
          value(ot_listobject) TYPE table_abaplist
        RAISING
          zcx_zs_spool_error,

       get_objectlist
        IMPORTING
          i_source       TYPE ty_source
          value(i_report)     TYPE program
          value(i_variant)    TYPE raldb_vari
          i_spool_number     TYPE rspoid
        RETURNING
          value(ot_listobject) TYPE table_abaplist
        RAISING
          zcx_zs_spool_error
          zcx_zs_objectlist_error,

       create_text_document
         IMPORTING
           i_report       TYPE program
           i_variant      TYPE raldb_vari
           i_spool_number TYPE rspoid
           i_subject      TYPE so_obj_des
           it_listobject  TYPE table_abaplist OPTIONAL
         RETURNING
           value(o_document)    TYPE REF TO cl_document_bcs
         RAISING
           zcx_zs_objectlist_error
           cx_document_bcs,

       add_header_to_body
         IMPORTING
           i_report       TYPE program
           i_variant      TYPE raldb_vari
           i_spool_number TYPE rspoid
         CHANGING
           xt_soli TYPE soli_tab,

      add_abaplist_text_attach
        IMPORTING
          it_listobject   TYPE table_abaplist
          i_name          TYPE so_obj_des
        CHANGING
          x_document      TYPE REF TO cl_document_bcs
        RAISING
          zcx_zs_objectlist_error
          cx_document_bcs,

      add_abaplist_html_attach
        IMPORTING
          it_listobject   TYPE table_abaplist
          i_name          TYPE so_obj_des
        CHANGING
          x_document      TYPE REF TO cl_document_bcs
        RAISING
          cx_document_bcs,

      send_mail
        IMPORTING
          value(it_listobject) TYPE table_abaplist
          i_report      TYPE program OPTIONAL
          i_variant     TYPE raldb_vari OPTIONAL
          i_spool_number TYPE rspoid OPTIONAL
          i_send_mode   TYPE ty_send_mode
          i_subject     TYPE so_obj_des
        RAISING
          zcx_zs_objectlist_error
          zcx_zs_invalid_parameters
          cx_bcs,

      build_attach_name
        IMPORTING
          i_report    TYPE program
          i_variant   TYPE raldb_vari
          i_spool_number TYPE rspoid
          i_extension TYPE so_obj_des OPTIONAL
        RETURNING value(o_name) TYPE so_obj_des.

ENDCLASS.                    "cl_send_list_by_mail DEFINITION

*----------------------------------------------------------------------*
*       CLASS cl_send_list_by_mail IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS cl_send_list_by_mail IMPLEMENTATION.

  METHOD constructor.
*   Create the send request
    go_send_request = cl_bcs=>create_persistent( ).
    go_send_request->set_send_immediately( 'X' ).
  ENDMETHOD.                    "constructor

  METHOD add_recipient.
    DATA: recipient TYPE REF TO if_recipient_bcs.
    recipient = cl_cam_address_bcs=>create_internet_address( i_smtp_address ).
    go_send_request->add_recipient( recipient ).
  ENDMETHOD.                    "add_receiver

  METHOD submit_and_send_mail.
    get_objectlist_and_send_mail(
              i_source       = k_source_submit
              i_send_mode    = i_send_mode
              i_report       = i_report
              i_variant      = i_variant
              i_subject      = i_subject ).
  ENDMETHOD.                    "submit_report_and_sendmail

  METHOD read_spool_and_send_mail.
    get_objectlist_and_send_mail(
              i_source       = k_source_spool
              i_send_mode    = i_send_mode
              i_spool_number = i_spool_number
              i_subject      = i_subject ).
  ENDMETHOD.                    "read_spool_and_send_mail

  METHOD get_objectlist_and_send_mail.
    DATA: t_recipients TYPE bcsy_re,
          t_listobject TYPE table_abaplist.

*   Check parameters
    IF ( i_source = k_source_spool AND i_spool_number IS INITIAL ) OR
       ( i_source = k_source_submit AND i_report IS INITIAL ).
      RAISE EXCEPTION TYPE zcx_zs_invalid_parameters.
    ENDIF.

*   Add recipient if supplied
    IF i_smtp_address IS SUPPLIED.
      add_recipient( i_smtp_address ).
    ENDIF.

*   It should only run if there is at least one recipient
    t_recipients = go_send_request->recipients( ).
    IF t_recipients[] IS INITIAL.
      RAISE EXCEPTION TYPE zcx_zs_no_receivers.
    ENDIF.

*   Get objectlist either from submit or from spool
    t_listobject = get_objectlist(
      i_source       = i_source
      i_report       = i_report
      i_variant      = i_variant
      i_spool_number = i_spool_number ).

*   Send email
    CALL METHOD send_mail
      EXPORTING
        it_listobject  = t_listobject
        i_report       = i_report
        i_variant      = i_variant
        i_spool_number = i_spool_number
        i_send_mode    = i_send_mode
        i_subject      = i_subject.
  ENDMETHOD.                    "get_objectlist_and_send_mail

  METHOD get_objectlist.
*   Get abap list into memory (either from spool or submit)
    IF i_source = k_source_submit.
      submit_report_to_memory( i_report  = i_report i_variant = i_variant ).
    ELSE.
      read_spool_to_memory( i_spool_number = i_spool_number ).
    ENDIF.

*   Import the list from memory and store it in table listobject
    REFRESH : ot_listobject.
    CALL FUNCTION 'LIST_FROM_MEMORY'
      TABLES
        listobject = ot_listobject
      EXCEPTIONS
        not_found  = 1
        OTHERS     = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_zs_objectlist_error.
    ENDIF.

*   Free memory
    CALL FUNCTION 'LIST_FREE_MEMORY'
      TABLES
        listobject = ot_listobject
      EXCEPTIONS
        OTHERS     = 1.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_zs_objectlist_error.
    ENDIF.

  ENDMETHOD.                    "get_objectlist

  METHOD submit_report_to_memory.
    TRANSLATE: i_report  TO UPPER CASE,
               i_variant TO UPPER CASE.
*   Submit report
    SUBMIT (i_report) USING SELECTION-SET i_variant EXPORTING LIST TO MEMORY AND RETURN.
  ENDMETHOD.                    "submit_report

  METHOD read_spool_to_memory.
*   Adapted from FM RSPO_RETURN_ABAP_SPOOLJOB. This FM converts the list to ASCII
*   and I needed it as an ABAPLIST so I just copied the code and adapted it
    DATA: data_is_otf TYPE c.

    CALL FUNCTION 'RSPO_CHECK_JOB_ID_PERMISSION'
      EXPORTING
        rqident       = i_spool_number
        access        = 'DISP'
      EXCEPTIONS
        no_such_job   = 1
        no_permission = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_zs_spool_error.
    ENDIF.

    CALL FUNCTION 'RSPO_GET_TYPE_SPOOLJOB'
      EXPORTING
        rqident        = i_spool_number
      IMPORTING
        is_otf         = data_is_otf
      EXCEPTIONS
        can_not_access = 1.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_zs_spool_error.
    ENDIF.

    IF data_is_otf = 'X'.
      RAISE EXCEPTION TYPE zcx_zs_spool_error.
    ELSE.
      SUBMIT rspolist EXPORTING LIST TO MEMORY AND RETURN
                      WITH rqident = i_spool_number
                      WITH first = 1
                      WITH last = 0
                      WITH pages = space.
    ENDIF.
  ENDMETHOD.                    "read_spool

  METHOD send_mail.

    DATA: document       TYPE REF TO cl_document_bcs,
          sent_to_all    TYPE os_boolean,
          bcs_exception  TYPE REF TO cx_bcs,
          name           TYPE so_obj_des.

    IF i_send_mode = k_send_mode_text.
      document = create_text_document(
        i_report       = i_report
        i_variant      = i_variant
        i_spool_number = i_spool_number
        it_listobject  = it_listobject
        i_subject      = i_subject ).
    ELSE.
*     Create text document without adding listobject to the body (it will be attached)
      document = create_text_document(
        i_report       = i_report
        i_variant      = i_variant
        i_spool_number = i_spool_number
        i_subject      = i_subject ).
*     Add attachment to document
      IF i_send_mode = k_send_mode_text_attach.
        name = build_attach_name(
          i_report       = i_report
          i_variant      = i_variant
          i_spool_number = i_spool_number
          i_extension    = 'TXT' ).
        add_abaplist_text_attach(
           EXPORTING
             it_listobject = it_listobject
             i_name        = name
          CHANGING
            x_document = document ).
      ELSEIF i_send_mode = k_send_mode_html_attach.
        name = build_attach_name(
          i_report       = i_report
          i_variant      = i_variant
          i_spool_number = i_spool_number ).
        add_abaplist_html_attach(
           EXPORTING
             it_listobject = it_listobject
             i_name        = name
          CHANGING
            x_document = document ).
      ENDIF.
    ENDIF.

*   Add document to send request
    go_send_request->set_document( document ).

*   Send
    sent_to_all = go_send_request->send( i_with_error_screen = 'X' ).
*    IF sent_to_all NE 'X'.
*      RAISE EXCEPTION TYPE zcx_zs_mail_not_sent.
*    ENDIF.

  ENDMETHOD.                    "send_mail

  METHOD create_text_document.
    DATA: t_listasci   TYPE STANDARD TABLE OF solisti1,
          t_listobject LIKE it_listobject,
          t_soli       TYPE soli_tab,
          w_soli       LIKE LINE OF t_soli.

*   Add header
    add_header_to_body(
      EXPORTING
        i_report       = i_report
        i_variant      = i_variant
        i_spool_number = i_spool_number
      CHANGING
        xt_soli = t_soli ).

*   Add abap list
    IF it_listobject IS SUPPLIED.

      CLEAR w_soli.
      APPEND w_soli TO t_soli.
      w_soli = '------------------------------------------------------'.
      APPEND w_soli TO t_soli.
      CLEAR w_soli.
      APPEND w_soli TO t_soli.

      t_listobject[] = it_listobject[].
      CALL FUNCTION 'LIST_TO_ASCI'
        TABLES
          listasci           = t_listasci
          listobject         = t_listobject
        EXCEPTIONS
          empty_list         = 1
          list_index_invalid = 2
          OTHERS             = 3.
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_zs_objectlist_error.
      ENDIF.
      APPEND LINES OF t_listasci TO t_soli.
    ENDIF.

*   Create text document
    o_document = cl_document_bcs=>create_document(
                            i_type    = 'RAW'
                            i_text    = t_soli
                            i_subject = i_subject ).
  ENDMETHOD.                    "create_text_document

  METHOD add_header_to_body.
    DATA: w_soli LIKE LINE OF xt_soli.

    IF i_report IS NOT INITIAL.
      CONCATENATE 'Report:' i_report INTO w_soli SEPARATED BY space.
      APPEND w_soli TO xt_soli.
    ENDIF.

    IF i_variant IS NOT INITIAL.
      CONCATENATE 'Variant:' i_variant INTO w_soli SEPARATED BY space.
      APPEND w_soli TO xt_soli.
    ENDIF.

    IF i_spool_number IS NOT INITIAL.
      MOVE i_spool_number TO w_soli.
      CONCATENATE 'Spool number:' w_soli INTO w_soli SEPARATED BY space.
      APPEND w_soli TO xt_soli.
    ENDIF.

    CONCATENATE 'Date:' sy-datum sy-uzeit INTO w_soli SEPARATED BY space.
    APPEND w_soli TO xt_soli.

    CONCATENATE 'User:' sy-uname INTO w_soli SEPARATED BY space.
    APPEND w_soli TO xt_soli.

  ENDMETHOD.                    "build_header

  METHOD add_abaplist_text_attach.
    DATA: t_solix TYPE solix_tab.

*   It's always necessary to compress the table. SAPconnect will decompress it
    CALL FUNCTION 'TABLE_COMPRESS'                          "#EC *
      TABLES
        in             = it_listobject
        out            = t_solix
      EXCEPTIONS
        compress_error = 1
        OTHERS         = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_zs_objectlist_error.
    ENDIF.

    x_document->add_attachment(
      i_attachment_type    = 'ALI'
      i_attachment_subject = i_name
      i_att_content_hex    = t_solix ).

  ENDMETHOD.                    "add_abaplist_text_attach

  METHOD add_abaplist_html_attach.
    DATA: t_soli       TYPE soli_tab,
          t_listobject LIKE it_listobject.

    t_listobject[] = it_listobject[].

    CALL FUNCTION 'WWW_HTML_FROM_LISTOBJECT'
      TABLES
        html       = t_soli
        listobject = t_listobject.

    x_document->add_attachment(
      i_attachment_type    = 'HTM'
      i_attachment_subject = i_name
      i_att_content_text   = t_soli ).

  ENDMETHOD.                    "add_abaplist_html_attach

  METHOD build_attach_name.
    IF i_report IS NOT INITIAL.
      o_name = i_report.
      IF i_variant IS NOT INITIAL.
        CONCATENATE o_name '-' i_variant INTO o_name.
      ENDIF.
    ELSE.
      MOVE i_spool_number TO o_name.
      SHIFT o_name LEFT DELETING LEADING space.
    ENDIF.
    CONCATENATE o_name '-' sy-datum '-' sy-uzeit INTO o_name.
    IF i_extension IS NOT INITIAL.
      CONCATENATE o_name '.' i_extension INTO o_name.
    ENDIF.
  ENDMETHOD.                    "build_attach_name

ENDCLASS.                    "cl_send_list_by_mail IMPLEMENTATION

***************************************************************************
TABLES: adr6.

*------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-001.
PARAMETERS: p_subj TYPE so_obj_des OBLIGATORY.
SELECT-OPTIONS: s_rec  FOR adr6-smtp_addr NO INTERVALS LOWER CASE OBLIGATORY.
SELECTION-SCREEN END OF BLOCK b1.

SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE text-002.
PARAMETERS: p_submit RADIOBUTTON GROUP typ USER-COMMAND typ.
SELECTION-SCREEN BEGIN OF BLOCK b2a WITH FRAME.
PARAMETERS:     p_rep  TYPE program MODIF ID ty1,
              p_var  TYPE raldb_vari MODIF ID ty1.
SELECTION-SCREEN END OF BLOCK b2a.
PARAMETERS: p_spool RADIOBUTTON GROUP typ.
SELECTION-SCREEN BEGIN OF BLOCK b2b WITH FRAME.
PARAMETERS:   p_rspoid TYPE rspoid MODIF ID ty2.
SELECTION-SCREEN END OF BLOCK b2b.
SELECTION-SCREEN END OF BLOCK b2.

SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE text-003.
PARAMETERS: p_text  RADIOBUTTON GROUP opt,
            p_texta RADIOBUTTON GROUP opt,
            p_htmla RADIOBUTTON GROUP opt DEFAULT 'X'.
SELECTION-SCREEN END OF BLOCK b3.

AT SELECTION-SCREEN OUTPUT.
  LOOP AT SCREEN.
    IF screen-group1 = 'TY1'.
      IF p_submit IS NOT INITIAL.
        screen-input = 1.
      ELSE.
        screen-input = 0.
      ENDIF.
      MODIFY SCREEN.
    ENDIF.
    IF screen-group1 = 'TY2'.
      IF p_spool IS NOT INITIAL.
        screen-input = 1.
      ELSE.
        screen-input = 0.
      ENDIF.
      MODIFY SCREEN.
    ENDIF.
  ENDLOOP.

START-OF-SELECTION.

  DATA: go_sender TYPE REF TO cl_send_list_by_mail,
        send_mode TYPE cl_send_list_by_mail=>ty_send_mode,
        exc_ref   TYPE REF TO cx_root,
        text      TYPE string.

  IF ( p_submit IS NOT INITIAL AND p_rep IS INITIAL ) OR
     ( p_spool IS NOT INITIAL AND p_rspoid IS INITIAL ).
    MESSAGE s622 DISPLAY LIKE 'E'.
    EXIT.
  ENDIF.

  CREATE OBJECT go_sender.

* Determine send mode
  CASE 'X'.
    WHEN p_texta.
      send_mode = cl_send_list_by_mail=>k_send_mode_text_attach.
    WHEN p_htmla.
      send_mode = cl_send_list_by_mail=>k_send_mode_html_attach.
    WHEN OTHERS.
      send_mode = cl_send_list_by_mail=>k_send_mode_text.
  ENDCASE.

* Add recipients
  LOOP AT s_rec.
    CALL METHOD go_sender->add_recipient
      EXPORTING
        i_smtp_address = s_rec-low.
  ENDLOOP.

  TRY.
      CASE 'X'.
        WHEN p_submit.
          CALL METHOD go_sender->submit_and_send_mail
            EXPORTING
              i_send_mode = send_mode
              i_report    = p_rep
              i_variant   = p_var
              i_subject   = p_subj.
        WHEN p_spool.
          CALL METHOD go_sender->read_spool_and_send_mail
            EXPORTING
              i_send_mode    = send_mode
              i_spool_number = p_rspoid
              i_subject      = p_subj.
      ENDCASE.
      WRITE: / 'Mail message sent'.
    CATCH cx_root INTO exc_ref.
      text = exc_ref->get_text( ).
      WRITE: / text.
  ENDTRY.

* It will not work without this commit
  COMMIT WORK.

Texts for selection screen:

P_SUBJ Subject

S_REC| Recipients

P_SUBMIT| Send report

P_REP| Report

P_VAR| Variant

P_SPOOL| Send spool job

P_RSPOID| Spool job ID

P_TEXT| Email body

P_TEXTA| TXT attachment

P_HTMLA| HTML attachment

Greetings from Abapinho.

Automating the ALV field catalogue

images/thumbnail.jpg - Thumbnail

Sometimes I ask myself what percentage of the world’s ABAP code is unnecessary. A paradigmatic example of how time can be wasted writing code which is of no use to anyone and only creates problems is the ALV’s all-too-common field description definition sitting directly in ABAP.

The *_SINGLE_READ functions

images/thumbnail.jpg - Thumbnail

When you need to derive a single record from a database table, you normally use SELECT SINGLE, which is like this in its most basic form, as everyone knows: SELECT SINGLE * FROM KNA1 WHERE KUNNR = '1234567890'. Of course, if you are interested in just a few fields, ideally you select them explicitly to avoid copying unnecessary data from one side to the other: DATA: lv_name1 TYPE name1. SELECT SINGLE name1 INTO lv_name1 FROM KNA1 WHERE KUNNR = '1234567890'.

LOOP ASSIGNING instead of LOOP INTO

images/thumbnail.jpg - Thumbnail

In the beginning there was INTO. Actually, in the beginning it was not even INTO.

On your marks, get set, go!

images/thumbnail.jpg - Thumbnail

Ladies and gentlemen, boys and girls, the race is about to begin.

Introduction

The four competitors are as follows. They are 4 internal tables, of different races and creeds, which will fight for the athletics title of speed LOOP. Here they are:

Competitor 1: DATA: LT_ITEM TYPE TABLE Competitor 2: DATA: LT_ITEM_HASHED TYPE HASHED TABLE Competitor 3: DATA: LT_ITEM_SORTED TYPE SORTED TABLE Competitor 4: DATA: LT_ITEM TYPE TABLE + INTO INDEX

<!--:pt-->Como encavalitar tabelas<!--:-->

images/thumbnail.jpg - Thumbnail

Às vezes temos de criar uma tabela Z. Às vezes temos até de criar várias tabelas Z. Às vezes estas tabelas estão relacionadas de alguma forma. Como quando uma contém dados de cabeçalho e a outra dados de item, por exemplo. Ora se estão relacionadas pode dar jeito que sejam editadas em conjunto. É para isso que servem os Clusters de Visão (view cluster).

<!--:pt-->SELECT com mais olhos que barriga<!--:-->

images/thumbnail.jpg - Thumbnail

Embora seja evidente que, ao fazer selecções de dados de uma tabela da base de dados, devemos ter o cuidado de escolher apenas os campos que necessitamos, a verdade é que há muito boa gente que não se dá a esse trabalho e manda vir tudo.

Mediremos aqui a diferença real entre as duas abordagens.

<!--:pt-->Import/Export = Contrabando<!--:-->

images/thumbnail.jpg - Thumbnail

O Java, uma linguagem de programação bem pensada, ajuda o programador a organizar o seu código obrigando-o a desenvolvê-lo de forma estruturada. A sua própria filosofia potencia o pensamento estruturado e promove coerência e arrumação.

Já o ABAP… promove o caos. Está cheio de caminhos perniciosos que levam direitinho a um inferno confuso e labiríntico. E geralmente são as coisas aparentemente mais convenientes que se revelam as mais perigosas.

Uma das conveniências piores é a parelha IMPORT e EXPORT.

<!--:pt-->Procurar uma BADI no palheiro<!--:-->

images/thumbnail.jpg - Thumbnail

O SAP é um enorme palheiro. E os ABAPers são pessoas que trepam por esse palheiro acima e nele vasculham e escarafuncham em busca de agulhas de todo o género. Às vezes, desesperados, deitam-se a descansar e vêm uma quantidade enorme de bicharocos que vivem no palheiro fazer-lhes comichão. Para evitar que isso aconteça, o Artur Moreira propõe-nos uma série de diferentes técnicas para procurar BADIs neste grande palheiro que é o SAP.

<!--:pt-->Evernote - No anotar é que está o ganho<!--:-->

images/thumbnail.jpg - Thumbnail

Il faut cultiver notre jardin - Voltaire

No SAP fala-se muito de experiência. Fulano tem muita experiência, sicrano tem pouca experiência. Tenta-se com isso medir a capacidade que alguém tem de usar o seu passado para lidar com o seu futuro. Mas nem sempre ter experimentado uma coisa é sinónimo de ganhar experiência com ela. O exemplo paradigmático ao alcance de todos é o amor: quantos desgraçados, por muitas experiências desgraçadas que tenham tido, se continuam a desgraçar-se no amor! O SAP não é o amor. Ainda assim não nos faltarão exemplos de consultores que no CV descrevem muitas experiências e na prática se revelam uns grandes inexperientes.

A Inteligência terá certamente muito peso nessa capacidade de sublimar as experiências, de as conservar, de fazer delas Compota de Experiência. Mas, pelo menos no que toca ao mirabolante mundo do SAP, quem tem também muito a dizer é a senhora Memória. Ou melhor dizendo, a falta dela.

O mundo do SAP é horizontal e vasto. Para onde quer que se olhe é a perder de vista. E embora seja um mundo bastante populado e onde se viaja muito, não houve até hoje, que eu saiba, cartógrafos de jeito. Os mapas que dele se fizeram são parcos, pobres, distorcidos e normalmente ainda o julgam plano quando toda a gente já sabe há muito tempo que ele é curvo.

Há que cartografar o nosso mapa.

<!--:pt-->O maravilhoso mundo do Application Log<!--:-->

Uma boa parte dos reports, interfaces ou jobs, têm de produzir algum tipo de relatório. É normal ver isso feito recorrendo ao comando WRITE. Ora nos dias que correm, usar o comando WRITE para isto é como recorrer a um par de pedras para acender uma fogueira. Afinal, porque não usar o Application Log que é muito mais simples e prático e standard e é só vantagens?

<!--:pt-->Macros - Velocidade de ponta<!--:-->

Normalmente quando há um pedaço de código que pretendemos reutilizar várias vezes, transformamo-lo numa sub-rotina que pode depois ser invocada repetidamente. Embora a SAP não saiba estruturar o seu próprio código, ainda assim, o ABAP, coitadinho, permite-o. E até disponibiliza várias alternativas para modularizar o código. Eu conto quatro alternativas que listo aqui, da mais rígida para a mais flácida: METHOD, FUNCTION, FORM, DEFINE. Se os 3 primeiros são já familiar de todos, o último - DEFINE - quase ninguém usa. O DEFINE permite definir macros em ABAP. E o que são macros? São sub-rotinas aparentes.

Aparentes porquê?

<!--:pt-->Constantes inconstantes<!--:-->

As constantes não o são. Toda a gente sabe. Quando se define uma constante, é certo como a morte que mais cedo ou mais tarde alguém vem e muda-a. Quando isto acontece, quem usa os valores directamente no código ABAP está feito ao bife; Quem define constantes no código só tem de alterar o valor num sítio mas vê-se ainda assim obrigado a editar o programa e a transportá-lo, coisa que, dependendo do grau de burocracia do ambiente em que se está, se pode revelar complicada. Este artigo apresenta uma solução simples mas sofisticada para gerir de forma centralizada todas as constantes de um sistema SAP através do uso de uma tabela de utilizador central onde se manterá todos os valores constantes e de uma classe com alguns métodos estáticos que serão usados para os obter.

<!--:pt-->Melhorar os melhoramentos<!--:-->

No princípio era o INCLUDE.

Depois vieram os CMODs,
  Seguiram-se logo as BADIs,
    Agora são os Enhancements.

        E no entanto, o caos continua.

Crítica Na maior parte dos projectos SAP em que já trabalhei, a metodologia de utilização de todas estas modificações é a tudo-ao-molho-e-fé-em-SAP e é normal encontrar num único include - como o MV45AFZZ - extensões de código tão grandes que, se o SAP fosse a princesa Rapunzel, dava para lhe fazer umas tranças até cá abaixo para o príncipe subir à sua torre. Este código normalmente implementa várias funcionalidades diferentes e independentes que, ao longo do tempo, se vão emaranhando quase irreversivelmente umas nas outras (tipo trança mesmo). Como consequência, qualquer alteração ao código existente requer cuidados redobrados e é sempre vista como um risco para o funcionamento de tudo o que lá está.

Venho aqui propor uma solução simples e eficaz para este problema.

<!--:pt-->Change pointers<!--:-->

Neste artigo tento explicar o que são change pointers e revelar como são úteis e fáceis de usar.

O que é um change pointer Um change pointer é um mecanismo de registo de alteração de dados baseado em change documents desenvolvido pela SAP especialmente para ALE. Permite saber, de forma simples e eficiente, quais os registos alterados em uma ou nas várias tabelas por ele monitorizadas. Os change pointers são utilizados maioritariamente como gatilhos para a criação de IDOCs. Mas não devem ser vistos apenas como tal e espero que este artigo traga alguma luz a este mecanismo tão útil mas tão descurado no SAP.