Monday, 2 January 2023

Automate printing of PDF documents received as email attachments in SAP

In this blog, we will see how we can enable auto print functionality for PDF documents on dedicated physical printer or on local printer (LP01) in spool.

Additional Business requirement : All the PDF documents (if there are multiple attachments in e-mail) should get generated as one PDF in spool and in case of two-sided printing, one documents shouldn’t get printed on back side of another one.

As per previous article, we have already implemented the two methods of Interface IF_INBOUND_EXIT_BCS in inbound Exit(Z-class : ZCL_BILLING_EMAIL_IN).

Method : IF_INBOUND_EXIT_BCS~CREATE_INSTANCE (Same as previous article)

*Create instance of the object with the class itself

    DATA(lo_ref_instance) = NEW zcl_billing_email_in( ).
    IF lo_ref_instance IS BOUND.
      ro_ref = lo_ref_instance.
    ENDIF.

Method : IF_INBOUND_EXIT_BCS~PROCESS_INBOUND

We need to write code in 2nd method of the interface to implement the printing functionality as per below code snippet with following steps.

1. Get the PDF documents (hex format) from the e-mail object
2. Convert hex format into XSTRING format
3. Get total no. of pages in each PDF document and identify if total no of pages are odd : Method – GET_TOTAL_PAGES.
4. Add additional blank page in PDF document if total no of pages are odd to avoid any issue in case of two sided printing. (Additional requirement) : Method – GET_BLANK_PDF_PAGE
5. Merge all the XSTRING data
6. Print PDF document on printer : Method – PRINT_PDF_DOC

    CONSTANTS: lc_pdf TYPE char3 VALUE 'PDF'.        "Document Type PDF

    DATA: lv_printer      TYPE rspopshort,                "Spool: short name for output device
          lv_blank_page   TYPE xstring,
          lv_total_pages  TYPE i.

    CLEAR: e_retcode, es_t100msg.

lv_printer = 'LP01'. "Or any physical printer in SAP but with short name

*Extract Attachment from e-mail
    TRY .
        IF io_sreq IS BOUND.
* Get document
          DATA(lo_ref_document) = io_sreq->get_document( ).
          IF lo_ref_document IS BOUND.

* Find all the PDF attachments and process it.
              TRY.
* Create PDF Merge Object to be printed in spool as one document
                    TRY.
                        DATA(lo_ref_pdf_merger) = NEW cl_rspo_pdf_merge( ).
                      CATCH cx_rspo_pdf_merge INTO DATA(lv_merge_ex).
                    ENDTRY.

                    DO lo_ref_document->get_body_part_count( ) TIMES.
                      TRY.
                          DATA(lv_doc_type) = lo_ref_document->get_body_part_attributes( im_part = sy-index )-doc_type.
                          TRANSLATE lv_doc_type TO UPPER CASE.

* If Document type is PDF then only proceed as program needs to look into PDF attachments only
                          IF lv_doc_type = lc_pdf.
* Get the file name of attached document
                            DATA(lv_filename) = lo_ref_document->get_body_part_attributes( im_part = sy-index  )-filename.

* Get the content of attached file in hex format.
                            DATA(ls_body_part_content) = lo_ref_document->get_body_part_content( sy-index ).

                            IF lv_filename IS NOT INITIAL AND ls_body_part_content IS NOT INITIAL.

      TRY.
*Convert Hex data into Xstring
                        CALL METHOD cl_bcs_convert=>xtab_to_xstring
                           EXPORTING
                              it_xtab    = iv_body_part_content-cont_hex
                           RECEIVING
                              rv_xstring = DATA(lv_xstring).
                      CATCH cx_bcs .
                         continue.
                      ENDTRY.

                              IF lv_xstring IS NOT INITIAL.
                                IF lo_ref_pdf_merger IS BOUND.
                                  lo_ref_pdf_merger->add_document( lv_xstring ).
                                ENDIF.

* Get total no of pages in PDF document
                                CALL METHOD me->get_total_pages
                                  EXPORTING
                                    iv_content   = lv_xstring
                                  IMPORTING
                                    ev_pages     = DATA(lv_pages)
                                    ev_odd_pages = DATA(lv_odd_flag).

                                lv_total_pages = lv_total_pages + lv_pages.
                                IF lv_odd_flag EQ abap_true.
* If there is odd number of pages, then add one blank PDF page at the end to avoid back to back printing
                                  IF lv_blank_page IS INITIAL.
                                    CALL METHOD me->get_blank_pdf_page
                                      IMPORTING
                                        ev_blank_content = lv_blank_page.
                                  ENDIF.
                                  IF lv_blank_page IS NOT INITIAL.
