SAP Forms by Adobe ist der Cloud-native Dienst fuer professionelle PDF-Formulare auf SAP BTP. Er ersetzt die klassischen Adobe Forms (Transaktion SFP) und ermoeglicht die Generierung hochwertiger PDF-Dokumente mit dynamischen Inhalten, Tabellen und Barcodes.
Ueberblick und Unterschiede zu klassischen Adobe Forms
In On-Premise SAP-Systemen nutzt du Adobe Forms ueber die Transaktion SFP mit dem Adobe LiveCycle Designer. In ABAP Cloud ist dieser Ansatz nicht verfuegbar. Stattdessen bietet SAP den SAP Forms Service by Adobe als BTP-Service an.
Vergleich: Klassisch vs. Cloud
| Aspekt | Klassische Adobe Forms (SFP) | SAP Forms Service by Adobe |
|---|---|---|
| Zugriff | Transaktion SFP, SE78 | REST API auf BTP |
| Template-Design | Adobe LiveCycle Designer (lokal) | Adobe LiveCycle Designer (lokal) |
| Template-Speicherung | SAP-System (Form Builder) | BTP Document Repository |
| Rendering | Adobe Document Services (ADS) | Cloud-basierte Adobe Engine |
| Lizenz | SAP-Standardlizenz | Separate BTP-Lizenz erforderlich |
| Integration | ABAP-Funktionsbausteine | HTTP/REST mit Communication Arrangement |
Architektur
┌─────────────────────────────────────────────────────────────────────────────┐│ SAP Forms by Adobe - Architektur ││ ││ ┌────────────────────────────────────────────────────────────────────────┐ ││ │ ABAP Cloud Environment │ ││ │ │ ││ │ ┌─────────────────────┐ ┌─────────────────────┐ │ ││ │ │ Business Logic │────▶│ Forms Client │ │ ││ │ │ (RAP/Klassen) │ │ (HTTP Client) │ │ ││ │ └─────────────────────┘ └──────────┬──────────┘ │ ││ │ │ │ ││ └──────────────────────────────────────────┼───────────────────────────────┘ ││ │ ││ │ HTTPS/REST ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ SAP Forms Service by Adobe │ ││ │ │ ││ │ ┌─────────────────────┐ ┌─────────────────────┐ │ ││ │ │ Template Storage │ │ Adobe Rendering │ │ ││ │ │ (XDP Templates) │────▶│ Engine │ │ ││ │ └─────────────────────┘ └──────────┬──────────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────────────┐ │ ││ │ │ PDF Output │ │ ││ │ └─────────────────────┘ │ ││ └─────────────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘Service-Setup auf SAP BTP
Schritt 1: Service aktivieren
- Oeffne das SAP BTP Cockpit
- Navigiere zu Service Marketplace
- Suche nach SAP Forms Service by Adobe
- Erstelle eine Service Instance
- Erstelle einen Service Key fuer die Credentials
Schritt 2: Service Key analysieren
Der Service Key enthaelt die notwendigen Credentials:
{ "uri": "https://forms-service.cfapps.eu10.hana.ondemand.com", "uaa": { "clientid": "sb-forms-service!t12345", "clientsecret": "abc123...", "url": "https://your-tenant.authentication.eu10.hana.ondemand.com" }}Schritt 3: Communication Arrangement einrichten
Erstelle in ABAP Cloud ein Communication Arrangement fuer den Forms Service.
Communication Scenario (ADT):
<?xml version="1.0" encoding="utf-8"?><scn:scenario xmlns:scn="http://sap.com/communication-scenario" id="Z_FORMS_ADOBE_SERVICE" displayName="SAP Forms by Adobe Service">
<scn:communicationType>HTTP</scn:communicationType> <scn:outboundServices> <scn:outboundService id="FORMS_RENDER_API"> <scn:technicalName>FORMS_RENDER_API</scn:technicalName> <scn:description>PDF Rendering API</scn:description> </scn:outboundService> <scn:outboundService id="FORMS_TEMPLATE_API"> <scn:technicalName>FORMS_TEMPLATE_API</scn:technicalName> <scn:description>Template Management API</scn:description> </scn:outboundService> </scn:outboundServices>
<scn:supportedAuthenticationMethods> <scn:supportedAuthenticationMethod>OAUTH2_CLIENT_CREDENTIALS</scn:supportedAuthenticationMethod> </scn:supportedAuthenticationMethods></scn:scenario>Fiori App “Maintain Communication Arrangements”:
- Communication System erstellen mit Forms Service URL
- OAuth 2.0 Client Credentials aus Service Key eintragen
- Communication Arrangement mit deinem Scenario aktivieren
Template Definition und Upload
Templates werden mit Adobe LiveCycle Designer erstellt und als XDP-Dateien gespeichert.
XDP-Template Struktur
Ein XDP-Template besteht aus:
- Masterpage: Seitenlayout, Kopf-/Fusszeilen
- Body Pages: Dynamischer Inhalt
- Data Schema: XML-Struktur fuer Daten
- Scripts: JavaScript fuer Logik
Template erstellen
- Oeffne Adobe LiveCycle Designer
- Erstelle ein neues Formular
- Definiere die Data Connection mit XML Schema
- Gestalte das Layout mit Feldern, Tabellen, Bildern
- Speichere als .xdp Datei
Template-Upload per API
" Template Upload zum Forms ServiceCLASS zcl_forms_template_manager DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: upload_template IMPORTING iv_template_name TYPE string iv_template_content TYPE xstring RETURNING VALUE(rv_success) TYPE abap_bool RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS: list_templates RETURNING VALUE(rt_templates) TYPE string_table RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS: delete_template IMPORTING iv_template_name TYPE string RETURNING VALUE(rv_success) TYPE abap_bool RAISING cx_http_dest_provider_error cx_web_http_client_error.
PRIVATE SECTION. CONSTANTS: gc_comm_scenario TYPE string VALUE 'Z_FORMS_ADOBE_SERVICE', gc_service_id TYPE string VALUE 'FORMS_TEMPLATE_API'.
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_template_manager IMPLEMENTATION.
METHOD upload_template. DATA(lo_client) = get_http_client( ).
TRY. DATA(lo_request) = lo_client->get_http_request( ).
" Multipart Request fuer Template Upload lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/octet-stream' ).
" Template-Name als Query Parameter lo_request->set_uri_path( |/v1/templates/{ iv_template_name }| ).
" Template-Content als Body lo_request->set_binary( iv_template_content ).
" PUT Request fuer Upload DATA(lo_response) = lo_client->execute( if_web_http_client=>put ). DATA(lv_status) = lo_response->get_status( ).
rv_success = xsdbool( lv_status-code = 200 OR lv_status-code = 201 ).
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
METHOD list_templates. DATA(lo_client) = get_http_client( ).
TRY. DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( '/v1/templates' ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code = 200. " JSON Response parsen DATA(lv_json) = lo_response->get_text( ). " Templates aus JSON extrahieren " ... ENDIF.
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
METHOD delete_template. DATA(lo_client) = get_http_client( ).
TRY. DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/v1/templates/{ iv_template_name }| ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>delete ). rv_success = xsdbool( lo_response->get_status( )-code = 204 ).
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
METHOD get_http_client. DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = gc_comm_scenario service_id = gc_service_id ).
ro_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ). ENDMETHOD.
ENDCLASS.PDF-Generierung mit Daten
Die Kernfunktion des Forms Service ist die PDF-Generierung aus Template und Daten.
Rendering API aufrufen
" SAP Forms Service - PDF Rendering ClientCLASS zcl_forms_pdf_renderer DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_render_options, output_type TYPE string, " PDF, PCL, PS, ZPL tagged_pdf TYPE abap_bool, embed_fonts TYPE abap_bool, locale TYPE string, END OF ty_render_options.
TYPES: BEGIN OF ty_render_result, pdf_content TYPE xstring, page_count TYPE i, file_size TYPE i, render_time TYPE i, " Millisekunden success TYPE abap_bool, error_message TYPE string, END OF ty_render_result.
METHODS: render_pdf IMPORTING iv_template_name TYPE string iv_xml_data TYPE xstring is_options TYPE ty_render_options OPTIONAL RETURNING VALUE(rs_result) TYPE ty_render_result RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS: render_pdf_with_json IMPORTING iv_template_name TYPE string iv_json_data TYPE string is_options TYPE ty_render_options OPTIONAL RETURNING VALUE(rs_result) TYPE ty_render_result RAISING cx_http_dest_provider_error cx_web_http_client_error.
PRIVATE SECTION. CONSTANTS: gc_comm_scenario TYPE string VALUE 'Z_FORMS_ADOBE_SERVICE', gc_service_id TYPE string VALUE 'FORMS_RENDER_API'.
METHODS: build_request_json IMPORTING iv_template_name TYPE string iv_data TYPE string iv_data_type TYPE string " xml oder json is_options TYPE ty_render_options RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_forms_pdf_renderer IMPLEMENTATION.
METHOD render_pdf. " XML-Daten zu Base64 konvertieren DATA(lv_xml_base64) = cl_web_http_utility=>encode_base64( iv_xml_data ).
" Request JSON aufbauen DATA(lv_request) = build_request_json( iv_template_name = iv_template_name iv_data = lv_xml_base64 iv_data_type = 'xml' is_options = is_options ).
" HTTP Client erstellen DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = gc_comm_scenario service_id = gc_service_id ). DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
TRY. DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ). lo_request->set_header_field( i_name = 'Accept' i_value = 'application/json' ).
lo_request->set_uri_path( '/v1/documents/render' ). lo_request->set_text( lv_request ).
" Zeitmessung starten DATA(lv_start) = utclong_current( ).
" Request ausfuehren DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Zeitmessung beenden DATA(lv_end) = utclong_current( ). rs_result-render_time = CONV i( utclong_diff( high = lv_end low = lv_start ) * 1000 ).
" Response verarbeiten DATA(lv_status) = lo_response->get_status( ).
IF lv_status-code = 200. " Erfolg - PDF extrahieren DATA(lv_response_json) = lo_response->get_text( ).
" JSON parsen und PDF Base64 extrahieren " Vereinfachte Extraktion: DATA lv_pdf_base64 TYPE string. " ... JSON Parsing ...
rs_result-pdf_content = cl_web_http_utility=>decode_base64( lv_pdf_base64 ). rs_result-file_size = xstrlen( rs_result-pdf_content ). rs_result-success = abap_true. ELSE. rs_result-success = abap_false. rs_result-error_message = lo_response->get_text( ). ENDIF.
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
METHOD render_pdf_with_json. " JSON-Daten direkt verwenden DATA(lv_request) = build_request_json( iv_template_name = iv_template_name iv_data = iv_json_data iv_data_type = 'json' is_options = is_options ).
" ... (analog zu render_pdf) ENDMETHOD.
METHOD build_request_json. " Request Body fuer Forms Service API DATA(lv_output) = COND string( WHEN is_options-output_type IS NOT INITIAL THEN is_options-output_type ELSE 'PDF' ).
rv_json = |\{| && |"templateSource": "storageName",| && |"templateName": "{ iv_template_name }",| && |"outputType": "{ lv_output }",| && |"taggedPdf": { COND string( WHEN is_options-tagged_pdf = abap_true THEN 'true' ELSE 'false' ) },| && |"embedFonts": { COND string( WHEN is_options-embed_fonts = abap_true THEN 'true' ELSE 'false' ) },|.
IF is_options-locale IS NOT INITIAL. rv_json = rv_json && |"locale": "{ is_options-locale }",|. ENDIF.
IF iv_data_type = 'xml'. rv_json = rv_json && |"xmlData": "{ iv_data }"|. ELSE. rv_json = rv_json && |"data": { iv_data }|. ENDIF.
rv_json = rv_json && |\}|. ENDMETHOD.
ENDCLASS.Rechnungsdaten aufbereiten
" XML-Daten Generator fuer Forms ServiceCLASS zcl_invoice_form_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_item, position TYPE i, material_id TYPE string, description TYPE string, quantity TYPE p LENGTH 10 DECIMALS 3, unit TYPE string, unit_price TYPE p LENGTH 15 DECIMALS 2, total TYPE p LENGTH 15 DECIMALS 2, END OF ty_item, ty_items TYPE STANDARD TABLE OF ty_item WITH KEY position.
TYPES: BEGIN OF ty_invoice, invoice_number TYPE string, invoice_date TYPE d, due_date TYPE d, company_name TYPE string, company_address TYPE string, company_city TYPE string, company_country TYPE string, company_vat_id TYPE string, customer_number TYPE string, customer_name TYPE string, customer_address TYPE string, customer_city TYPE string, customer_country TYPE string, items TYPE ty_items, subtotal TYPE p LENGTH 15 DECIMALS 2, tax_rate TYPE p LENGTH 5 DECIMALS 2, tax_amount TYPE p LENGTH 15 DECIMALS 2, total TYPE p LENGTH 15 DECIMALS 2, currency TYPE string, payment_terms TYPE string, bank_name TYPE string, bank_iban TYPE string, bank_bic TYPE string, END OF ty_invoice.
METHODS: generate_xml IMPORTING is_invoice TYPE ty_invoice RETURNING VALUE(rv_xml) TYPE xstring.
METHODS: generate_json IMPORTING is_invoice TYPE ty_invoice RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_invoice_form_data IMPLEMENTATION.
METHOD generate_xml. " XML-Dokument aufbauen DATA(lo_ixml) = cl_ixml=>create( ). DATA(lo_doc) = lo_ixml->create_document( ).
" Root Element DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Namespace fuer Adobe Forms lo_root->set_attribute( name = 'xmlns:xfa' value = 'http://www.xfa.org/schema/xfa-data/1.0/' ).
" === Header Sektion === 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_inv_date) = lo_doc->create_element( name = 'InvoiceDate' ). lo_inv_date->set_value( |{ is_invoice-invoice_date DATE = USER }| ). lo_header->append_child( lo_inv_date ).
DATA(lo_due_date) = lo_doc->create_element( name = 'DueDate' ). lo_due_date->set_value( |{ is_invoice-due_date DATE = USER }| ). lo_header->append_child( lo_due_date ).
" === Company (Absender) === DATA(lo_company) = lo_doc->create_element( name = 'Company' ). lo_root->append_child( lo_company ).
DATA(lo_comp_name) = lo_doc->create_element( name = 'Name' ). lo_comp_name->set_value( is_invoice-company_name ). lo_company->append_child( lo_comp_name ).
DATA(lo_comp_addr) = lo_doc->create_element( name = 'Address' ). lo_comp_addr->set_value( is_invoice-company_address ). lo_company->append_child( lo_comp_addr ).
DATA(lo_comp_city) = lo_doc->create_element( name = 'City' ). lo_comp_city->set_value( is_invoice-company_city ). lo_company->append_child( lo_comp_city ).
DATA(lo_comp_country) = lo_doc->create_element( name = 'Country' ). lo_comp_country->set_value( is_invoice-company_country ). lo_company->append_child( lo_comp_country ).
DATA(lo_vat_id) = lo_doc->create_element( name = 'VatId' ). lo_vat_id->set_value( is_invoice-company_vat_id ). lo_company->append_child( lo_vat_id ).
" === Customer (Empfaenger) === DATA(lo_customer) = lo_doc->create_element( name = 'Customer' ). lo_root->append_child( lo_customer ).
DATA(lo_cust_no) = lo_doc->create_element( name = 'CustomerNumber' ). lo_cust_no->set_value( is_invoice-customer_number ). lo_customer->append_child( lo_cust_no ).
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_address ). lo_customer->append_child( lo_cust_addr ).
DATA(lo_cust_city) = lo_doc->create_element( name = 'City' ). lo_cust_city->set_value( is_invoice-customer_city ). lo_customer->append_child( lo_cust_city ).
DATA(lo_cust_country) = lo_doc->create_element( name = 'Country' ). lo_cust_country->set_value( is_invoice-customer_country ). lo_customer->append_child( lo_cust_country ).
" === Items (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_mat) = lo_doc->create_element( name = 'MaterialId' ). lo_mat->set_value( ls_item-material_id ). lo_item->append_child( lo_mat ).
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_unit) = lo_doc->create_element( name = 'Unit' ). lo_unit->set_value( ls_item-unit ). lo_item->append_child( lo_unit ).
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.
" === Totals === 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_rate) = lo_doc->create_element( name = 'TaxRate' ). lo_tax_rate->set_value( |{ is_invoice-tax_rate }| ). lo_totals->append_child( lo_tax_rate ).
DATA(lo_tax_amt) = lo_doc->create_element( name = 'TaxAmount' ). lo_tax_amt->set_value( |{ is_invoice-tax_amount }| ). lo_totals->append_child( lo_tax_amt ).
DATA(lo_grand) = lo_doc->create_element( name = 'GrandTotal' ). lo_grand->set_value( |{ is_invoice-total }| ). lo_totals->append_child( lo_grand ).
DATA(lo_curr) = lo_doc->create_element( name = 'Currency' ). lo_curr->set_value( is_invoice-currency ). lo_totals->append_child( lo_curr ).
" === Payment Info === DATA(lo_payment) = lo_doc->create_element( name = 'Payment' ). lo_root->append_child( lo_payment ).
DATA(lo_terms) = lo_doc->create_element( name = 'Terms' ). lo_terms->set_value( is_invoice-payment_terms ). lo_payment->append_child( lo_terms ).
DATA(lo_bank) = lo_doc->create_element( name = 'BankName' ). lo_bank->set_value( is_invoice-bank_name ). lo_payment->append_child( lo_bank ).
DATA(lo_iban) = lo_doc->create_element( name = 'IBAN' ). lo_iban->set_value( is_invoice-bank_iban ). lo_payment->append_child( lo_iban ).
DATA(lo_bic) = lo_doc->create_element( name = 'BIC' ). lo_bic->set_value( is_invoice-bank_bic ). lo_payment->append_child( lo_bic ).
" XML zu xstring DATA(lo_stream) = lo_ixml->create_stream_factory( )->create_ostream_xstring( rv_xml ). DATA(lo_renderer) = lo_ixml->create_renderer( document = lo_doc ostream = lo_stream ). lo_renderer->render( ). ENDMETHOD.
METHOD generate_json. " JSON-Serialisierung fuer Forms Service rv_json = /ui2/cl_json=>serialize( data = is_invoice compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). ENDMETHOD.
ENDCLASS.Dynamische Tabellen in Formularen
Dynamische Tabellen sind ein Kernelement professioneller Formulare. Sie wachsen automatisch mit den Daten.
Template-Design fuer Tabellen
Im Adobe LiveCycle Designer:
- Fuege eine Table aus der Palette ein
- Setze die Tabelle auf Dynamic (Eigenschaften > Pagination)
- Binde die Header Row an statische Werte
- Binde die Body Row an den wiederholenden Datenpfad (z.B.
$.Invoice.Items.Item[*])
XML-Struktur fuer Tabellen
<Invoice> <Items> <Item> <Position>1</Position> <Description>Produkt A</Description> <Quantity>10</Quantity> <UnitPrice>99.00</UnitPrice> <Total>990.00</Total> </Item> <Item> <Position>2</Position> <Description>Produkt B</Description> <Quantity>5</Quantity> <UnitPrice>149.00</UnitPrice> <Total>745.00</Total> </Item> <!-- Weitere Items... --> </Items></Invoice>Seitenumbruch in Tabellen
" Tabellen mit automatischem Seitenumbruch" Im Template: Table > Pagination = "Continue filling from previous page"
" ABAP: Daten aufbereiten - das Template handhabt den UmbruchMETHOD generate_multi_page_invoice. " Alle Items in einer Liste - Forms Service teilt automatisch auf Seiten LOOP AT lt_order_items INTO DATA(ls_item). APPEND VALUE ty_item( position = ls_item-posnr description = ls_item-arktx quantity = ls_item-kwmeng unit_price = ls_item-netpr total = ls_item-netwr ) TO ls_invoice-items. ENDLOOP.
" XML generieren DATA(lv_xml) = NEW zcl_invoice_form_data( )->generate_xml( ls_invoice ).
" PDF rendern - automatischer Seitenumbruch bei langen Tabellen DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ). rs_result = lo_renderer->render_pdf( iv_template_name = 'INVOICE_TEMPLATE' iv_xml_data = lv_xml is_options = VALUE #( output_type = 'PDF' ) ).ENDMETHOD.Gruppierung und Zwischensummen
" Positionen mit Gruppierung nach KategorieTYPES: BEGIN OF ty_category_group, category_name TYPE string, items TYPE ty_items, subtotal TYPE p LENGTH 15 DECIMALS 2, END OF ty_category_group, ty_category_groups TYPE STANDARD TABLE OF ty_category_group WITH KEY category_name.
METHOD generate_grouped_xml. " XML mit Gruppen fuer mehrstufige Tabellen DATA(lo_doc) = cl_ixml=>create( )->create_document( ). DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Kategorien DATA(lo_categories) = lo_doc->create_element( name = 'Categories' ). lo_root->append_child( lo_categories ).
LOOP AT it_groups INTO DATA(ls_group). DATA(lo_category) = lo_doc->create_element( name = 'Category' ). lo_categories->append_child( lo_category ).
DATA(lo_cat_name) = lo_doc->create_element( name = 'Name' ). lo_cat_name->set_value( ls_group-category_name ). lo_category->append_child( lo_cat_name ).
DATA(lo_items) = lo_doc->create_element( name = 'Items' ). lo_category->append_child( lo_items ).
" Items der Kategorie LOOP AT ls_group-items INTO DATA(ls_item). DATA(lo_item) = lo_doc->create_element( name = 'Item' ). lo_items->append_child( lo_item ). " ... Item-Felder ... ENDLOOP.
" Zwischensumme DATA(lo_subtotal) = lo_doc->create_element( name = 'Subtotal' ). lo_subtotal->set_value( |{ ls_group-subtotal }| ). lo_category->append_child( lo_subtotal ). ENDLOOP.
" XML zu xstring " ...ENDMETHOD.Barcode-Integration in Formulare
Barcodes und QR-Codes sind wichtig fuer automatisierte Verarbeitung und Tracking.
Unterstuetzte Barcode-Typen
| Typ | Beschreibung | Anwendung |
|---|---|---|
| Code 128 | Alphanumerisch, variabel | Allgemein, Logistik |
| EAN-13 | 13 Ziffern | Produktkennzeichnung |
| QR-Code | 2D, hohe Kapazitaet | URLs, Zahlungsdaten |
| Data Matrix | 2D, kompakt | Industrielle Anwendungen |
| PDF417 | 2D, hohe Dichte | Ausweise, Transportdokumente |
Barcode im Template
Im Adobe LiveCycle Designer:
- Fuege ein Barcode Objekt aus der Palette ein
- Waehle den Barcode-Typ (z.B. QR Code)
- Binde das Data-Feld an den XML-Pfad
- Konfiguriere Groesse und Fehlerkorrektur
XML mit Barcode-Daten
" Barcode-Daten in XML aufnehmenMETHOD add_barcode_data. DATA(lo_barcodes) = lo_doc->create_element( name = 'Barcodes' ). lo_root->append_child( lo_barcodes ).
" QR-Code mit Zahlungsinformationen (GiroCode/EPC QR) DATA(lo_qr) = lo_doc->create_element( name = 'PaymentQR' ). lo_qr->set_value( build_girocode( iv_iban = ls_invoice-bank_iban iv_bic = ls_invoice-bank_bic iv_recipient = ls_invoice-company_name iv_amount = ls_invoice-total iv_reference = ls_invoice-invoice_number ) ). lo_barcodes->append_child( lo_qr ).
" Code128 fuer Rechnungsnummer DATA(lo_barcode) = lo_doc->create_element( name = 'InvoiceBarcode' ). lo_barcode->set_value( ls_invoice-invoice_number ). lo_barcodes->append_child( lo_barcode ).ENDMETHOD.
" GiroCode (EPC QR Code) generierenMETHOD build_girocode. " EPC QR Code Format " Service Tag: BCD " Version: 002 " Character Set: 1 (UTF-8) " Identification: SCT (SEPA Credit Transfer)
rv_girocode = |BCD\n| && |002\n| && |1\n| && |SCT\n| && |{ iv_bic }\n| && |{ iv_recipient }\n| && |{ iv_iban }\n| && |EUR{ iv_amount }\n| && |\n| && " Purpose (leer) |{ iv_reference }\n| && " Reference |\n|. " Text (leer)ENDMETHOD.Dynamische Barcode-Generierung
" Barcode-Generator fuer verschiedene TypenCLASS zcl_barcode_generator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_barcode_data, type TYPE string, " QR, CODE128, EAN13, DATAMATRIX content TYPE string, width TYPE i, height TYPE i, END OF ty_barcode_data.
METHODS: " Tracking-Barcode fuer Pakete create_tracking_barcode IMPORTING iv_tracking_id TYPE string RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS: " QR-Code fuer Produktinfo-URL create_product_qr IMPORTING iv_product_id TYPE string RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS: " EPC/GiroCode fuer Zahlungen create_payment_qr IMPORTING iv_iban TYPE string iv_bic TYPE string iv_recipient TYPE string iv_amount TYPE p iv_currency TYPE string iv_reference TYPE string RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
ENDCLASS.
CLASS zcl_barcode_generator IMPLEMENTATION.
METHOD create_tracking_barcode. rs_barcode-type = 'CODE128'. rs_barcode-content = iv_tracking_id. rs_barcode-width = 200. rs_barcode-height = 50. ENDMETHOD.
METHOD create_product_qr. rs_barcode-type = 'QR'. " URL zur Produktseite rs_barcode-content = |https://products.example.com/{ iv_product_id }|. rs_barcode-width = 100. rs_barcode-height = 100. ENDMETHOD.
METHOD create_payment_qr. rs_barcode-type = 'QR'. " EPC/GiroCode Format rs_barcode-content = |BCD\n| && |002\n| && |1\n| && |SCT\n| && |{ iv_bic }\n| && |{ iv_recipient }\n| && |{ iv_iban }\n| && |{ iv_currency }{ iv_amount }\n| && |\n| && |{ iv_reference }|. rs_barcode-width = 150. rs_barcode-height = 150. ENDMETHOD.
ENDCLASS.RAP Integration
Die Integration in RAP ermoeglicht PDF-Generierung direkt aus Business Objects.
Behavior Definition
managed implementation in class ZBP_I_SALESORDER unique;strict ( 2 );
define behavior for ZI_SalesOrder alias SalesOrderpersistent table zsalesorderlock masterauthorization master ( instance ){ create; update; delete;
// PDF-Generierung als Action action generateInvoicePdf result [1] $self;
// Download Action mit Binary Result static action downloadInvoice parameter ZA_InvoiceDownloadParam result [1] ZA_PdfDownloadResult;
field ( readonly ) OrderUUID, CreatedAt, CreatedBy; field ( readonly : update ) OrderNumber;}Action Implementation
" RAP Behavior Implementation fuer PDF-GenerierungCLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION. METHODS generateInvoicePdf FOR MODIFY IMPORTING keys FOR ACTION SalesOrder~generateInvoicePdf RESULT result.
METHODS downloadInvoice FOR MODIFY IMPORTING keys FOR ACTION SalesOrder~downloadInvoice RESULT result.
METHODS: prepare_invoice_data IMPORTING iv_order_uuid TYPE sysuuid_x16 RETURNING VALUE(rs_invoice) TYPE zcl_invoice_form_data=>ty_invoice.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD generateInvoicePdf. " Auftragsdaten lesen READ ENTITIES OF zi_salesorder IN LOCAL MODE ENTITY SalesOrder ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order). " Rechnungsdaten aufbereiten DATA(ls_invoice) = prepare_invoice_data( ls_order-OrderUUID ).
TRY. " XML generieren DATA(lo_data_gen) = NEW zcl_invoice_form_data( ). DATA(lv_xml) = lo_data_gen->generate_xml( ls_invoice ).
" PDF rendern DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ). DATA(ls_pdf_result) = lo_renderer->render_pdf( iv_template_name = 'Z_INVOICE_TEMPLATE' iv_xml_data = lv_xml is_options = VALUE #( output_type = 'PDF' embed_fonts = abap_true ) ).
IF ls_pdf_result-success = abap_true. " PDF als Attachment speichern (optional) " Oder: Status aktualisieren
APPEND VALUE #( %tky = ls_order-%tky %param = ls_order ) TO result.
" Erfolgsmeldung APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = |Rechnung { ls_invoice-invoice_number } wurde generiert| ) ) TO reported-salesorder. ELSE. " Fehler vom Forms Service APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = ls_pdf_result-error_message ) ) TO reported-salesorder. ENDIF.
CATCH cx_http_dest_provider_error cx_web_http_client_error INTO DATA(lx_http). APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Verbindungsfehler: { lx_http->get_text( ) }| ) ) TO reported-salesorder. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD downloadInvoice. " Parameter auslesen DATA(ls_param) = keys[ 1 ]-%param.
" Auftrag laden SELECT SINGLE * FROM zsalesorder WHERE order_number = @ls_param-OrderNumber INTO @DATA(ls_order).
IF sy-subrc <> 0. APPEND VALUE #( %tky = keys[ 1 ]-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Auftrag { ls_param-OrderNumber } nicht gefunden| ) ) TO reported-salesorder. RETURN. ENDIF.
" Rechnungsdaten und PDF generieren DATA(ls_invoice) = prepare_invoice_data( ls_order-order_uuid ).
TRY. DATA(lo_data_gen) = NEW zcl_invoice_form_data( ). DATA(lv_xml) = lo_data_gen->generate_xml( ls_invoice ).
DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ). DATA(ls_pdf_result) = lo_renderer->render_pdf( iv_template_name = 'Z_INVOICE_TEMPLATE' iv_xml_data = lv_xml ).
IF ls_pdf_result-success = abap_true. " Ergebnis zurueckgeben APPEND VALUE #( %tky = keys[ 1 ]-%tky %param = VALUE #( FileName = |Rechnung_{ ls_param-OrderNumber }.pdf| MimeType = 'application/pdf' FileContent = ls_pdf_result-pdf_content FileSize = ls_pdf_result-file_size ) ) TO result. ENDIF.
CATCH cx_root 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-salesorder. ENDTRY. ENDMETHOD.
METHOD prepare_invoice_data. " Auftragskopf laden SELECT SINGLE * FROM zsalesorder WHERE order_uuid = @iv_order_uuid INTO @DATA(ls_order).
" Kundendaten laden SELECT SINGLE * FROM zcustomer WHERE customer_id = @ls_order-customer_id INTO @DATA(ls_customer).
" Positionen laden SELECT * FROM zsalesorder_item WHERE order_uuid = @iv_order_uuid ORDER BY position INTO TABLE @DATA(lt_items).
" Daten zusammenstellen rs_invoice-invoice_number = ls_order-order_number. rs_invoice-invoice_date = sy-datum. rs_invoice-due_date = sy-datum + 30.
rs_invoice-customer_number = ls_customer-customer_id. rs_invoice-customer_name = ls_customer-name. rs_invoice-customer_address = ls_customer-street. rs_invoice-customer_city = |{ ls_customer-postal_code } { ls_customer-city }|. rs_invoice-customer_country = ls_customer-country.
" Company-Daten (aus Customizing) rs_invoice-company_name = 'Meine Firma GmbH'. rs_invoice-company_address = 'Hauptstrasse 1'. rs_invoice-company_city = '12345 Berlin'. rs_invoice-company_country = 'Deutschland'. rs_invoice-company_vat_id = 'DE123456789'.
" Items konvertieren LOOP AT lt_items INTO DATA(ls_item). APPEND VALUE #( position = ls_item-position material_id = ls_item-material_id description = ls_item-description quantity = ls_item-quantity unit = ls_item-unit unit_price = ls_item-unit_price total = ls_item-quantity * ls_item-unit_price ) TO rs_invoice-items.
rs_invoice-subtotal = rs_invoice-subtotal + ls_item-quantity * ls_item-unit_price. ENDLOOP.
" Steuern berechnen rs_invoice-tax_rate = '19.00'. rs_invoice-tax_amount = rs_invoice-subtotal * rs_invoice-tax_rate / 100. rs_invoice-total = rs_invoice-subtotal + rs_invoice-tax_amount. rs_invoice-currency = 'EUR'.
" Zahlungsinformationen rs_invoice-payment_terms = 'Zahlbar innerhalb von 30 Tagen'. rs_invoice-bank_name = 'Sparkasse Berlin'. rs_invoice-bank_iban = 'DE89370400440532013000'. rs_invoice-bank_bic = 'COBADEFFXXX'. ENDMETHOD.
ENDCLASS.Lizenzierung und Kosten
SAP Forms Service by Adobe Lizenz
| Aspekt | Details |
|---|---|
| Lizenzmodell | Subscription-basiert auf SAP BTP |
| Preismetrik | Anzahl der API-Aufrufe pro Monat |
| Free Tier | Typischerweise 1.000 Renderings/Monat |
| Standard Tier | ~0,01-0,05 EUR pro Rendering (volumenabhaengig) |
| Enterprise | Individuelle Vereinbarung |
Kostenfaktoren
- Rendering-Volumen: Anzahl der PDF-Generierungen
- Dokumentgroesse: Anzahl Seiten beeinflusst Rendering-Zeit
- Template-Speicher: Speicherplatz fuer XDP-Templates
- Region: Preise variieren nach BTP-Region
Kostenoptimierung
" Caching von Templates und haeufigen PDFsCLASS zcl_pdf_cache DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: get_cached_pdf IMPORTING iv_cache_key TYPE string EXPORTING ev_pdf TYPE xstring ev_cache_hit TYPE abap_bool.
METHODS: cache_pdf IMPORTING iv_cache_key TYPE string iv_pdf TYPE xstring iv_ttl TYPE i DEFAULT 3600. " 1 Stunde
ENDCLASS.
" Verwendung: Standarddokumente cachenMETHOD generate_with_cache. " Cache-Key basierend auf Dokumentinhalt DATA(lv_hash) = calculate_hash( ls_data ). DATA(lv_cache_key) = |INVOICE_{ lv_hash }|.
" Cache pruefen DATA(lo_cache) = NEW zcl_pdf_cache( ). lo_cache->get_cached_pdf( EXPORTING iv_cache_key = lv_cache_key IMPORTING ev_pdf = rv_pdf ev_cache_hit = DATA(lv_hit) ).
IF lv_hit = abap_true. RETURN. " Cached PDF zurueckgeben ENDIF.
" Neu generieren rv_pdf = generate_pdf( ls_data ).
" Im Cache speichern lo_cache->cache_pdf( iv_cache_key = lv_cache_key iv_pdf = rv_pdf iv_ttl = 86400 " 24 Stunden ).ENDMETHOD.Fehlerbehandlung
Robuste PDF-Generierung
" PDF-Service mit umfassender FehlerbehandlungCLASS zcl_robust_forms_service DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_generation_result, success TYPE abap_bool, pdf_content TYPE xstring, error_code TYPE string, error_message TYPE string, retry_after TYPE i, " Sekunden bis Retry END OF ty_generation_result.
METHODS: generate_pdf_safe IMPORTING is_invoice TYPE zcl_invoice_form_data=>ty_invoice iv_template TYPE string iv_max_retries TYPE i DEFAULT 3 RETURNING VALUE(rs_result) TYPE ty_generation_result.
PRIVATE SECTION. CONSTANTS: " Fehlertypen gc_error_network TYPE string VALUE 'NETWORK', gc_error_timeout TYPE string VALUE 'TIMEOUT', gc_error_template TYPE string VALUE 'TEMPLATE', gc_error_data TYPE string VALUE 'DATA', gc_error_service TYPE string VALUE 'SERVICE', gc_error_quota TYPE string VALUE 'QUOTA'.
METHODS: classify_error IMPORTING iv_http_status TYPE i iv_error_message TYPE string EXPORTING ev_error_code TYPE string ev_is_retryable TYPE abap_bool.
METHODS: wait_with_backoff IMPORTING iv_attempt TYPE i.
ENDCLASS.
CLASS zcl_robust_forms_service IMPLEMENTATION.
METHOD generate_pdf_safe. DATA: lv_attempt TYPE i VALUE 0, lo_renderer TYPE REF TO zcl_forms_pdf_renderer.
" Data Generator DATA(lo_data_gen) = NEW zcl_invoice_form_data( ).
TRY. " XML-Daten generieren DATA(lv_xml) = lo_data_gen->generate_xml( is_invoice ). CATCH cx_root INTO DATA(lx_data). rs_result-success = abap_false. rs_result-error_code = gc_error_data. rs_result-error_message = lx_data->get_text( ). RETURN. ENDTRY.
" Renderer erstellen lo_renderer = NEW zcl_forms_pdf_renderer( ).
" Retry-Loop WHILE lv_attempt < iv_max_retries. lv_attempt = lv_attempt + 1.
TRY. DATA(ls_render_result) = lo_renderer->render_pdf( iv_template_name = iv_template iv_xml_data = lv_xml ).
IF ls_render_result-success = abap_true. " Erfolg rs_result-success = abap_true. rs_result-pdf_content = ls_render_result-pdf_content. RETURN. ELSE. " Service-Fehler analysieren classify_error( EXPORTING iv_http_status = 0 iv_error_message = ls_render_result-error_message IMPORTING ev_error_code = rs_result-error_code ev_is_retryable = DATA(lv_retryable) ).
IF lv_retryable = abap_false. " Nicht retry-faehiger Fehler rs_result-success = abap_false. rs_result-error_message = ls_render_result-error_message. RETURN. ENDIF. ENDIF.
CATCH cx_http_dest_provider_error INTO DATA(lx_dest). rs_result-error_code = gc_error_network. rs_result-error_message = lx_dest->get_text( ).
CATCH cx_web_http_client_error INTO DATA(lx_http). classify_error( EXPORTING iv_http_status = 0 " Nicht verfuegbar bei Exception iv_error_message = lx_http->get_text( ) IMPORTING ev_error_code = rs_result-error_code ev_is_retryable = lv_retryable ).
IF lv_retryable = abap_false. rs_result-success = abap_false. rs_result-error_message = lx_http->get_text( ). RETURN. ENDIF. ENDTRY.
" Warten vor naechstem Versuch IF lv_attempt < iv_max_retries. wait_with_backoff( lv_attempt ). ENDIF. ENDWHILE.
" Alle Versuche fehlgeschlagen rs_result-success = abap_false. rs_result-error_message = |PDF-Generierung nach { iv_max_retries } Versuchen fehlgeschlagen: | && rs_result-error_message. ENDMETHOD.
METHOD classify_error. " Fehlertyp aus HTTP-Status oder Message ableiten CASE iv_http_status. WHEN 429. ev_error_code = gc_error_quota. ev_is_retryable = abap_true. WHEN 500 OR 502 OR 503 OR 504. ev_error_code = gc_error_service. ev_is_retryable = abap_true. WHEN 400 OR 404. ev_error_code = gc_error_template. ev_is_retryable = abap_false. WHEN OTHERS. " Aus Message ableiten IF iv_error_message CS 'timeout' OR iv_error_message CS 'timed out'. ev_error_code = gc_error_timeout. ev_is_retryable = abap_true. ELSEIF iv_error_message CS 'template not found'. ev_error_code = gc_error_template. ev_is_retryable = abap_false. ELSE. ev_error_code = gc_error_network. ev_is_retryable = abap_true. ENDIF. ENDCASE. ENDMETHOD.
METHOD wait_with_backoff. " Exponential Backoff: 1s, 2s, 4s... DATA(lv_wait_seconds) = 2 ** ( iv_attempt - 1 ).
" In ABAP Cloud: Einfache Warteimplementierung " (Alternative: cl_abap_timers oder Background Processing) DATA(lv_end_time) = utclong_current( ) + CONV utclong( lv_wait_seconds ). WHILE utclong_current( ) < lv_end_time. " Warten ENDWHILE. ENDMETHOD.
ENDCLASS.Best Practices
Template-Design
| Empfehlung | Beschreibung |
|---|---|
| Modular | Wiederverwendbare Subforms fuer Header, Footer, Tabellen |
| Flexibel | Dynamische Tabellen statt fixer Zeilen |
| Testbar | Test-Datenset fuer alle Edge Cases |
| Wartbar | Klare Namenskonventionen fuer Felder |
Performance
- Template-Caching: Templates nicht bei jedem Aufruf hochladen
- Batch-Generierung: Mehrere PDFs in einem Request (wenn API unterstuetzt)
- Asynchron: Grosse PDFs im Hintergrund generieren
- Komprimierung: PDF-Komprimierung aktivieren
Sicherheit
- Sensible Daten: Keine Credentials in Templates
- Input-Validierung: XML/JSON-Daten vor Uebergabe validieren
- Zugriffskontrolle: RAP Authorization fuer PDF-Actions
- Audit: PDF-Generierungen protokollieren
Weiterfuehrende Themen
- PDF-Generierung in ABAP Cloud - Alternativen zu Adobe Forms
- HTTP Client - HTTP-Kommunikation mit BTP Services
- RAP Actions und Functions - Actions fuer PDF-Download
- Communication Scenarios - Konfiguration externer Services
Zusammenfassung
SAP Forms by Adobe in ABAP Cloud bietet professionelle PDF-Generierung:
- Service-Setup: Communication Arrangement mit OAuth 2.0 Credentials
- Template-Design: XDP-Templates mit Adobe LiveCycle Designer erstellen
- PDF-Rendering: REST API mit XML/JSON-Daten aufrufen
- Dynamische Tabellen: Automatischer Seitenumbruch und Gruppierung
- Barcode-Integration: QR-Codes fuer Zahlungen, Code128 fuer Tracking
- RAP-Integration: Actions fuer PDF-Generierung und Download
- Lizenzierung: Subscription-basiert, Kosten nach Rendering-Volumen
Der Service ersetzt klassische Adobe Forms auf BTP vollstaendig und ermoeglicht hochwertige Geschaeftsdokumente in der Cloud.