SAP Forms by Adobe est le service cloud natif pour des formulaires PDF professionnels sur SAP BTP. Il remplace les Adobe Forms classiques (Transaction SFP) et permet la génération de documents PDF de haute qualité avec contenus dynamiques, tableaux et codes-barres.
Vue d’ensemble et différences avec les Adobe Forms classiques
Dans les systèmes SAP On-Premise, tu utilises Adobe Forms via la transaction SFP avec Adobe LiveCycle Designer. Dans ABAP Cloud, cette approche n’est pas disponible. À la place, SAP propose le SAP Forms Service by Adobe comme service BTP.
Comparaison : Classique vs. Cloud
| Aspect | Adobe Forms classiques (SFP) | SAP Forms Service by Adobe |
|---|---|---|
| Accès | Transaction SFP, SE78 | API REST sur BTP |
| Conception template | Adobe LiveCycle Designer (local) | Adobe LiveCycle Designer (local) |
| Stockage template | Système SAP (Form Builder) | BTP Document Repository |
| Rendu | Adobe Document Services (ADS) | Moteur Adobe cloud |
| Licence | Licence SAP standard | Licence BTP séparée requise |
| Intégration | Modules fonction ABAP | HTTP/REST avec Communication Arrangement |
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐│ SAP Forms by Adobe - Architecture ││ ││ ┌────────────────────────────────────────────────────────────────────────┐ ││ │ ABAP Cloud Environment │ ││ │ │ ││ │ ┌─────────────────────┐ ┌─────────────────────┐ │ ││ │ │ Business Logic │────▶│ Forms Client │ │ ││ │ │ (RAP/Classes) │ │ (HTTP Client) │ │ ││ │ └─────────────────────┘ └──────────┬──────────┘ │ ││ │ │ │ ││ └──────────────────────────────────────────┼───────────────────────────────┘ ││ │ ││ │ HTTPS/REST ││ ▼ ││ ┌─────────────────────────────────────────────────────────────────────────┐ ││ │ SAP Forms Service by Adobe │ ││ │ │ ││ │ ┌─────────────────────┐ ┌─────────────────────┐ │ ││ │ │ Template Storage │ │ Adobe Rendering │ │ ││ │ │ (XDP Templates) │────▶│ Engine │ │ ││ │ └─────────────────────┘ └──────────┬──────────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────────────┐ │ ││ │ │ PDF Output │ │ ││ │ └─────────────────────┘ │ ││ └─────────────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘Configuration du service sur SAP BTP
Étape 1 : Activer le service
- Ouvre le SAP BTP Cockpit
- Navigue vers Service Marketplace
- Recherche SAP Forms Service by Adobe
- Crée une Service Instance
- Crée une Service Key pour les credentials
Étape 2 : Analyser la Service Key
La Service Key contient les credentials nécessaires :
{ "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" }}Étape 3 : Configurer Communication Arrangement
Crée un Communication Arrangement dans ABAP Cloud pour le 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>Application Fiori “Maintain Communication Arrangements” :
- Créer un Communication System avec l’URL Forms Service
- Saisir OAuth 2.0 Client Credentials de la Service Key
- Activer Communication Arrangement avec ton Scenario
Définition et upload de template
Les templates sont créés avec Adobe LiveCycle Designer et enregistrés comme fichiers XDP.
Structure template XDP
Un template XDP se compose de :
- Masterpage : Mise en page, en-têtes/pieds de page
- Body Pages : Contenu dynamique
- Data Schema : Structure XML pour les données
- Scripts : JavaScript pour la logique
Créer un template
- Ouvre Adobe LiveCycle Designer
- Crée un nouveau formulaire
- Définis la Data Connection avec schéma XML
- Conçois la mise en page avec champs, tableaux, images
- Enregistre comme fichier .xdp
Upload template via API
" Template Upload vers 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( ).
" Requête multipart pour upload template lo_request->set_header_field( i_name = 'Content-Type" i_value = 'application/octet-stream" ).
" Nom template comme paramètre Query lo_request->set_uri_path( |/v1/templates/{ iv_template_name }| ).
" Contenu template comme Body lo_request->set_binary( iv_template_content ).
" Requête PUT pour 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. " Parser réponse JSON DATA(lv_json) = lo_response->get_text( ). " Extraire templates depuis JSON " ... 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.Génération PDF avec données
La fonction centrale du Forms Service est la génération de PDF à partir de template et données.
Appeler l’API Rendering
" SAP Forms Service - Client de rendu PDFCLASS 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, " Millisecondes 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 ou json is_options TYPE ty_render_options RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_forms_pdf_renderer IMPLEMENTATION.
METHOD render_pdf. " Convertir données XML en Base64 DATA(lv_xml_base64) = cl_web_http_utility=>encode_base64( iv_xml_data ).
" Construire JSON requête 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 ).
" Créer client HTTP 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 ).
" Démarrer mesure temps DATA(lv_start) = utclong_current( ).
" Exécuter requête DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Terminer mesure temps DATA(lv_end) = utclong_current( ). rs_result-render_time = CONV i( utclong_diff( high = lv_end low = lv_start ) * 1000 ).
" Traiter réponse DATA(lv_status) = lo_response->get_status( ).
IF lv_status-code = 200. " Succès - extraire PDF DATA(lv_response_json) = lo_response->get_text( ).
" Parser JSON et extraire PDF Base64 " Extraction simplifiée : DATA lv_pdf_base64 TYPE string. " ... Parsing JSON ...
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. " Utiliser données JSON directement 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 ).
" ... (analogue à render_pdf) ENDMETHOD.
METHOD build_request_json. " Body requête pour API Forms Service 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.Préparer données de facture
" Générateur données XML pour 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. " Construire document XML DATA(lo_ixml) = cl_ixml=>create( ). DATA(lo_doc) = lo_ixml->create_document( ).
" Élément racine DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Namespace pour Adobe Forms lo_root->set_attribute( name = 'xmlns:xfa" value = 'http://www.xfa.org/schema/xfa-data/1.0/" ).
" === Section en-tête === 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 ).
" === Société (Expéditeur) === 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 ).
" === Client (Destinataire) === 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 ).
" === Articles (Postes) === 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.
" === 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_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 ).
" === Informations paiement === 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 vers 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. " Sérialisation JSON pour 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.Tableaux dynamiques dans les formulaires
Les tableaux dynamiques sont un élément central des formulaires professionnels. Ils croissent automatiquement avec les données.
Conception template pour tableaux
Dans Adobe LiveCycle Designer :
- Insère une Table depuis la palette
- Définis la table sur Dynamic (Propriétés > Pagination)
- Lie la ligne d’en-tête aux valeurs statiques
- Lie la ligne de corps au chemin de données répétitif (par ex.
$.Invoice.Items.Item[*])
Structure XML pour tableaux
<Invoice> <Items> <Item> <Position>1</Position> <Description>Produit A</Description> <Quantity>10</Quantity> <UnitPrice>99.00</UnitPrice> <Total>990.00</Total> </Item> <Item> <Position>2</Position> <Description>Produit B</Description> <Quantity>5</Quantity> <UnitPrice>149.00</UnitPrice> <Total>745.00</Total> </Item> <!-- Autres items... --> </Items></Invoice>Saut de page dans les tableaux
" Tableaux avec saut de page automatique" Dans le template : Table > Pagination = "Continue filling from previous page"
" ABAP : Préparer données - le template gère le sautMETHOD generate_multi_page_invoice. " Tous les articles dans une liste - Forms Service divise automatiquement sur pages 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.
" Générer XML DATA(lv_xml) = NEW zcl_invoice_form_data( )->generate_xml( ls_invoice ).
" Rendre PDF - saut de page automatique pour longs tableaux 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.Regroupement et sous-totaux
" Postes avec regroupement par catégorieTYPES: 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 avec groupes pour tableaux multi-niveaux DATA(lo_doc) = cl_ixml=>create( )->create_document( ). DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ). lo_doc->append_child( lo_root ).
" Catégories 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 ).
" Articles de la catégorie LOOP AT ls_group-items INTO DATA(ls_item). DATA(lo_item) = lo_doc->create_element( name = 'Item' ). lo_items->append_child( lo_item ). " ... Champs item ... ENDLOOP.
" Sous-total DATA(lo_subtotal) = lo_doc->create_element( name = 'Subtotal' ). lo_subtotal->set_value( |{ ls_group-subtotal }| ). lo_category->append_child( lo_subtotal ). ENDLOOP.
" XML vers xstring " ...ENDMETHOD.Intégration codes-barres dans les formulaires
Les codes-barres et QR codes sont importants pour le traitement automatisé et le suivi.
Types de codes-barres supportés
| Type | Description | Application |
|---|---|---|
| Code 128 | Alphanumérique, variable | Général, Logistique |
| EAN-13 | 13 chiffres | Identification produit |
| QR Code | 2D, haute capacité | URLs, Données paiement |
| Data Matrix | 2D, compact | Applications industrielles |
| PDF417 | 2D, haute densité | Cartes d’identité, Documents transport |
Code-barres dans le template
Dans Adobe LiveCycle Designer :
- Insère un objet Barcode depuis la palette
- Choisis le type de code-barres (par ex. QR Code)
- Lie le champ Data au chemin XML
- Configure la taille et correction d’erreurs
XML avec données code-barres
" Données code-barres dans XMLMETHOD add_barcode_data. DATA(lo_barcodes) = lo_doc->create_element( name = 'Barcodes' ). lo_root->append_child( lo_barcodes ).
" QR Code avec informations paiement (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 pour numéro facture DATA(lo_barcode) = lo_doc->create_element( name = 'InvoiceBarcode' ). lo_barcode->set_value( ls_invoice-invoice_number ). lo_barcodes->append_child( lo_barcode ).ENDMETHOD.
" Générer GiroCode (EPC QR Code)METHOD build_girocode. " Format EPC QR Code " 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 (vide) |{ iv_reference }\n| && " Reference |\n|. " Text (vide)ENDMETHOD.Génération dynamique code-barres
" Générateur code-barres pour différents typesCLASS 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: " Code-barres suivi pour colis create_tracking_barcode IMPORTING iv_tracking_id TYPE string RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS: " QR Code pour URL info produit create_product_qr IMPORTING iv_product_id TYPE string RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS: " EPC/GiroCode pour paiements 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 vers page produit 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'. " Format EPC/GiroCode 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.Intégration RAP
L’intégration dans RAP permet la génération PDF directement depuis les 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;
// Génération PDF comme Action action generateInvoicePdf result [1] $self;
// Action Download avec Binary Result static action downloadInvoice parameter ZA_InvoiceDownloadParam result [1] ZA_PdfDownloadResult;
field ( readonly ) OrderUUID, CreatedAt, CreatedBy; field ( readonly : update ) OrderNumber;}Implémentation Action
" Implémentation RAP Behavior pour génération PDFCLASS 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. " Lire données commande 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). " Préparer données facture DATA(ls_invoice) = prepare_invoice_data( ls_order-OrderUUID ).
TRY. " Générer XML DATA(lo_data_gen) = NEW zcl_invoice_form_data( ). DATA(lv_xml) = lo_data_gen->generate_xml( ls_invoice ).
" Rendre PDF 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. " Sauvegarder PDF comme Attachment (optionnel) " Ou : Actualiser statut
APPEND VALUE #( %tky = ls_order-%tky %param = ls_order ) TO result.
" Message succès APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = |Facture { ls_invoice-invoice_number } a été générée| ) ) TO reported-salesorder. ELSE. " Erreur depuis 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 = |Erreur connexion : { lx_http->get_text( ) }| ) ) TO reported-salesorder. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD downloadInvoice. " Lire paramètres DATA(ls_param) = keys[ 1 ]-%param.
" Charger commande 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 = |Commande { ls_param-OrderNumber } non trouvée| ) ) TO reported-salesorder. RETURN. ENDIF.
" Données facture et générer PDF 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. " Retourner résultat APPEND VALUE #( %tky = keys[ 1 ]-%tky %param = VALUE #( FileName = |Facture_{ 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. " Charger en-tête commande SELECT SINGLE * FROM zsalesorder WHERE order_uuid = @iv_order_uuid INTO @DATA(ls_order).
" Charger données client SELECT SINGLE * FROM zcustomer WHERE customer_id = @ls_order-customer_id INTO @DATA(ls_customer).
" Charger postes SELECT * FROM zsalesorder_item WHERE order_uuid = @iv_order_uuid ORDER BY position INTO TABLE @DATA(lt_items).
" Assembler données 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.
" Données société (depuis Customizing) rs_invoice-company_name = 'Ma Société SARL'. rs_invoice-company_address = 'Rue Principale 1'. rs_invoice-company_city = '75001 Paris'. rs_invoice-company_country = 'France'. rs_invoice-company_vat_id = 'FR123456789'.
" Convertir articles 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.
" Calculer taxes rs_invoice-tax_rate = '20.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'.
" Informations paiement rs_invoice-payment_terms = 'Payable sous 30 jours'. rs_invoice-bank_name = 'Banque de Paris'. rs_invoice-bank_iban = 'FR7630001007941234567890185'. rs_invoice-bank_bic = 'BNPAFRPPXXX'. ENDMETHOD.
ENDCLASS.Licences et coûts
Licence SAP Forms Service by Adobe
| Aspect | Détails |
|---|---|
| Modèle licence | Basé sur abonnement sur SAP BTP |
| Métrique prix | Nombre d’appels API par mois |
| Free Tier | Typiquement 1000 rendus/mois |
| Standard Tier | ~0,01-0,05 EUR par rendu (selon volume) |
| Enterprise | Accord individuel |
Facteurs de coût
- Volume rendu : Nombre de générations PDF
- Taille document : Nombre de pages influence temps rendu
- Stockage template : Espace stockage pour templates XDP
- Région : Prix varient selon région BTP
Optimisation des coûts
" Mise en cache templates et PDFs fréquentsCLASS 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 heure
ENDCLASS.
" Utilisation : Mettre en cache documents standardsMETHOD generate_with_cache. " Clé cache basée sur contenu document DATA(lv_hash) = calculate_hash( ls_data ). DATA(lv_cache_key) = |INVOICE_{ lv_hash }|.
" Vérifier cache 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. " Retourner PDF en cache ENDIF.
" Générer nouveau rv_pdf = generate_pdf( ls_data ).
" Sauvegarder dans cache lo_cache->cache_pdf( iv_cache_key = lv_cache_key iv_pdf = rv_pdf iv_ttl = 86400 " 24 heures ).ENDMETHOD.Gestion des erreurs
Génération PDF robuste
" Service PDF avec gestion erreurs complèteCLASS 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, " Secondes avant réessai 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: " Types erreur 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.
" Générateur données DATA(lo_data_gen) = NEW zcl_invoice_form_data( ).
TRY. " Générer données XML 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.
" Créer renderer lo_renderer = NEW zcl_forms_pdf_renderer( ).
" Boucle réessai 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. " Succès rs_result-success = abap_true. rs_result-pdf_content = ls_render_result-pdf_content. RETURN. ELSE. " Analyser erreur service 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. " Erreur non réessayable 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 " Non disponible en cas d'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.
" Attendre avant prochain essai IF lv_attempt < iv_max_retries. wait_with_backoff( lv_attempt ). ENDIF. ENDWHILE.
" Tous les essais ont échoué rs_result-success = abap_false. rs_result-error_message = |Génération PDF échouée après { iv_max_retries } tentatives : | && rs_result-error_message. ENDMETHOD.
METHOD classify_error. " Dériver type erreur depuis statut HTTP ou message 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. " Dériver depuis message 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 ).
" Dans ABAP Cloud : Implémentation attente simple " (Alternative : cl_abap_timers ou Background Processing) DATA(lv_end_time) = utclong_current( ) + CONV utclong( lv_wait_seconds ). WHILE utclong_current( ) < lv_end_time. " Attendre ENDWHILE. ENDMETHOD.
ENDCLASS.Bonnes pratiques
Conception template
| Recommandation | Description |
|---|---|
| Modulaire | Subforms réutilisables pour en-tête, pied de page, tableaux |
| Flexible | Tableaux dynamiques plutôt que lignes fixes |
| Testable | Jeu de données test pour tous les cas limites |
| Maintenable | Conventions de nommage claires pour les champs |
Performance
- Mise en cache template : Ne pas uploader les templates à chaque appel
- Génération batch : Plusieurs PDFs en une requête (si API le supporte)
- Asynchrone : Générer gros PDFs en arrière-plan
- Compression : Activer compression PDF
Sécurité
- Données sensibles : Pas de credentials dans les templates
- Validation entrée : Valider données XML/JSON avant transfert
- Contrôle accès : RAP Authorization pour actions PDF
- Audit : Journaliser les générations PDF
Sujets complémentaires
- Génération PDF dans ABAP Cloud - Alternatives à Adobe Forms
- Client HTTP - Communication HTTP avec services BTP
- Actions et Functions RAP - Actions pour téléchargement PDF
- Scénarios de Communication - Configuration services externes
Résumé
SAP Forms by Adobe dans ABAP Cloud offre une génération PDF professionnelle :
- Configuration service : Communication Arrangement avec OAuth 2.0 Credentials
- Conception template : Créer templates XDP avec Adobe LiveCycle Designer
- Rendu PDF : Appeler API REST avec données XML/JSON
- Tableaux dynamiques : Saut de page automatique et regroupement
- Intégration codes-barres : QR Codes pour paiements, Code128 pour suivi
- Intégration RAP : Actions pour génération et téléchargement PDF
- Licences : Basé sur abonnement, coûts selon volume de rendu