* Merge all PDF documents in one PDF
                                    lv_total_pages = lv_total_pages + 1.
                                    lo_ref_pdf_merger->add_document( lv_blank_page ).
                                  ENDIF.
                                ENDIF.
                              ENDIF.
                            ENDIF.
                          ENDIF.
                        CATCH cx_document_bcs.
                          CONTINUE.
                      ENDTRY.
                      CLEAR: lv_xstring,ls_body_part_content,
                             lv_doc_type, lv_filename.
                    ENDDO.

* Merge all the PDF documents (in XSTRING format)
                      IF lo_ref_pdf_merger IS BOUND.
                        lo_ref_pdf_merger->merge_documents(
                                        IMPORTING
                                           merged_document = DATA(lv_pdf_all)
                                           rc              = DATA(lv_rc) ).
                        IF lv_rc NE 0.
                          lo_ref_pdf_merger->get_err_doc_index(
                                        IMPORTING
                                          index = DATA(lv_docindex) ).
                          MESSAGE e895(po) WITH lv_docindex INTO DATA(lv_errortext).
                        ENDIF.
                      ENDIF.

* Print the PDF document on dedicated printer 
                          CALL METHOD me->print_pdf_doc
                            EXPORTING
                              iv_content = lv_pdf_all
                              iv_dest    = lv_printer
                              iv_pages   = lv_total_pages.
                  ENDIF.
                CATCH cx_abap_error_analyze.
                  e_retcode  = if_inbound_exit_bcs=>gc_continue.
              ENDTRY.
          ENDIF.
        ENDIF.
      CATCH cx_os_object_not_found.
        e_retcode  = if_inbound_exit_bcs=>gc_continue.
    ENDTRY.

For point 3: Method- GET_TOTAL_PAGES, code will check if total no of pages are odd or not (by using MOD function on integer ev_pages)

    DATA: lt_result TYPE match_result_tab.

* Convert XSTRING to STRING format
    CLEAR: ev_odd_pages, ev_pages.
    cl_bcs_convert=>xstring_to_string(
        EXPORTING
          iv_xstr   = iv_content
          iv_cp     =  1100                " SAP character set identification
        RECEIVING
          rv_string = DATA(lv_cont) ).

* Get total no of pages in PDF document
      FIND REGEX `/N (.{1,5})/` IN lv_cont IGNORING CASE RESULTS lt_result.
      IF sy-subrc NE 0.
        FIND ALL OCCURRENCES OF REGEX `/count (.{1,4})/` IN lv_cont IGNORING CASE RESULTS lt_result.
      ENDIF.

      DATA(lv_lines) = lines( lt_result ).
      IF lv_lines IS NOT INITIAL.
        READ TABLE lt_result ASSIGNING FIELD-SYMBOL(<fs_result>) INDEX lv_lines.
        IF sy-subrc EQ 0.
          READ TABLE <fs_result>-submatches ASSIGNING FIELD-SYMBOL(<fs_subm>) INDEX 1.
          IF sy-subrc EQ 0.
            DATA(lv_temp) = lv_cont+<fs_subm>-offset(<fs_subm>-length).
            CONDENSE lv_temp NO-GAPS.

            DATA(lv_pages) = CONV numc5( |{ lv_temp ALPHA = OUT }| ).
            ev_pages = CONV i( lv_pages ).
            IF ev_pages MOD 2 NE 0.
              ev_odd_pages = abap_true.
            ENDIF.
          ENDIF.
        ENDIF.
      ENDIF.

For point 4: Method – GET_BLANK_PDF_PAGE

If we have total no of pages odd, then add additional blank PDF page. We can store XSTRING data for blank PDF page in SO10 standard text and read it from there with function module READ_TEXT.

    DATA: lt_lines TYPE STANDARD TABLE OF tline,
          lv_data  TYPE string.

    CLEAR: ev_blank_content,
           lt_lines.

    CALL FUNCTION 'READ_TEXT'
      EXPORTING
        client                  = sy-mandt
        id                      = 'ST'
        language                = 'E'
        name                    = 'ZBLANK_PDF_XSTRING'
        object                  = 'TEXT'
      TABLES
        lines                   = lt_lines
      EXCEPTIONS
        id                      = 1
        language                = 2
        name                    = 3
        not_found               = 4
        object                  = 5
        reference_check         = 6
        wrong_access_to_archive = 7
        OTHERS                  = 8.
    IF sy-subrc EQ 0.
      LOOP AT lt_lines ASSIGNING FIELD-SYMBOL(<fs_lines>).
        CONCATENATE lv_data <fs_lines>-tdline INTO lv_data.
      ENDLOOP.
      IF lv_data IS NOT INITIAL.
        ev_blank_content = CONV xstring( lv_data ).
      ENDIF.
    ENDIF.
 
