Friday 4 December 2020

QR code in smartforms

Most of the time in SAP the answer for many things come from a lot of sources. The QR code implementation in SAP is an example of it.

But then I realized that even if I use that code I had to deal with the fact that I should be aware that the QR code had to be generated from a Google API. That could be a problem because in my case I needed to use that feature internally, so it was not ok to access the internet to get the image. On the other hand, this site could be at any moment be closed or shutdown.

So, the thing is that I had to make my own webservice to get that image, and having a background of C# I found it logical to make my own webservice. So I began to investigate how to generate the QR and when I got it then I proceeded to code it. There are a lot of open source solutions out there and in my case, I just had to grab one of them and use it. I found a couple and the ZXing.Net implementation seemed to be the best in this case.

So I had to trick it a little so I could make it to work. One requirement of this webservice was that it had to make the QR code graphic on the fly. It was not ok to save a file representing the QR code in the webserver because eventually, it would have to take some kind of job or additional work on it to delete those files. The code for the webservice is the following:

using System.Drawing.Imaging;

using System.IO;

using System.Web;

using ZXing;

using ZXing.Common;

namespace lcqrcode

{

    /// <summary>

    /// Short description of Service

    /// </summary>

    public class Service : IHttpHandler

    {

        private const string NotAvailable = "Not available";

        private const int MaxLength = 256;


        public void ProcessRequest(HttpContext context)

        {

            if (context.Request.HttpMethod != "GET") return;


            var qrValue = context.Request.QueryString["text"] ?? NotAvailable;


            qrValue = qrValue.Trim();


            if (qrValue.Length > MaxLength)

                qrValue = qrValue.Substring(0, MaxLength - 1); // Limit to 'MaxLength' characters


            var barcodeWriter = new BarcodeWriter

                                    {

                                        Format = BarcodeFormat.QR_CODE,

                                        Options = new EncodingOptions

                                                      {

                                                          Height = 200,

                                                          Width = 200,

                                                          PureBarcode = true

                                                      }

                                    };


            using (var bitmap = barcodeWriter.Write(qrValue))


            using (var mem = new MemoryStream())

            {

                bitmap.SetResolution(50, 50);


                bitmap.Save(mem, ImageFormat.Png);

                mem.Seek(0, SeekOrigin.Begin);


                context.Response.ContentType = "image/png";


                mem.CopyTo(context.Response.OutputStream, 4096);

                context.Response.Flush();

            }

        }


        public bool IsReusable

        {

            get

            {

                return false;

            }

        }

    }

}

Create a web project and insert a generic handler file (ASHX extension) in Visual Studio, name it ‘Service’, and paste the above code. Don’t forget to include de ZXING reference in the project. I had to make use of this kind of file because I had to make the webservice restful.

The other step that I took was to make the ABAP code to easily use it in any smartform and the best way to do that was to make a BAPI. This approach would help to make use of it anytime anywhere.

So I took the code from above and encapsulated it in a BAPI. Here is the code

function zqrcode .

*"----------------------------------------------------------------------

*"*"Interfase local

*"  IMPORTING

*"     REFERENCE(I_TEXT) TYPE  CHAR255

*"     REFERENCE(I_NAME) TYPE  CHAR20

*"     REFERENCE(I_WIDHT) TYPE  I DEFAULT 200

*"     REFERENCE(I_HEIGHT) TYPE  I DEFAULT 200

*"  EXPORTING

*"     REFERENCE(E_CHECK) TYPE  CHAR01

*"----------------------------------------------------------------------

  perform get_qrcode using i_text changing e_check.

  check e_check is not initial.

  perform convert_image using i_name i_widht i_height changing e_check.

endfunction.

And here is the code for the Include of the BAPI

*&---------------------------------------------------------------------*

*&      Form  GET_QRCODE

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

*      -->P_TEXT  text

*      -->P_CHECK text

*----------------------------------------------------------------------*

form get_qrcode using p_text type char255 changing p_check type c.

  data oref type ref to cx_root.

  data wa_source type zgen_qrsource.

  select single * from zgen_qrsource into wa_source. " Get webservice address for QR code generation

  check sy-subrc eq 0.

  concatenate wa_source-address p_text into url.

  call method cl_http_client=>create_by_url

    exporting

      url                = url

    importing

      client             = http_client

    exceptions

      argument_not_found = 1

      plugin_not_active  = 2

      internal_error     = 3

      others             = 4.

  if sy-subrc = 0.

