Tuesday 2 April 2019

PDF Base 64 (string) to SAP FBL1N

I did something simple in SAP, but I did not find everything in one topic only. So I decided to do this post and I’ll try to explain it as easily as possible.

The problem was: “I have a PDF file in the portal and I need to attach it to the FBL1N transaction.”

The tools used were SAP ECC 618 EHP 6, Abap Netweaver 7.51, SAP PO 7.5 SP01 and any portal with API Rest that returns a JSON.

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

Doing some research I discovered that the best way to transpose a PDF is to use PDF converted to Base64, but as a string and not binary. For the binary does not recognize some special characters of our handwriting.

Conversion from PDF to Base64 (STRING) can be done easily via code (http://bit.ly/2Ft4vPE in JS) or by using some sites that already do this, the result is quite different from the conventional, more or less this:

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

The communication to integrate with the PO was done in NodeJS and I used Ngrok to make the reverse proxy. Simple, easy and fast. I used the Visual Studio Code to make the code.

Something like this https://www.youtube.com/watch?v=ZPdYdVPtWNo

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

198/5000
PO is the simplest part to do.

In Enterprise Builder:

In this image already shows much of what was done. All fields are strings as we are using the Base64 string and not the binary.

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

Operation Mapping

In my scenario I do not send anything to do the “GET”, but the PO requires that we pass some information, so I’m passing a dummy

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

In Integration Builder, it was just the configuration of ICO to connect with Ngrok. Remember that every time Ngrok is initialized, the URL of the Communication Channel must be changed. See image below.

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

Let’s now see the part that matters, the ABAP part.

In the SPORXY transaction we create all the PO objects that will be needed for reading the NodeJS.

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

Then in the program is created the reading of the proxy that will get to the NodeJS.

    DATA output TYPE update_pdf_dummy .
    DATA input TYPE return_update_pdf_sa.

    TRY.
        DATA(Base64_pdf) = NEW outbound_update_pdf( ).
        Base64_pdf->si_outbound_update_pdf_titulos(
          EXPORTING
            output             = output
          IMPORTING
            input              = input ).

      CATCH cx_ai_system_fault INTO DATA(system_fault).
        BREAK-POINT.
    ENDTRY.

The Input will come from the Base64 string. The first thing to do is to execute the SSFC_BASE64_DECODE function.

DATA twebpdf TYPE string.
DATA fic_binario TYPE xstring.

    twebpdf = base64.

    CALL FUNCTION 'SSFC_BASE64_DECODE'
      EXPORTING
        b64data = twebpdf
      IMPORTING
        bindata = fic_binario.

Here we will convert the string to xstring, which is the string that SAP understands to do the conversion.

Soon after we will create a table of type sdokcntbin and put the base64 inside it. Taking advantage of the Try already put the OPEN DATASET in it, because I will generate in the server the file. If you are generating the local file you can use the GUI_DOWNLOAD for binary files.

    DATA contents_tb TYPE TABLE OF sdokcntbin.
    DATA contents_st TYPE sdokcntbin.
    DATA file_length TYPE i.
    DATA flag        TYPE c.
    DATA off         TYPE i.
    DATA len         TYPE i.
    DATA file_name TYPE string VALUE '/usr/sap/tmp/'.

    TRY.
        len = xstrlen( fic_binario ).
        file_length = len.
        WHILE flag IS INITIAL.

          IF len LE 1022.
            contents_st-line = fic_binario+off(len).
            flag = 'X'.
          ELSE.
            contents_st-line = fic_binario+off(1022).
            off = off + 1022.
            len = len - 1022.
          ENDIF.
          APPEND contents_st TO contents_tb.
        ENDWHILE.

        CONCATENATE file_name filename INTO file_name.
        CONDENSE file_name NO-GAPS.

        OPEN DATASET file_name FOR OUTPUT IN BINARY MODE.
        LOOP AT contents_tb ASSIGNING FIELD-SYMBOL(<contents_fs>).
          TRANSFER <contents_fs> TO file_name.
        ENDLOOP.
        CLOSE DATASET file_name.

    ENDTRY.

If you used GUI_DOWNLOAD you will have the PDF already converted, if you used the OPEN DATASET you can get the PDF using CG3Y or AL11. Remember the format is always BIN.

We have not yet attached to FBL1N, but we already have the file ready.

Basically we will have to read via OPEN DATASET, or GUI_UPLOAD, the file and store in a table type soli and convert the entire contents to binary.

    DATA content_tb  TYPE  STANDARD TABLE OF soli.
    DATA content_st  TYPE soli.
    DATA file        TYPE string VALUE '/usr/sap/tmp/'.

    CONCATENATE file filename INTO file.
    OPEN DATASET file FOR INPUT IN BINARY MODE.
      WHILE sy-subrc = 0.
        READ DATASET file INTO content_st.

        APPEND  content_st TO content_tb.
      ENDWHILE.
    CLOSE DATASET file.

    CALL FUNCTION 'SO_CONVERT_CONTENTS_BIN'
      EXPORTING
        it_contents_bin = content_tb[]
      IMPORTING
        et_contents_bin = content_tb[].

After that we have to get the ID of the folder that SAP will store to show in FBL1N, for this we use the SO_FOLDER_ROOT_ID_GET function. Use as private because shared is not recognized by the transaction.

    DATA folder_id   TYPE soodk.

      CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'
        EXPORTING
          region                = 'B' “Privado
        IMPORTING
          folder_id             = folder_id
        EXCEPTIONS
          communication_failure = 1
          owner_not_exist       = 2
          system_failure        = 3
          x_error               = 4
          OTHERS                = 5.

The next step is to separate the file extension from the name. There are thousands of ways to do this I’ve used a function, you can do as you see fit. With the done split we will store in the objheader_tb table concatenated ‘& SO_FILENAME = “and the name of the file. This will cause the transaction to understand the file that will be opened.

    DATA objhead_tb  TYPE STANDARD TABLE OF soli.
    DATA file_name   TYPE c LENGTH 100.
    DATA extension   TYPE c LENGTH 4.

      CALL FUNCTION 'CH_SPLIT_FILENAME'
        EXPORTING
          complete_filename = file
        IMPORTING
          extension         = extension
          name_with_ext     = file_name
        EXCEPTIONS
          invalid_drive     = 1
          invalid_path      = 2
          OTHERS            = 3.
      IF sy-subrc EQ 0.

        APPEND INITIAL LINE TO objhead_tb ASSIGNING FIELD-SYMBOL(<objhead_fs>).
        CONCATENATE '&SO_FILENAME=' file_name INTO <objhead_fs>.
      ENDIF.

Next we will execute the SO_OBJECT_INSERT function, but before we need to fill in the prerequisite structures of it. Objheader_tb was populated up and content_tb higher up.

    DATA content_tb  TYPE  STANDARD TABLE OF soli.
    DATA objhead_tb  TYPE STANDARD TABLE OF soli.
    DATA object_st   TYPE borident.
    DATA obj_id      TYPE soodk.
    DATA content_st  TYPE soli.
    DATA obj_data_st TYPE sood1.

      object_st-objkey  = objid.
      object_st-objtype = 'BSEG'.“BSEG pq é FBL1N outras transações isso muda
      obj_data_st-objsns = 'O'.
      obj_data_st-objla = sy-langu.
      obj_data_st-objdes = 'Attachment by Thiago'.
      obj_data_st-file_ext = extension.
      TRANSLATE obj_data_st-file_ext TO UPPER CASE.
      obj_data_st-objlen =  lines( content_tb ) * 255.

      CALL FUNCTION 'SO_OBJECT_INSERT'
        EXPORTING
          folder_id                  = folder_id
          object_type                = 'EXT'
          object_hd_change           = obj_data_st
        IMPORTING
          object_id                  = obj_id
        TABLES
          objhead                    = objhead_tb
          objcont                    = content_tb
        EXCEPTIONS
          active_user_not_exist      = 1
          communication_failure      = 2
          component_not_available    = 3
          dl_name_exist              = 4
          folder_not_exist           = 5
          folder_no_authorization    = 6
          object_type_not_exist      = 7
          operation_no_authorization = 8
          owner_not_exist            = 9
          parameter_error            = 10
          substitute_not_active      = 11
          substitute_not_defined     = 12
          system_failure             = 13
          x_error                    = 14
          OTHERS                     = 15.

And to finish we just need to execute the BINARY_RELATION_CREATE COMMIT function, followed by a COMMIT WORK.

    DATA folmem_k_st TYPE sofmk.
    DATA note_st     TYPE borident.
    DATA ep_note     TYPE borident-objkey.

        folmem_k_st-foltp = folder_id-objtp.
        folmem_k_st-folyr = folder_id-objyr.
        folmem_k_st-folno = folder_id-objno.
        folmem_k_st-doctp = folder_id-objtp.
        folmem_k_st-docyr = folder_id-objyr.
        folmem_k_st-docno = folder_id-objno.
        ep_note = folmem_k_st.
        note_st-objtype = 'MESSAGE'.
        note_st-objkey = ep_note.
        CALL FUNCTION 'BINARY_RELATION_CREATE_COMMIT'
          EXPORTING
            obj_rolea      = object_st
            obj_roleb      = note_st
            relationtype   = 'ATTA'
          EXCEPTIONS
            no_model       = 1
            internal_error = 2
            unknown        = 3
            OTHERS         = 4.
        IF sy-subrc EQ 0.
          COMMIT WORK.
        ENDIF.

Okay, we have a PDF attached to FBL1N.

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certifications, SAP ABAP Learning

No comments:

Post a Comment