La generation de PDF dans ABAP Cloud differe fondamentalement de l’environnement On-Premise. Les Adobe Forms classiques (SFP) et SmartForms ne sont pas disponibles sur SAP BTP. A la place, vous utilisez des alternatives Cloud-native comme XSL-FO, le SAP Forms Service by Adobe ou des services PDF externes.
Apercu des options disponibles
Dans ABAP Cloud, il existe plusieurs methodes pour generer des PDF :
| Option | Description | Cas d’utilisation |
|---|---|---|
| XSL-FO | Transformation XML vers PDF | Documents simples a moyens |
| SAP Forms Service by Adobe | Service Adobe base sur le Cloud | Formulaires complexes avec templates Adobe |
| Services PDF externes | APIs PDF basees sur HTTP | Flexibilite, exigences speciales |
| SAP Document Management | Integration avec DMS | Workflows documentaires |
Vue d’ensemble de l’architecture
┌─────────────────────────────────────────────────────────────────────────────┐│ Generation de PDF dans ABAP Cloud ││ ││ ┌────────────────────────────────────────────────────────────────────────┐ ││ │ RAP Business Object / Service │ ││ │ │ ││ │ ┌─────────────────────┐ │ ││ │ │ RAP Action │ │ ││ │ │ "Generate PDF" │ │ ││ │ └──────────┬──────────┘ │ ││ └─────────────┼────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ Options de generation PDF │ ││ │ │ ││ │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ ││ │ │ XSL-FO │ │ SAP Forms │ │ APIs externes │ │ ││ │ │ │ │ Service │ │ (Services PDF) │ │ ││ │ │ - XML + XSLT │ │ - Adobe Templates│ │ - REST APIs │ │ ││ │ │ - cl_xslt_proc │ │ - BTP Service │ │ - HTTP Client │ │ ││ │ └───────────────────┘ └───────────────────┘ └───────────────────┘ │ ││ └─────────────────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ Sortie PDF │ ││ │ - Telechargement via RAP Action Result │ ││ │ - Piece jointe au Business Object │ ││ │ - Envoi par email │ ││ └─────────────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘XSL-FO : Generation PDF basee sur XML
XSL-FO (Extensible Stylesheet Language Formatting Objects) est une technique standard W3C pour la transformation XML vers des documents pagines comme les PDF.
Principe de base
┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ Donnees │ + │ Feuille de │ = │ PDF ││ XML │ │ style XSL-FO│ │ │└──────────────┘ └──────────────┘ └──────────────┘Structure de donnees XML
D’abord, vous definissez une structure XML pour vos donnees de document :
" Generer les donnees XML pour la factureCLASS 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. " Construire le document XML DATA(lo_doc) = cl_ixml=>create( )->create_document( ).
" Element racine DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Elements d'en-tete 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 ).
" Donnees client 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 ).
" Lignes 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.
" Totaux 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 en chaine 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.Feuille de style XSL-FO
La feuille de style XSL-FO definit la mise en page de votre PDF :
<?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> <!-- Definir la mise en page --> <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"> <!-- En-tete --> <fo:static-content flow-name="xsl-region-before"> <fo:block font-size="18pt" font-weight="bold" text-align="center"> FACTURE </fo:block> </fo:static-content>
<!-- Pied de page --> <fo:static-content flow-name="xsl-region-after"> <fo:block font-size="8pt" text-align="center"> Page <fo:page-number/> sur <fo:page-number-citation ref-id="last-page"/> </fo:block> </fo:static-content>
<!-- Contenu principal --> <fo:flow flow-name="xsl-region-body"> <!-- En-tete de facture --> <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">Client :</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>Numero de facture : <xsl:value-of select="Header/InvoiceNumber"/></fo:block> <fo:block>Date : <xsl:value-of select="Header/InvoiceDate"/></fo:block> </fo:table-cell> </fo:table-row> </fo:table-body> </fo:table> </fo:block>
<!-- Lignes --> <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>Description</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Quantite</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Prix unitaire</fo:block> </fo:table-cell> <fo:table-cell border="1pt solid black" padding="2mm" text-align="right"> <fo:block>Total</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>
<!-- Totaux --> <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>Sous-total :</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>TVA (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>Montant total :</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>
<!-- Marquer la derniere page pour la numerotation --> <fo:block id="last-page"/> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template>
</xsl:stylesheet>Transformer XSL-FO en PDF
" Generateur PDF avec 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. " Transformation XSL-FO " Note : La generation directe de PDF a partir de XSL-FO est " limitee dans ABAP Cloud. Pour les scenarios de production, il est " recommande d'utiliser un service FOP externe (Apache FOP) ou SAP Forms Service.
" Alternative : Transformation vers sortie XSL-FO DATA lv_xsl_fo_result TYPE xstring.
CALL TRANSFORMATION id SOURCE XML iv_xml RESULT XML lv_xsl_fo_result.
" La sortie XSL-FO doit ensuite etre envoyee a un processeur FOP " Cela se fait generalement via un appel HTTP a un service externe rv_pdf = call_fop_service( lv_xsl_fo_result ). ENDMETHOD.
METHOD generate_invoice_pdf. " Generer la facture en PDF 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. " Lire le template XSL-FO depuis le repository " Dans ABAP Cloud : depuis le MIME Repository ou comme constante 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
Le SAP Forms Service by Adobe est la solution Cloud-native pour les formulaires complexes. Il permet l’utilisation de templates bases sur Adobe sur SAP BTP.
Apercu du service
| Aspect | Description |
|---|---|
| Service | SAP Forms Service by Adobe |
| Protocole | REST API (JSON/XML) |
| Templates | XDP (Adobe LiveCycle Designer) |
| Sortie | PDF, Print Stream |
| Licence | Licence BTP separee requise |
Architecture
┌────────────────────────────────────────────────────────────────────────────┐│ 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) │ │ ││ │ └─────────────┘ │ │ └─────────────────────────────────┘ │ ││ └───────────────────┘ └───────────────────────────────────────┘ │└────────────────────────────────────────────────────────────────────────────┘Configuration du Communication Arrangement
- Communication Scenario - Creer pour SAP Forms Service
- Communication System - Configurer avec l’URL du BTP Forms Service
- Communication Arrangement - Avec les credentials OAuth 2.0
Exemple de code : Appeler le Forms Service
" Client SAP Forms ServiceCLASS 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 ou 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. " Creer le client HTTP DATA(lo_client) = get_http_client( ).
TRY. " Preparer la requete DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_header_field( i_name = 'Content-Type" i_value = 'application/json" ).
" Corps de la requete (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 ).
" Appeler l'API DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Traiter la reponse 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 depuis le 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.Preparer les donnees du template
" Constructeur de donnees de formulaire en JSON pour 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. " Structure JSON pour le template Adobe Form DATA(lo_json) = NEW /ui2/cl_json( ).
" Construire la structure de donnees 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.
" En-tete CREATE DATA ls_form_data-header TYPE STANDARD TABLE OF string WITH EMPTY KEY. " ... remplir les donnees
" Serialiser en JSON rv_json = /ui2/cl_json=>serialize( data = ls_form_data compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). ENDMETHOD.
ENDCLASS.Services PDF externes
Pour une flexibilite maximale, vous pouvez utiliser des APIs PDF externes. Les options populaires sont :
| Service | Description | Avantages |
|---|---|---|
| PDFShift | API HTML vers PDF | Simple, flexible |
| DocRaptor | HTML/CSS vers PDF | Bon support CSS |
| Prince | XML/HTML vers PDF | Excellente typographie |
| AWS Textract | Traitement PDF | Integration AWS |
Exemple de code : Utiliser un service PDF externe
" Client de service PDF externe (ex. 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 depuis le 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( ).
" Definir les en-tetes 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 }| ).
" Corps de la requete 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 ).
" Appeler l'API 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.Template HTML pour PDF
" Generation PDF basee sur HTMLCLASS 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. " Template HTML avec CSS inline pour 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>FACTURE</h1>| && |<div style="display: flex; justify-content: space-between;">| && |<div>| && |<strong>Client :</strong><br>| && |{ is_invoice-customer_name }<br>| && |{ is_invoice-customer_addr }| && |</div>| && |<div style="text-align: right;">| && |<strong>Numero de facture :</strong> { is_invoice-invoice_number }<br>| && |<strong>Date :</strong> { is_invoice-invoice_date DATE = USER }| && |</div>| && |</div>|.
" Lignes rv_html = rv_html && |<table>| && |<tr><th>Pos.</th><th>Description</th><th>Quantite</th>| && |<th>Prix unitaire</th><th>Total</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>|.
" Totaux rv_html = rv_html && |<table class="totals">| && |<tr><td>Sous-total :</td><td>{ is_invoice-subtotal } EUR</td></tr>| && |<tr><td>TVA (19%) :</td><td>{ is_invoice-tax } EUR</td></tr>| && |<tr class="grand-total">| && |<td>Montant total :</td><td>{ is_invoice-total } EUR</td></tr>| && |</table>| && |</body></html>|. ENDMETHOD.
ENDCLASS.RAP Action pour la generation de PDF
La meilleure integration se fait via une RAP Action qui genere les PDF directement dans le Business Object.
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;
// Generation PDF comme Action action generatePdf result [1] $self;
// Static Factory Action pour le telechargement static action downloadInvoicePdf parameter ZA_InvoiceDownload result [1] ZA_PdfResult;
field ( readonly ) InvoiceUUID, CreatedAt, CreatedBy;}Parametres et resultat de l’Action
-- Parametre pour l'Action de telechargement@EndUserText.label: 'Parametre de telechargement de facture"define abstract entity ZA_InvoiceDownload{ InvoiceNumber : abap.char(10); OutputFormat : abap.char(10); // PDF, HTML}
-- Resultat avec PDF binaire@EndUserText.label: 'Resultat PDF"define abstract entity ZA_PdfResult{ FileName : abap.char(100); MimeType : abap.char(50); FileContent : abap.rawstring(0); // XSTRING pour binaire}Implementation du Behavior
" Implementation RAP Behavior avec Action PDFCLASS 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. " Lire les donnees de facture 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). " Preparer les donnees pour le PDF DATA(ls_pdf_data) = build_invoice_data( CORRESPONDING #( ls_invoice ) ).
" Generer le PDF TRY. DATA(lo_pdf_gen) = NEW zcl_pdf_generator( ). DATA(lv_pdf) = lo_pdf_gen->generate_invoice_pdf( ls_pdf_data ).
" Sauvegarder le PDF en piece jointe (optionnel) " Ou : renvoyer dans le result
" Message de succes APPEND VALUE #( %tky = ls_invoice-%tky %param = ls_invoice ) TO result.
CATCH cx_transformation_error INTO DATA(lx_error). " Signaler l'erreur 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. " Lire le parametre DATA(ls_param) = keys[ 1 ]-%param.
" Charger la facture 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 = |Facture { ls_param-InvoiceNumber } non trouvee| ) ) TO reported-invoice. RETURN. ENDIF.
" Generer le PDF 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 ).
" Renvoyer le resultat APPEND VALUE #( %tky = keys[ 1 ]-%tky %param = VALUE #( FileName = |Facture_{ 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. " Preparer les donnees de facture pour le PDF 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.
" Charger les lignes 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.
" Calculer les totaux 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.Gestion des erreurs
Exception personnalisee pour la generation PDF
" Exception de generation PDFCLASS 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.Generation PDF robuste
" Service PDF avec logique de retryCLASS 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. " Generer le PDF 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. " Succes ELSE. " Erreur de service 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). " Erreur reseau - Retry possible lx_last_error = lx_http. ENDTRY.
" Attendre avant le retry IF lv_attempts < iv_max_retries. DATA(lv_wait) = gc_retry_delay_ms * lv_attempts. " Dans ABAP Cloud : utiliser cl_abap_timers ENDIF. ENDWHILE.
" Toutes les tentatives ont echoue RAISE EXCEPTION TYPE zcx_pdf_generation_error EXPORTING textid = zcx_pdf_generation_error=>api_error previous = lx_last_error mv_message = |Generation PDF echouee apres { iv_max_retries } tentatives|. ENDMETHOD.
ENDCLASS.Alternatives et solutions de contournement
Option 1 : Export HTML au lieu de PDF
Pour des documents simples, un export HTML peut suffire :
" HTML comme alternative au 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. " Document HTML complet avec CSS optimise pour l'impression 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()">Imprimer</button>| && |<!-- Contenu du document -->| && |</body>| && |</html>|. ENDMETHOD.
METHOD get_as_data_uri. " HTML comme Data URI pour telechargement direct 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 : Export Excel comme alternative
" Export Excel pour les donnees tabulairesCLASS 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 : Generation PDF asynchrone
Pour les gros documents ou la generation en masse :
" Generation PDF asynchrone avec 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.Bonnes pratiques
Recommandations pour la generation PDF
| Aspect | Recommandation |
|---|---|
| Documents simples | HTML vers PDF avec service externe |
| Formulaires complexes | SAP Forms Service by Adobe |
| Volumes eleves | Generation asynchrone avec bgPF |
| Scenarios hors ligne | Export HTML avec CSS Print |
| Conformite | Implementer la journalisation des acces |
Conseils de performance
- Mise en cache : Mettre en cache les templates et contenus statiques
- Traitement par lots : Generer plusieurs PDF en une seule requete
- Compression : Activer la compression PDF
- Asynchrone : Generer les gros PDF en arriere-plan
Sujets connexes
- Client HTTP - Communication HTTP pour les services externes
- RAP Actions et Functions - Actions pour le telechargement PDF
- Jobs en arriere-plan - Generation PDF asynchrone
- Gestion de la sortie - Sortie d’impression dans le Cloud
Resume
La generation de PDF dans ABAP Cloud necessite de nouvelles approches :
- XSL-FO pour la transformation basee sur XML (avec processeur FOP externe)
- SAP Forms Service by Adobe pour les formulaires complexes avec templates Adobe
- APIs PDF externes pour une flexibilite maximale (PDFShift, DocRaptor)
- RAP Actions integrent la generation PDF de maniere transparente dans les Business Objects
- Gestion des erreurs avec logique de retry pour une production robuste
Le choix de la bonne option depend de vos exigences : complexite des documents, templates existants, volume et budget.