*    http_client->send( ).

    call method http_client->send

    exceptions

      http_communication_failure = 1

      http_invalid_state = 2

      http_processing_failed = 3

      http_invalid_timeout = 4.


*    http_client->receive( ).

    call method http_client->receive

    exceptions

      http_communication_failure = 1

      http_invalid_state         = 2

      http_processing_failed     = 3

      others                     = 4.

    check sy-subrc eq 0.

    content = http_client->response->get_data( ).

    http_client->close( ).

    l_str_length = xstrlen( content ).

    call function 'RSFO_XSTRING_TO_MIME'

      exporting

        c_xstring = content

        i_length  = l_str_length

      tables

        c_t_mime  = mime.

    p_check = 'X'.

  endif.

endform.                    " download_qrcode

*&---------------------------------------------------------------------*

*&      Form  convert_image

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

*      -->P_NAME     text

*      -->P_HEIGHT   text

*      -->P_WIDTH    text

*      -->P_CHECK    text

*----------------------------------------------------------------------*

form convert_image using p_name type char20 p_height type i p_width type i changing p_check type c.

  create object i_igs_image_converter .

  i_igs_image_converter->input = 'image/png'.

  i_igs_image_converter->output = 'image/bmp'.

  i_igs_image_converter->width = p_width.

  i_igs_image_converter->height = p_height.

  call method i_igs_image_converter->set_image

    exporting

      blob      = mime

      blob_size = l_content_length.

  call method i_igs_image_converter->execute

    exceptions

      communication_error = 1

      internal_error      = 2

      external_error      = 3

      others              = 4.

  if sy-subrc = 0.

    call method i_igs_image_converter->get_image

      importing

        blob      = blob

        blob_size = blob_size

        blob_type = blob_type.

    p_check = 'X'.

  else.

    clear p_check.

  endif.

  if sy-subrc = 0.

    perform generate_picture using p_name changing p_check.

  endif.

endform.                    " CONVERT_IMAGE

*&---------------------------------------------------------------------*

*&      Form  generate_picture

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

*      -->P_NAME     text

*      -->P_CHECK    text

*----------------------------------------------------------------------*

form generate_picture using p_name type char20 changing p_check type c.

  gi_name = p_name.

  gi_object = 'GRAPHICS'.

  gi_id = 'BMAP'.

  gi_btype = 'BCOL'. " If u want black and white pass BMON

  gi_resident = ' '.

  gi_autoheight =  'X'.

  gi_bmcomp = 'X'.

  l_extension = 'BMP'.

  "importing the image into se78 before displaying it in the smartform.

  perform import_bitmap_bds    using blob

                                   gi_name

                                   gi_object

                                   gi_id

                                   gi_btype

                                   l_extension

                                   ' '

                                   gi_resident

                                   gi_autoheight

                                   gi_bmcomp

                          changing l_docid

                                   gi_resolution.


  if sy-subrc = 0.

    p_check = 'X'.

  else.

    clear p_check.

  endif.

endform.                    " SHOW_SMART_FORM


*&---------------------------------------------------------------------*

*&      Form  import_bitmap_bds

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

*      -->P_BLOB        text

*      -->P_NAME        text

*      -->P_OBJECT      text

*      -->P_ID          text

*      -->P_BTYPE       text

*      -->P_FORMAT      text

*      -->P_TITLE       text

*      -->P_RESIDENT    text

*      -->P_AUTOHEIGHT  text

*      -->P_BMCOMP      text

*      -->P_DOCID       text

*      -->P_RESOLUTION  text

*----------------------------------------------------------------------*