To print the PDF document, we need to open ADS spool request and write in there as per below code logic.

Method – PRINT_PDF_DOC

DATA: lv_handle    TYPE sy-tabix,
          lv_partname  TYPE adspart,
          lv_globaldir TYPE text1024,
          lv_dstfile   TYPE text1024,
          lv_filesize  TYPE i.
* Open an ADS spool request for writing the attachment file for printing
    CALL FUNCTION 'ADS_SR_OPEN'
      EXPORTING
        dest             = iv_dest
        doctype          = 'ADSP'
        copies           = 1
        immediate_print  = abap_true
      IMPORTING
        handle           = lv_handle
        partname         = lv_partname
      EXCEPTIONS
        device_missing   = 1
        no_such_device   = 2
        operation_failed = 3
        wrong_doctype    = 4
        wrong_devicetype = 5
        OTHERS           = 6.
* If there is any error then set the variable lv_print_err_flag as X
    IF sy-subrc NE 0.
      DATA(lv_print_err_flag) = abap_true.
    ELSE.
* Provides storage location for ADS spool requests
      CALL FUNCTION 'ADS_GET_PATH'
        IMPORTING
          ads_path = lv_globaldir.
* Prepare the filepath to write the content
      CONCATENATE lv_globaldir '/' lv_partname '.pdf' INTO lv_dstfile.
* Open the filepath and transfer the PDF content in Spool
      TRY.
          OPEN DATASET lv_dstfile FOR OUTPUT IN BINARY MODE.
          IF sy-subrc NE 0.
            lv_print_err_flag = abap_true.
          ELSE.
            TRANSFER iv_content TO lv_dstfile.
            IF sy-subrc NE 0.
              lv_print_err_flag = abap_true.
            ELSE.
* Close the file when writing is completed
              CLOSE DATASET lv_dstfile.
              IF sy-subrc NE 0.
                lv_print_err_flag = abap_true.

              ELSE.

                lv_filesize = xstrlen( iv_content ).
*Writing all lines in an open ADS spool request
                CALL FUNCTION 'ADS_SR_CONFIRM'
                  EXPORTING
                    handle           = lv_handle
                    partname         = lv_partname
                    size             = lv_filesize
                    pages            = iv_pages
                  EXCEPTIONS
                    handle_not_valid = 1
                    operation_failed = 2
                    OTHERS           = 3.
                IF sy-subrc NE 0.
                  lv_print_err_flag = abap_true.
                ELSE.
*Closing an ADS spool request
                  CALL FUNCTION 'ADS_SR_CLOSE'
                    EXPORTING
                      handle           = lv_handle
                    EXCEPTIONS
                      handle_not_valid = 1
                      operation_failed = 2
                      OTHERS           = 3.
                  IF sy-subrc NE 0.
                    lv_print_err_flag = abap_true.
                  ENDIF.
                ENDIF.
              ENDIF.
            ENDIF.
          ENDIF.
        CATCH cx_sy_file_open
              cx_sy_codepage_converter_init
              cx_sy_conversion_codepage
              cx_sy_file_authority
              cx_sy_pipes_not_supported
              cx_sy_too_many_files
              cx_sy_file_close
              cx_sy_file_io
              cx_sy_file_open_mode
              cx_sy_pipe_reopen.
          lv_print_err_flag = abap_true.
      ENDTRY.
    ENDIF.

* If there is any error in printing then raise exception
    IF lv_print_err_flag = abap_true.
      RAISE EXCEPTION TYPE cx_abap_error_analyze
        EXPORTING
          message = CONV string( TEXT-001 )
          kind    = 'E'.
    ENDIF.

Let’s execute the functionality after triggering the e-mail which has three separate PDF documents.

SAP ABAP Certification, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Jobs, SAP ABAP Prep, SAP ABAP Preparation

Now execute T-code SP01 with selection screen value Created By: MAILADM (or similar system ID which process the incoming email in SAP).

SAP ABAP Certification, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Jobs, SAP ABAP Prep, SAP ABAP Preparation

Note: Here we have addressed additional business functionality (one PDF document in spool and adding blank PDF page, if no of pages in document is odd). These are not required to complete the auto-print functionality and code can be suppressed accordingly.

No comments:

Post a Comment