PDF-Generierung in ABAP Cloud unterscheidet sich grundlegend von On-Premise. Klassische Adobe Forms (SFP) und SmartForms stehen auf SAP BTP nicht zur Verfuegung. Stattdessen nutzt du Cloud-native Alternativen wie XSL-FO, den SAP Forms Service by Adobe oder externe PDF-Services.
Verfuegbare Optionen im Ueberblick
In ABAP Cloud gibt es mehrere Wege, PDFs zu generieren:
| Option | Beschreibung | Anwendungsfall |
|---|---|---|
| XSL-FO | XML-basierte Transformation zu PDF | Einfache bis mittlere Dokumente |
| SAP Forms Service by Adobe | Cloud-basierter Adobe Forms Dienst | Komplexe Formulare mit Adobe-Templates |
| Externe PDF-Services | HTTP-basierte PDF-APIs | Flexibilitaet, spezielle Anforderungen |
| SAP Document Management | Integration mit DMS | Dokumenten-Workflows |
Architektur-Uebersicht
┌─────────────────────────────────────────────────────────────────────────────┐│ PDF-Generierung in ABAP Cloud ││ ││ ┌────────────────────────────────────────────────────────────────────────┐ ││ │ RAP Business Object / Service │ ││ │ │ ││ │ ┌─────────────────────┐ │ ││ │ │ RAP Action │ │ ││ │ │ "Generate PDF" │ │ ││ │ └──────────┬──────────┘ │ ││ └─────────────┼────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ PDF-Generierungs-Optionen │ ││ │ │ ││ │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ ││ │ │ XSL-FO │ │ SAP Forms │ │ Externe APIs │ │ ││ │ │ │ │ Service │ │ (PDF-Services) │ │ ││ │ │ - XML + XSLT │ │ - Adobe Templates│ │ - REST APIs │ │ ││ │ │ - cl_xslt_proc │ │ - BTP Service │ │ - HTTP Client │ │ ││ │ └───────────────────┘ └───────────────────┘ └───────────────────┘ │ ││ └─────────────────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ PDF Output │ ││ │ - Download via RAP Action Result │ ││ │ - Attachment an Business Object │ ││ │ - Email-Versand │ ││ └─────────────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘XSL-FO: XML-basierte PDF-Generierung
XSL-FO (Extensible Stylesheet Language Formatting Objects) ist eine W3C-Standardtechnik fuer die Transformation von XML zu paginierten Dokumenten wie PDF.
Grundprinzip
┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ XML-Daten │ + │ XSL-FO │ = │ PDF ││ │ │ Stylesheet │ │ │└──────────────┘ └──────────────┘ └──────────────┘XML-Datenstruktur
Zunaechst definierst du eine XML-Struktur fuer deine Dokumentdaten:
" XML-Daten fuer Rechnung generierenCLASS zcl_invoice_xml_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_invoice_item, position TYPE i, description TYPE string, quantity TYPE i, unit_price TYPE p LENGTH 10 DECIMALS 2, total TYPE p LENGTH 10 DECIMALS 2, END OF ty_invoice_item, ty_invoice_items TYPE STANDARD TABLE OF ty_invoice_item WITH KEY position.
TYPES: BEGIN OF ty_invoice, invoice_number TYPE string, invoice_date TYPE d, customer_name TYPE string, customer_addr TYPE string, items TYPE ty_invoice_items, subtotal TYPE p LENGTH 10 DECIMALS 2, tax TYPE p LENGTH 10 DECIMALS 2, total TYPE p LENGTH 10 DECIMALS 2, END OF ty_invoice.
METHODS: generate_xml IMPORTING is_invoice TYPE ty_invoice RETURNING VALUE(rv_xml) TYPE xstring.
ENDCLASS.
CLASS zcl_invoice_xml_generator IMPLEMENTATION.
METHOD generate_xml. " XML-Dokument aufbauen DATA(lo_doc) = cl_ixml=>create( )->create_document( ).
" Root-Element DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Header-Elemente DATA(lo_header) = lo_doc->create_element( name = 'Header' ). lo_root->append_child( lo_header ).
DATA(lo_inv_no) = lo_doc->create_element( name = 'InvoiceNumber' ). lo_inv_no->set_value( is_invoice-invoice_number ). lo_header->append_child( lo_inv_no ).
DATA(lo_date) = lo_doc->create_element( name = 'InvoiceDate' ). lo_date->set_value( |{ is_invoice-invoice_date DATE = USER }| ). lo_header->append_child( lo_date ).
" Kundendaten DATA(lo_customer) = lo_doc->create_element( name = 'Customer' ). lo_root->append_child( lo_customer ).
DATA(lo_cust_name) = lo_doc->create_element( name = 'Name' ). lo_cust_name->set_value( is_invoice-customer_name ). lo_customer->append_child( lo_cust_name ).
DATA(lo_cust_addr) = lo_doc->create_element( name = 'Address' ). lo_cust_addr->set_value( is_invoice-customer_addr ). lo_customer->append_child( lo_cust_addr ).
" Positionen DATA(lo_items) = lo_doc->create_element( name = 'Items' ). lo_root->append_child( lo_items ).
LOOP AT is_invoice-items INTO DATA(ls_item). DATA(lo_item) = lo_doc->create_element( name = 'Item' ). lo_items->append_child( lo_item ).
DATA(lo_pos) = lo_doc->create_element( name = 'Position' ). lo_pos->set_value( |{ ls_item-position }| ). lo_item->append_child( lo_pos ).
DATA(lo_desc) = lo_doc->create_element( name = 'Description' ). lo_desc->set_value( ls_item-description ). lo_item->append_child( lo_desc ).
DATA(lo_qty) = lo_doc->create_element( name = 'Quantity' ). lo_qty->set_value( |{ ls_item-quantity }| ). lo_item->append_child( lo_qty ).
DATA(lo_price) = lo_doc->create_element( name = 'UnitPrice' ). lo_price->set_value( |{ ls_item-unit_price }| ). lo_item->append_child( lo_price ).
DATA(lo_total) = lo_doc->create_element( name = 'Total' ). lo_total->set_value( |{ ls_item-total }| ). lo_item->append_child( lo_total ). ENDLOOP.
" Summen DATA(lo_totals) = lo_doc->create_element( name = 'Totals' ). lo_root->append_child( lo_totals ).
DATA(lo_subtotal) = lo_doc->create_element( name = 'Subtotal' ). lo_subtotal->set_value( |{ is_invoice-subtotal }| ). lo_totals->append_child( lo_subtotal ).
DATA(lo_tax) = lo_doc->create_element( name = 'Tax' ). lo_tax->set_value( |{ is_invoice-tax }| ). lo_totals->append_child( lo_tax ).
DATA(lo_grand_total) = lo_doc->create_element( name = 'GrandTotal' ). lo_grand_total->set_value( |{ is_invoice-total }| ). lo_totals->append_child( lo_grand_total ).
" XML als String DATA(lo_stream) = cl_ixml=>create( )->create_stream_factory( )->create_ostream_xstring( rv_xml ). cl_ixml=>create( )->create_renderer( document = lo_doc ostream = lo_stream )->render( ). ENDMETHOD.
ENDCLASS.XSL-FO Stylesheet
Das XSL-FO Stylesheet definiert das Layout deines PDFs:
<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/Invoice"> <fo:root> <!-- Seitenlayout definieren --> <fo:layout-master-set> <fo:simple-page-master master-name="A4" page-height="297mm" page-width="210mm" margin-top="20mm" margin-bottom="20mm" margin-left="25mm" margin-right="25mm"> <fo:region-body margin-top="30mm" margin-bottom="20mm"/> <fo:region-before extent="25mm"/> <fo:region-after extent="15mm"/> </fo:simple-page-master> </fo:layout-master-set>
<fo:page-sequence master-reference="A4"> <!-- Kopfzeile --> <fo:static-content flow-name="xsl-region-before"> <fo:block font-size="18pt" font-weight="bold" text-align="center"> RECHNUNG </fo:block> </fo:static-content>
<!-- Fusszeile --> <fo:static-content flow-name="xsl-region-after"> <fo:block font-size="8pt" text-align="center"> Seite <fo:page-number/> von <fo:page-number-citation ref-id="last-page"/> </fo:block> </fo:static-content>
<!-- Hauptinhalt --> <fo:flow flow-name="xsl-region-body"> <!-- Rechnungskopf --> <fo:block font-size="10pt" margin-bottom="10mm"> <fo:table width="100%"> <fo:table-column column-width="50%"/> <fo:table-column column-width="50%"/> <fo:table-body> <fo:table-row> <fo:table-cell> <fo:block font-weight="bold">Kunde:</fo:block> <fo:block><xsl:value-of select="Customer/Name"/></fo:block> <fo:block><xsl:value-of select="Customer/Address"/></fo:block> </fo:table-cell> <fo:table-cell text-align="right"> <fo:block>Rechnungsnummer: <xsl:value-of select="Header/InvoiceNumber"/></fo:block> <fo:block>Datum: <xsl:value-of select="Header/InvoiceDate"/></fo:block> </fo:table-cell> </fo:table-row> </fo:table-body> </fo:table> </fo:block>
<!-- Positionen --> <fo:block margin-top="10mm"> <fo:table width="100%" border="1pt solid black"> <fo:table-column column-width="10%"/> <fo:table-column column-width="45%"/> <fo:table-column column-width="15%"/> <fo:table-column column-width="15%"/> <fo:table-column column-width="15%"/> <fo:table-header> <fo:table-row background-color="#EEEEEE" font-weight="bold"> <fo:table-cell border="1pt solid black" padding="2mm"> <fo:block>Pos.</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm"> <fo:block>Beschreibung</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Menge</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Einzelpreis</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Gesamt</fo:block> </fo:table-cell> </fo:table-row> </fo:table-header> <fo:table-body> <xsl:for-each select="Items/Item"> <fo:table-row> <fo:table-cell border="1pt solid black" padding="2mm"> <fo:block><xsl:value-of select="Position"/></fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm"> <fo:block><xsl:value-of select="Description"/></fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block><xsl:value-of select="Quantity"/></fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block><xsl:value-of select="UnitPrice"/> EUR</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block><xsl:value-of select="Total"/> EUR</fo:block> </fo:table-cell> </fo:table-row> </xsl:for-each> </fo:table-body> </fo:table> </fo:block>
<!-- Summen --> <fo:block margin-top="5mm" text-align="right"> <fo:table width="40%" margin-left="60%"> <fo:table-column column-width="60%"/> <fo:table-column column-width="40%"/> <fo:table-body> <fo:table-row> <fo:table-cell padding="1mm"><fo:block>Zwischensumme:</fo:block></fo:table-cell> <fo:table-cell padding="1mm" text-align="right"> <fo:block><xsl:value-of select="Totals/Subtotal"/> EUR</fo:block> </fo:table-cell> </fo:table-row> <fo:table-row> <fo:table-cell padding="1mm"><fo:block>MwSt (19%):</fo:block></fo:table-cell> <fo:table-cell padding="1mm" text-align="right"> <fo:block><xsl:value-of select="Totals/Tax"/> EUR</fo:block> </fo:table-cell> </fo:table-row> <fo:table-row font-weight="bold"> <fo:table-cell padding="1mm" border-top="2pt solid black"> <fo:block>Gesamtbetrag:</fo:block> </fo:table-cell> <fo:table-cell padding="1mm" text-align="right" border-top="2pt solid black"> <fo:block><xsl:value-of select="Totals/GrandTotal"/> EUR</fo:block> </fo:table-cell> </fo:table-row> </fo:table-body> </fo:table> </fo:block>
<!-- Letzte Seite markieren fuer Seitenzahlen --> <fo:block id="last-page"/> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template>
</xsl:stylesheet>XSL-FO zu PDF transformieren
" PDF-Generator mit XSL-FOCLASS zcl_pdf_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: generate_pdf IMPORTING iv_xml TYPE xstring iv_xsl_fo TYPE xstring RETURNING VALUE(rv_pdf) TYPE xstring RAISING cx_transformation_error.
METHODS: generate_invoice_pdf IMPORTING is_invoice TYPE zcl_invoice_xml_generator=>ty_invoice RETURNING VALUE(rv_pdf) TYPE xstring RAISING cx_transformation_error.
PRIVATE SECTION. METHODS: get_xsl_fo_template RETURNING VALUE(rv_xsl) TYPE xstring.
ENDCLASS.
CLASS zcl_pdf_generator IMPLEMENTATION.
METHOD generate_pdf. " XSL-FO Transformation " Hinweis: Die direkte PDF-Generierung aus XSL-FO ist in ABAP Cloud " eingeschraenkt. Fuer produktive Szenarien wird empfohlen, " einen externen FOP-Service (Apache FOP) oder SAP Forms Service zu nutzen.
" Alternative: Transformation zu XSL-FO Output DATA lv_xsl_fo_result TYPE xstring.
CALL TRANSFORMATION id SOURCE XML iv_xml RESULT XML lv_xsl_fo_result.
" Der XSL-FO Output muss dann an einen FOP-Prozessor gesendet werden " Dies erfolgt typischerweise ueber einen HTTP-Aufruf an einen externen Service rv_pdf = call_fop_service( lv_xsl_fo_result ). ENDMETHOD.
METHOD generate_invoice_pdf. " Rechnung als PDF generieren DATA(lo_xml_gen) = NEW zcl_invoice_xml_generator( ). DATA(lv_xml) = lo_xml_gen->generate_xml( is_invoice ).
DATA(lv_xsl_fo) = get_xsl_fo_template( ).
rv_pdf = generate_pdf( iv_xml = lv_xml iv_xsl_fo = lv_xsl_fo ). ENDMETHOD.
METHOD get_xsl_fo_template. " XSL-FO Template aus Repository lesen " In ABAP Cloud: Aus MIME Repository oder als Konstante DATA lv_template TYPE string. lv_template = |<?xml version="1.0" encoding="UTF-8"?>| && |<xsl:stylesheet version="1.0" ...>| && |...| && |</xsl:stylesheet>|.
rv_xsl = cl_abap_conv_codepage=>create_out( )->convert( lv_template ). ENDMETHOD.
ENDCLASS.SAP Forms Service by Adobe
Der SAP Forms Service by Adobe ist die Cloud-native Loesung fuer komplexe Formulare. Er ermoeglicht die Nutzung von Adobe-basierten Templates auf SAP BTP.
Service-Ueberblick
| Aspekt | Beschreibung |
|---|---|
| Service | SAP Forms Service by Adobe |
| Protokoll | REST API (JSON/XML) |
| Templates | XDP (Adobe LiveCycle Designer) |
| Output | PDF, Print Stream |
| Lizenz | Separate BTP-Lizenz erforderlich |
Architektur
┌────────────────────────────────────────────────────────────────────────────┐│ SAP Forms Service by Adobe ││ ││ ┌───────────────────┐ ┌───────────────────────────────────────┐ ││ │ ABAP Cloud │ │ SAP Forms Service │ ││ │ │ HTTP │ │ ││ │ ┌─────────────┐ │ ─────► │ ┌─────────────────────────────────┐ │ ││ │ │ Form Data │ │ JSON │ │ Template Storage │ │ ││ │ │ (XML/JSON) │ │ │ │ (XDP Templates) │ │ ││ │ └─────────────┘ │ │ └─────────────────────────────────┘ │ ││ │ │ │ │ │ ││ │ │ ◄───── │ ▼ │ ││ │ ┌─────────────┐ │ PDF │ ┌─────────────────────────────────┐ │ ││ │ │ PDF │ │ │ │ Adobe Forms Engine │ │ ││ │ │ Output │ │ │ │ (PDF Rendering) │ │ ││ │ └─────────────┘ │ │ └─────────────────────────────────┘ │ ││ └───────────────────┘ └───────────────────────────────────────┘ │└────────────────────────────────────────────────────────────────────────────┘Communication Arrangement einrichten
- Communication Scenario erstellen fuer SAP Forms Service
- Communication System mit BTP Forms Service URL konfigurieren
- Communication Arrangement mit OAuth 2.0 Credentials
Code-Beispiel: Forms Service aufrufen
" SAP Forms Service ClientCLASS zcl_forms_service_client DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_form_request, template_name TYPE string, template_data TYPE string, " JSON oder XML output_type TYPE string, " PDF, PCL, PS END OF ty_form_request.
TYPES: BEGIN OF ty_form_response, pdf_content TYPE xstring, page_count TYPE i, status TYPE string, message TYPE string, END OF ty_form_response.
METHODS: constructor IMPORTING iv_comm_scenario TYPE string.
METHODS: render_form IMPORTING is_request TYPE ty_form_request RETURNING VALUE(rs_response) TYPE ty_form_response RAISING cx_http_dest_provider_error cx_web_http_client_error.
PRIVATE SECTION. DATA: mv_comm_scenario TYPE string.
METHODS: get_http_client RETURNING VALUE(ro_client) TYPE REF TO if_web_http_client RAISING cx_http_dest_provider_error cx_web_http_client_error.
ENDCLASS.
CLASS zcl_forms_service_client IMPLEMENTATION.
METHOD constructor. mv_comm_scenario = iv_comm_scenario. ENDMETHOD.
METHOD render_form. " HTTP Client erstellen DATA(lo_client) = get_http_client( ).
TRY. " Request vorbereiten DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
" Request Body (JSON) DATA(lv_request_body) = |\{| && |"templateSource": "storageName",| && |"templateName": "{ is_request-template_name }",| && |"outputType": "{ is_request-output_type }",| && |"data": { is_request-template_data }| && |\}|.
lo_request->set_text( lv_request_body ).
" API aufrufen DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Response verarbeiten DATA(lv_status) = lo_response->get_status( ).
IF lv_status-code = 200. rs_response-pdf_content = lo_response->get_binary( ). rs_response-status = 'SUCCESS'. ELSE. rs_response-status = 'ERROR'. rs_response-message = lo_response->get_text( ). ENDIF.
CATCH cx_web_http_client_error INTO DATA(lx_http). rs_response-status = 'ERROR'. rs_response-message = lx_http->get_text( ). ENDTRY.
lo_client->close( ). ENDMETHOD.
METHOD get_http_client. " Destination aus Communication Arrangement DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = mv_comm_scenario service_id = 'FORMS_SERVICE' ).
ro_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ). ENDMETHOD.
ENDCLASS.Template-Daten vorbereiten
" Form-Daten als JSON fuer Forms ServiceCLASS zcl_form_data_builder DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: build_invoice_data IMPORTING is_invoice TYPE zcl_invoice_xml_generator=>ty_invoice RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_form_data_builder IMPLEMENTATION.
METHOD build_invoice_data. " JSON-Struktur fuer Adobe Form Template DATA(lo_json) = NEW /ui2/cl_json( ).
" Datenstruktur aufbauen DATA: BEGIN OF ls_form_data, header TYPE REF TO data, customer TYPE REF TO data, items TYPE REF TO data, totals TYPE REF TO data, END OF ls_form_data.
" Header CREATE DATA ls_form_data-header TYPE STANDARD TABLE OF string WITH EMPTY KEY. " ... Daten fuellen
" JSON serialisieren rv_json = /ui2/cl_json=>serialize( data = ls_form_data compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). ENDMETHOD.
ENDCLASS.Externe PDF-Services
Fuer maximale Flexibilitaet kannst du externe PDF-APIs nutzen. Populaere Optionen sind:
| Service | Beschreibung | Vorteile |
|---|---|---|
| PDFShift | HTML zu PDF API | Einfach, flexibel |
| DocRaptor | HTML/CSS zu PDF | Gute CSS-Unterstuetzung |
| Prince | XML/HTML zu PDF | Hervorragende Typografie |
| AWS Textract | PDF-Verarbeitung | AWS-Integration |
Code-Beispiel: Externen PDF-Service nutzen
" Externer PDF-Service Client (z.B. PDFShift)CLASS zcl_external_pdf_service DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: constructor IMPORTING iv_api_key TYPE string iv_api_url TYPE string.
METHODS: html_to_pdf IMPORTING iv_html TYPE string iv_options TYPE string OPTIONAL RETURNING VALUE(rv_pdf) TYPE xstring RAISING cx_web_http_client_error zcx_pdf_generation_error.
PRIVATE SECTION. DATA: mv_api_key TYPE string, mv_api_url TYPE string.
ENDCLASS.
CLASS zcl_external_pdf_service IMPLEMENTATION.
METHOD constructor. mv_api_key = iv_api_key. mv_api_url = iv_api_url. ENDMETHOD.
METHOD html_to_pdf. " Destination aus Communication Arrangement DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'Z_PDF_SERVICE' i_authn_mode = if_a4c_cp_service=>service_specific ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
TRY. DATA(lo_request) = lo_client->get_http_request( ).
" Headers setzen lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ). lo_request->set_header_field( i_name = 'Authorization' i_value = |Basic { mv_api_key }| ).
" Request Body DATA(lv_body) = |\{| && |"source": "{ escape( val = iv_html format = cl_abap_format=>e_json_string ) }",| && |"landscape": false,| && |"use_print": true| && |\}|.
lo_request->set_text( lv_body ).
" API aufrufen DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
DATA(lv_status) = lo_response->get_status( ).
IF lv_status-code = 200. rv_pdf = lo_response->get_binary( ). ELSE. RAISE EXCEPTION TYPE zcx_pdf_generation_error EXPORTING textid = zcx_pdf_generation_error=>api_error mv_message = lo_response->get_text( ). ENDIF.
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
ENDCLASS.HTML-Template fuer PDF
" HTML-basierte PDF-GenerierungCLASS zcl_html_pdf_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: generate_invoice_html IMPORTING is_invoice TYPE zcl_invoice_xml_generator=>ty_invoice RETURNING VALUE(rv_html) TYPE string.
ENDCLASS.
CLASS zcl_html_pdf_generator IMPLEMENTATION.
METHOD generate_invoice_html. " HTML-Template mit Inline-CSS fuer PDF rv_html = |<!DOCTYPE html>| && |<html>| && |<head>| && |<style>| && |body \{ font-family: Arial, sans-serif; margin: 40px; \}| && |h1 \{ color: #333; \}| && |table \{ width: 100%; border-collapse: collapse; margin-top: 20px; \}| && |th, td \{ border: 1px solid #ddd; padding: 8px; text-align: left; \}| && |th \{ background-color: #f2f2f2; \}| && |.totals \{ width: 300px; float: right; margin-top: 20px; \}| && |.totals td:last-child \{ text-align: right; \}| && |.grand-total \{ font-weight: bold; border-top: 2px solid #333; \}| && |</style>| && |</head>| && |<body>| && |<h1>RECHNUNG</h1>| && |<div style="display: flex; justify-content: space-between;">| && |<div>| && |<strong>Kunde:</strong><br>| && |{ is_invoice-customer_name }<br>| && |{ is_invoice-customer_addr }| && |</div>| && |<div style="text-align: right;">| && |<strong>Rechnungsnummer:</strong> { is_invoice-invoice_number }<br>| && |<strong>Datum:</strong> { is_invoice-invoice_date DATE = USER }| && |</div>| && |</div>|.
" Positionen rv_html = rv_html && |<table>| && |<tr><th>Pos.</th><th>Beschreibung</th><th>Menge</th>| && |<th>Einzelpreis</th><th>Gesamt</th></tr>|.
LOOP AT is_invoice-items INTO DATA(ls_item). rv_html = rv_html && |<tr>| && |<td>{ ls_item-position }</td>| && |<td>{ ls_item-description }</td>| && |<td>{ ls_item-quantity }</td>| && |<td>{ ls_item-unit_price } EUR</td>| && |<td>{ ls_item-total } EUR</td>| && |</tr>|. ENDLOOP.
rv_html = rv_html && |</table>|.
" Summen rv_html = rv_html && |<table class="totals">| && |<tr><td>Zwischensumme:</td><td>{ is_invoice-subtotal } EUR</td></tr>| && |<tr><td>MwSt (19%):</td><td>{ is_invoice-tax } EUR</td></tr>| && |<tr class="grand-total">| && |<td>Gesamtbetrag:</td><td>{ is_invoice-total } EUR</td></tr>| && |</table>| && |</body></html>|. ENDMETHOD.
ENDCLASS.RAP Action fuer PDF-Generierung
Die beste Integration erfolgt ueber eine RAP Action, die PDFs direkt im Business Object generiert.
Behavior Definition
managed implementation in class ZBP_I_INVOICE unique;strict ( 2 );
define behavior for ZI_Invoice alias Invoicepersistent table zinvoicelock masterauthorization master ( instance ){ create; update; delete;
// PDF-Generierung als Action action generatePdf result [1] $self;
// Static Factory Action fuer Download static action downloadInvoicePdf parameter ZA_InvoiceDownload result [1] ZA_PdfResult;
field ( readonly ) InvoiceUUID, CreatedAt, CreatedBy;}Action Parameter und Result
-- Parameter fuer Download Action@EndUserText.label: 'Invoice Download Parameter'define abstract entity ZA_InvoiceDownload{ InvoiceNumber : abap.char(10); OutputFormat : abap.char(10); // PDF, HTML}
-- Result mit PDF Binary@EndUserText.label: 'PDF Result'define abstract entity ZA_PdfResult{ FileName : abap.char(100); MimeType : abap.char(50); FileContent : abap.rawstring(0); // XSTRING fuer Binary}Behavior Implementation
" RAP Behavior Implementation mit PDF-ActionCLASS lhc_invoice DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION. METHODS generatePdf FOR MODIFY IMPORTING keys FOR ACTION Invoice~generatePdf RESULT result.
METHODS downloadInvoicePdf FOR MODIFY IMPORTING keys FOR ACTION Invoice~downloadInvoicePdf RESULT result.
METHODS: build_invoice_data IMPORTING is_invoice TYPE zinvoice RETURNING VALUE(rs_data) TYPE zcl_invoice_xml_generator=>ty_invoice.
ENDCLASS.
CLASS lhc_invoice IMPLEMENTATION.
METHOD generatePdf. " Rechnungsdaten lesen READ ENTITIES OF zi_invoice IN LOCAL MODE ENTITY Invoice ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_invoices).
LOOP AT lt_invoices INTO DATA(ls_invoice). " Daten fuer PDF aufbereiten DATA(ls_pdf_data) = build_invoice_data( CORRESPONDING #( ls_invoice ) ).
" PDF generieren TRY. DATA(lo_pdf_gen) = NEW zcl_pdf_generator( ). DATA(lv_pdf) = lo_pdf_gen->generate_invoice_pdf( ls_pdf_data ).
" PDF als Attachment speichern (optional) " Oder: In result zurueckgeben
" Erfolgsmeldung APPEND VALUE #( %tky = ls_invoice-%tky %param = ls_invoice ) TO result.
CATCH cx_transformation_error INTO DATA(lx_error). " Fehler melden APPEND VALUE #( %tky = ls_invoice-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-invoice. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD downloadInvoicePdf. " Parameter auslesen DATA(ls_param) = keys[ 1 ]-%param.
" Rechnung laden SELECT SINGLE * FROM zinvoice WHERE invoice_number = @ls_param-InvoiceNumber INTO @DATA(ls_invoice).
IF sy-subrc <> 0. APPEND VALUE #( %tky = keys[ 1 ]-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Rechnung { ls_param-InvoiceNumber } nicht gefunden| ) ) TO reported-invoice. RETURN. ENDIF.
" PDF generieren TRY. DATA(ls_pdf_data) = build_invoice_data( ls_invoice ). DATA(lo_pdf_gen) = NEW zcl_pdf_generator( ). DATA(lv_pdf) = lo_pdf_gen->generate_invoice_pdf( ls_pdf_data ).
" Result zurueckgeben APPEND VALUE #( %tky = keys[ 1 ]-%tky %param = VALUE #( FileName = |Rechnung_{ ls_param-InvoiceNumber }.pdf| MimeType = 'application/pdf' FileContent = lv_pdf ) ) TO result.
CATCH cx_transformation_error INTO DATA(lx_error). APPEND VALUE #( %tky = keys[ 1 ]-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-invoice. ENDTRY. ENDMETHOD.
METHOD build_invoice_data. " Rechnungsdaten fuer PDF aufbereiten rs_data-invoice_number = is_invoice-invoice_number. rs_data-invoice_date = is_invoice-invoice_date. rs_data-customer_name = is_invoice-customer_name. rs_data-customer_addr = is_invoice-customer_address.
" Positionen laden SELECT position, description, quantity, unit_price, total_amount FROM zinvoice_item WHERE invoice_uuid = @is_invoice-invoice_uuid INTO CORRESPONDING FIELDS OF TABLE @rs_data-items.
" Summen berechnen LOOP AT rs_data-items INTO DATA(ls_item). rs_data-subtotal = rs_data-subtotal + ls_item-total. ENDLOOP.
rs_data-tax = rs_data-subtotal * '0.19'. rs_data-total = rs_data-subtotal + rs_data-tax. ENDMETHOD.
ENDCLASS.Fehlerbehandlung
Custom Exception fuer PDF-Generierung
" PDF-Generierungs-ExceptionCLASS zcx_pdf_generation_error DEFINITION PUBLIC INHERITING FROM cx_static_check CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_t100_message.
CONSTANTS: BEGIN OF template_not_found, msgid TYPE symsgid VALUE 'ZPDF', msgno TYPE symsgno VALUE '001', attr1 TYPE scx_attrname VALUE 'MV_TEMPLATE', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF template_not_found, BEGIN OF api_error, msgid TYPE symsgid VALUE 'ZPDF', msgno TYPE symsgno VALUE '002', attr1 TYPE scx_attrname VALUE 'MV_MESSAGE', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF api_error, BEGIN OF transformation_error, msgid TYPE symsgid VALUE 'ZPDF', msgno TYPE symsgno VALUE '003', attr1 TYPE scx_attrname VALUE 'MV_MESSAGE', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF transformation_error.
DATA: mv_template TYPE string READ-ONLY, mv_message TYPE string READ-ONLY.
METHODS: constructor IMPORTING textid LIKE if_t100_message=>t100key OPTIONAL previous LIKE previous OPTIONAL mv_template TYPE string OPTIONAL mv_message TYPE string OPTIONAL.
ENDCLASS.
CLASS zcx_pdf_generation_error IMPLEMENTATION.
METHOD constructor. super->constructor( previous = previous ). me->mv_template = mv_template. me->mv_message = mv_message.
CLEAR me->textid. IF textid IS INITIAL. if_t100_message~t100key = if_t100_message=>default_textid. ELSE. if_t100_message~t100key = textid. ENDIF. ENDMETHOD.
ENDCLASS.Robuste PDF-Generierung
" PDF-Service mit Retry-LogikCLASS zcl_robust_pdf_service DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: generate_pdf_with_retry IMPORTING is_data TYPE any iv_template TYPE string iv_max_retries TYPE i DEFAULT 3 RETURNING VALUE(rv_pdf) TYPE xstring RAISING zcx_pdf_generation_error.
PRIVATE SECTION. CONSTANTS: gc_retry_delay_ms TYPE i VALUE 1000.
ENDCLASS.
CLASS zcl_robust_pdf_service IMPLEMENTATION.
METHOD generate_pdf_with_retry. DATA: lv_attempts TYPE i VALUE 0, lx_last_error TYPE REF TO cx_root.
WHILE lv_attempts < iv_max_retries. lv_attempts = lv_attempts + 1.
TRY. " PDF generieren DATA(lo_service) = NEW zcl_forms_service_client( 'Z_FORMS_COMM' ). DATA(ls_response) = lo_service->render_form( is_request = VALUE #( template_name = iv_template template_data = /ui2/cl_json=>serialize( data = is_data ) output_type = 'PDF' ) ).
IF ls_response-status = 'SUCCESS'. rv_pdf = ls_response-pdf_content. RETURN. " Erfolg ELSE. " Service-Fehler lx_last_error = NEW zcx_pdf_generation_error( textid = zcx_pdf_generation_error=>api_error mv_message = ls_response-message ). ENDIF.
CATCH cx_http_dest_provider_error cx_web_http_client_error INTO DATA(lx_http). " Netzwerk-Fehler - Retry moeglich lx_last_error = lx_http. ENDTRY.
" Warten vor Retry IF lv_attempts < iv_max_retries. DATA(lv_wait) = gc_retry_delay_ms * lv_attempts. " In ABAP Cloud: cl_abap_timers verwenden ENDIF. ENDWHILE.
" Alle Versuche fehlgeschlagen RAISE EXCEPTION TYPE zcx_pdf_generation_error EXPORTING textid = zcx_pdf_generation_error=>api_error previous = lx_last_error mv_message = |PDF-Generierung nach { iv_max_retries } Versuchen fehlgeschlagen|. ENDMETHOD.
ENDCLASS.Alternativen und Workarounds
Option 1: HTML-Export statt PDF
Fuer einfache Dokumente kann ein HTML-Export ausreichen:
" HTML als Alternative zu PDFCLASS zcl_html_document_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: generate_html_document IMPORTING is_data TYPE any RETURNING VALUE(rv_html) TYPE string.
METHODS: get_as_data_uri IMPORTING iv_html TYPE string RETURNING VALUE(rv_data_uri) TYPE string.
ENDCLASS.
CLASS zcl_html_document_generator IMPLEMENTATION.
METHOD generate_html_document. " Vollstaendiges HTML-Dokument mit Print-optimiertem CSS rv_html = |<!DOCTYPE html>| && |<html>| && |<head>| && |<style>| && |@media print \{| && | body \{ margin: 0; padding: 20mm; \}| && | .no-print \{ display: none; \}| && | @page \{ size: A4; margin: 20mm; \}| && |\}| && |</style>| && |</head>| && |<body>| && |<button class="no-print" onclick="window.print()">Drucken</button>| && |<!-- Dokumentinhalt -->| && |</body>| && |</html>|. ENDMETHOD.
METHOD get_as_data_uri. " HTML als Data URI fuer direkten Download DATA(lv_base64) = cl_http_utility=>encode_base64( cl_abap_conv_codepage=>create_out( )->convert( iv_html ) ). rv_data_uri = |data:text/html;base64,{ lv_base64 }|. ENDMETHOD.
ENDCLASS.Option 2: Excel-Export als Alternative
" Excel-Export fuer tabellarische DatenCLASS zcl_excel_exporter DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: export_to_excel IMPORTING it_data TYPE ANY TABLE RETURNING VALUE(rv_excel) TYPE xstring.
ENDCLASS.Option 3: Asynchrone PDF-Generierung
Fuer grosse Dokumente oder Massengenerierung:
" Asynchrone PDF-Generierung mit bgPFCLASS zcl_async_pdf_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: schedule_pdf_generation IMPORTING iv_invoice_id TYPE zinvoice_id RETURNING VALUE(rv_job_id) TYPE string.
METHODS: check_pdf_status IMPORTING iv_job_id TYPE string EXPORTING ev_status TYPE string ev_pdf TYPE xstring.
ENDCLASS.Best Practices
Empfehlungen fuer PDF-Generierung
| Aspekt | Empfehlung |
|---|---|
| Einfache Dokumente | HTML-zu-PDF mit externem Service |
| Komplexe Formulare | SAP Forms Service by Adobe |
| Hohe Volumen | Asynchrone Generierung mit bgPF |
| Offline-Szenarien | HTML-Export mit Print-CSS |
| Compliance | Zugriffsprotokollierung implementieren |
Performance-Tipps
- Caching: Templates und statische Inhalte cachen
- Batch-Verarbeitung: Mehrere PDFs in einem Request generieren
- Komprimierung: PDF-Komprimierung aktivieren
- Asynchron: Grosse PDFs im Hintergrund generieren
Weiterfuehrende Themen
- HTTP Client - HTTP-Kommunikation fuer externe Services
- RAP Actions und Functions - Actions fuer PDF-Download
- Background Jobs - Asynchrone PDF-Generierung
- Output Management - Druckausgabe in der Cloud
Zusammenfassung
PDF-Generierung in ABAP Cloud erfordert neue Ansaetze:
- XSL-FO fuer XML-basierte Transformation (mit externem FOP-Prozessor)
- SAP Forms Service by Adobe fuer komplexe Formulare mit Adobe-Templates
- Externe PDF-APIs fuer maximale Flexibilitaet (PDFShift, DocRaptor)
- RAP Actions integrieren PDF-Generierung nahtlos in Business Objects
- Fehlerbehandlung mit Retry-Logik fuer robuste Produktion
Die Wahl der richtigen Option haengt von deinen Anforderungen ab: Komplexitaet der Dokumente, vorhandene Templates, Volumen und Budget.