form import_bitmap_bds

        using    p_blob       type w3mimetabtype

                 p_name           type stxbitmaps-tdname

                 p_object         type stxbitmaps-tdobject

                 p_id             type stxbitmaps-tdid

                 p_btype          type stxbitmaps-tdbtype

                 p_format         type c

                 p_title          like bds_description

                 p_resident       type stxbitmaps-resident

                 p_autoheight     type stxbitmaps-autoheight

                 p_bmcomp         type stxbitmaps-bmcomp

        changing p_docid          type stxbitmaps-docid

                 p_resolution     type stxbitmaps-resolution.

  data: l_object_key type sbdst_object_key.

  data: l_tab        type ddobjname.

  data: begin of l_bitmap occurs 0,

          l(64) type x,

        end of l_bitmap.

  data: l_filename        type string,

        l_bytecount       type i,

        l_bds_bytecount   type i.

  data: l_color(1)        type c,

        l_width_tw        type stxbitmaps-widthtw,

        l_height_tw       type stxbitmaps-heighttw,

        l_width_pix       type stxbitmaps-widthpix,

        l_height_pix      type stxbitmaps-heightpix.

  data: l_bds_object      type ref to cl_bds_document_set,

        l_bds_content     type sbdst_content,

        l_bds_components  type sbdst_components,

        wa_bds_components type line of sbdst_components,

        l_bds_signature   type sbdst_signature,

        wa_bds_signature  type line of sbdst_signature,

        l_bds_properties  type sbdst_properties,

        wa_bds_properties type line of sbdst_properties.

  data  wa_stxbitmaps type stxbitmaps.

* Enqueue

  perform enqueue_graphic using p_object

                                p_name

                                p_id

                                p_btype.

* Bitmap conversion

  call function 'SAPSCRIPT_CONVERT_BITMAP_BDS'

    exporting

      color                    = 'X'

      format                   = p_format

      resident                 = p_resident

      bitmap_bytecount         = l_bytecount

      compress_bitmap          = p_bmcomp

    importing

      width_tw                 = l_width_tw

      height_tw                = l_height_tw

      width_pix                = l_width_pix

      height_pix               = l_height_pix

      dpi                      = p_resolution

      bds_bytecount            = l_bds_bytecount

    tables

      bitmap_file              = p_blob

      bitmap_file_bds          = l_bds_content

    exceptions

      format_not_supported     = 1

      no_bmp_file              = 2

      bmperr_invalid_format    = 3

      bmperr_no_colortable     = 4

      bmperr_unsup_compression = 5

      bmperr_corrupt_rle_data  = 6

      others                   = 7.

  if sy-subrc <> 0.

    perform dequeue_graphic using p_object

                                  p_name

                                  p_id

                                  p_btype.

    message id sy-msgid type sy-msgty number sy-msgno

            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4

    raising conversion_failed.

  endif.

* Save bitmap in BDS

  create object l_bds_object.

  wa_bds_components-doc_count  = '1'.

  wa_bds_components-comp_count = '1'.

  wa_bds_components-mimetype   = c_bds_mimetype.

  wa_bds_components-comp_size  = l_bds_bytecount.

  append wa_bds_components to l_bds_components.

  if p_docid is initial.          " graphic is new

    wa_bds_signature-doc_count = '1'.

    append wa_bds_signature to l_bds_signature.

    call method l_bds_object->create_with_table

      exporting

        classname  = c_bds_classname

        classtype  = c_bds_classtype

        components = l_bds_components

        content    = l_bds_content

      changing

        signature  = l_bds_signature

        object_key = l_object_key

      exceptions

        others     = 1.

    if sy-subrc <> 0.

      perform dequeue_graphic using p_object

                                    p_name

                                    p_id

                                    p_btype.

*      message e285 with p_name  'BDS'.

    endif.

    read table l_bds_signature index 1 into wa_bds_signature

    transporting doc_id.

    if sy-subrc = 0.

      p_docid = wa_bds_signature-doc_id.

    else.

      perform dequeue_graphic using p_object

                                    p_name

                                    p_id

                                    p_btype.

*      message e285 with p_name 'BDS'.

    endif.

  else.                " graphic already exists

********* read object_key for faster access *****

    clear l_object_key.

    select single * from stxbitmaps into wa_stxbitmaps

        where tdobject = p_object

          and tdid     = p_id

          and tdname   = p_name

          and tdbtype  = p_btype.

    select single tabname from bds_locl into l_tab

       where classname = c_bds_classname

          and classtype = c_bds_classtype.

    if sy-subrc = 0.

      select single object_key from (l_tab) into l_object_key

        where loio_id = wa_stxbitmaps-docid+10(32)

          and classname = c_bds_classname

            and classtype = c_bds_classtype.

    endif.

******** read object_key end ********************

    call method l_bds_object->update_with_table

      exporting

        classname     = c_bds_classname

        classtype     = c_bds_classtype

        object_key    = l_object_key

        doc_id        = p_docid

        doc_ver_no    = '1'

        doc_var_id    = '1'

      changing

        components    = l_bds_components

        content       = l_bds_content

      exceptions

        nothing_found = 1

        others        = 2.

    if sy-subrc = 1.   " inconsistency STXBITMAPS - BDS; repeat check in

      wa_bds_signature-doc_count = '1'.

      append wa_bds_signature to l_bds_signature.

      call method l_bds_object->create_with_table

        exporting

          classname  = c_bds_classname

          classtype  = c_bds_classtype

          components = l_bds_components

          content    = l_bds_content

        changing

          signature  = l_bds_signature

          object_key = l_object_key

        exceptions

          others     = 1.

      if sy-subrc <> 0.

        perform dequeue_graphic using p_object

                                      p_name

                                      p_id

                                      p_btype.

*        message e285 with p_name 'BDS'.

      endif.

      read table l_bds_signature index 1 into wa_bds_signature

      transporting doc_id.

      if sy-subrc = 0.

        p_docid = wa_bds_signature-doc_id.

      else.

        perform dequeue_graphic using p_object

                                      p_name

                                      p_id

                                      p_btype.

*        message e285 with p_name 'BDS'.

      endif.

    elseif sy-subrc = 2.

      perform dequeue_graphic using p_object

                                    p_name

                                    p_id

                                    p_btype.


*      message e285 with p_name 'BDS'.

    endif.

  endif.

* Save bitmap header in STXBITPMAPS

  wa_stxbitmaps-tdname     = p_name.

  wa_stxbitmaps-tdobject   = p_object.

  wa_stxbitmaps-tdid       = p_id.

  wa_stxbitmaps-tdbtype    = p_btype.

  wa_stxbitmaps-docid      = p_docid.

  wa_stxbitmaps-widthpix   = l_width_pix.

  wa_stxbitmaps-heightpix  = l_height_pix.

  wa_stxbitmaps-widthtw    = l_width_tw.

  wa_stxbitmaps-heighttw   = l_height_tw.

  wa_stxbitmaps-resolution = p_resolution.

  wa_stxbitmaps-resident   = p_resident.

  wa_stxbitmaps-autoheight = p_autoheight.

  wa_stxbitmaps-bmcomp     = p_bmcomp.

  insert into stxbitmaps values wa_stxbitmaps.

  if sy-subrc <> 0.

    update stxbitmaps from wa_stxbitmaps.

    if sy-subrc <> 0.

*       message e285 with p_name 'STXBITMAPS'.

    endif.

  endif.

* Set description in BDS attributes

  wa_bds_properties-prop_name  = 'DESCRIPTION'.

  wa_bds_properties-prop_value = p_title.

  append wa_bds_properties to l_bds_properties.

  call method l_bds_object->change_properties

    exporting

      classname  = c_bds_classname

      classtype  = c_bds_classtype

      object_key = l_object_key

      doc_id     = p_docid

      doc_ver_no = '1'

      doc_var_id = '1'

    changing

      properties = l_bds_properties

    exceptions

      others     = 1.

  perform dequeue_graphic using p_object

                                p_name

                                p_id

                                p_btype.

endform.                    "import_bitmap_bds

*&---------------------------------------------------------------------*

*&      Form  ENQUEUE_GRAPHIC

*&---------------------------------------------------------------------*

* Enqueue of graphics stored in BDS

*----------------------------------------------------------------------*

form enqueue_graphic using p_object

                           p_name

                           p_id

                           p_btype.

  call function 'ENQUEUE_ESSGRABDS'

       exporting

*           MODE_STXBITMAPS = 'E'

            tdobject        = p_object

            tdname          = p_name

            tdid            = p_id

            tdbtype         = p_btype

*           X_TDOBJECT      = ' '

*           X_TDNAME        = ' '

*           X_TDID          = ' '

*           X_TDBTYPE       = ' '

*           _SCOPE          = '2'

*           _WAIT           = ' '

*           _COLLECT        = ' '

       exceptions

            foreign_lock    = 1

            others          = 2.

  if sy-subrc <> 0.

    message id sy-msgid type sy-msgty number sy-msgno

          with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4

    raising enqueue_failed.

  endif.

endform.                    " ENQUEUE_GRAPHIC

*&---------------------------------------------------------------------*

*&      Form  DEQUEUE_GRAPHIC

*&---------------------------------------------------------------------*

* Dequeue of graphics stored in BDS

*----------------------------------------------------------------------*

form dequeue_graphic using p_object

                           p_name

                           p_id

                           p_btype.

  call function 'DEQUEUE_ESSGRABDS'

       exporting

*           MODE_STXBITMAPS = 'E'

*           X_TDOBJECT      = ' '

*           X_TDNAME        = ' '

*           X_TDID          = ' '

*           X_TDBTYPE       = ' '

*           _SCOPE          = '3'

*           _SYNCHRON       = ' '

*           _COLLECT        = ' '

            tdobject        = p_object

            tdname          = p_name

            tdid            = p_id

            tdbtype         = p_btype.

endform.                    " DEQUEUE_GRAPHIC

Next, we have to make the output on a smartform and it’s very simple. Just take the BAPI and use it inside the smartform and to avoid that file names collide, use the ‘GUID_CREATE’ function module to generate different names.

This function module has 3 importing parameters which output 3 kinds of GUIDs. In my case, I used the ‘EV_GUID_16’ one. The base of this strategy is that there will be no chance to have the same file name per request.

data: lv_text type char255, " Variable for text to convert in QR code

      lv_check type c,      " Flag to check if QR code was generated

      lv_name type char20.  " Variable for file name

data lv_guid type guid_16. " Variable for GUID

call function 'GUID_CREATE'

 importing

   ev_guid_16       = lv_guid.

move lv_guid to lv_name.

lv_text = 'Demo for QR code'.

call function 'ZQRCODE'

  exporting

    i_text        = lv_text " Text to convert

    i_name        = lv_name " Graphic name

  importing

    e_check       = lv_check. " 'X' = success in conversion

if lv_check is not initial.

  g_name = lv_name.

  display = 'X'. " Yes, display the image

endif.

The steps in SAP for the smartform are:

1. Execute transaction ‘smartforms’.

2. Create a smartform

QR code in smartforms

3. Define global variables

QR code in smartforms

4. Add ‘Program Lines’

QR code in smartforms

5. Paste the above code into ‘%CODE1’

QR code in smartforms

6. Insert a graphic object and in ‘General Attributes’ table type ‘&G_NAME& in Name, ‘GRAPHICS’ in Object and ‘BMAP’ in ID properties.

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Exam Prep

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Exam Prep

7. Finally in ‘Conditions’ tab add the variable ‘DISPLAY’ with comparison value = ‘X’. This variable is defined in step 3.

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Exam Prep

Just consider that in this case the images are stored in SAP. You can check those files in transaction ‘SE78’.

The only step left was to make the webservice configurable, which meant that I had to keep in a table in SAP the webservice address. This strategy is good because it provides a flexible way to maintain the webservice in any server.

You can check that in the form ‘DOWNLOAD_QRCODE’ in the include file of the BAPI:

form download_qrcode using p_text type char255 changing p_check type c.
  data oref type ref to cx_root.
  data wa_source type zyour_table.
  select single * from zyour_table into wa_source. " Get webservice address for QR code generation

  check sy-subrc eq 0.

  concatenate wa_source-address p_text into url.

  call method cl_http_client=>create_by_url
    EXPORTING
      url                = url
    IMPORTING
      client             = http_client
    EXCEPTIONS
      argument_not_found = 1
      plugin_not_active  = 2
      internal_error     = 3
      others             = 4.

  if sy-subrc = 0.

This code will allow us to indicate the web address of the webservice and change it anytime I want.

Check the output of the smartform pressing 3 times the F8 key and then clicking on ‘Print preview’ button in the Print dialog:

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Exam Prep

Smartform can be very tricky but they are very handy and powerful. A lot of transactions in SAP ECC use these reports and you can define your own in transaction NACE, they come with all the information and information that are needed to adapt them according to the client requirements.

And the other hand you can make your own smartforms and use them in your own programs with the same tools in SAP.

No comments:

Post a Comment