SAP Forms by Adobe - Formulaires PDF dans ABAP Cloud

Catégorie
Integration
Publié
Auteur
Johannes

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

AspectAdobe Forms classiques (SFP)SAP Forms Service by Adobe
AccèsTransaction SFP, SE78API REST sur BTP
Conception templateAdobe LiveCycle Designer (local)Adobe LiveCycle Designer (local)
Stockage templateSystème SAP (Form Builder)BTP Document Repository
RenduAdobe Document Services (ADS)Moteur Adobe cloud
LicenceLicence SAP standardLicence BTP séparée requise
IntégrationModules fonction ABAPHTTP/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

  1. Ouvre le SAP BTP Cockpit
  2. Navigue vers Service Marketplace
  3. Recherche SAP Forms Service by Adobe
  4. Crée une Service Instance
  5. 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” :

  1. Créer un Communication System avec l’URL Forms Service
  2. Saisir OAuth 2.0 Client Credentials de la Service Key
  3. 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

  1. Ouvre Adobe LiveCycle Designer
  2. Crée un nouveau formulaire
  3. Définis la Data Connection avec schéma XML
  4. Conçois la mise en page avec champs, tableaux, images
  5. Enregistre comme fichier .xdp

Upload template via API

" Template Upload vers Forms Service
CLASS 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 PDF
CLASS 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 Service
CLASS 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 :

  1. Insère une Table depuis la palette
  2. Définis la table sur Dynamic (Propriétés > Pagination)
  3. Lie la ligne d’en-tête aux valeurs statiques
  4. 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 saut
METHOD 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égorie
TYPES:
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

TypeDescriptionApplication
Code 128Alphanumérique, variableGénéral, Logistique
EAN-1313 chiffresIdentification produit
QR Code2D, haute capacitéURLs, Données paiement
Data Matrix2D, compactApplications industrielles
PDF4172D, haute densitéCartes d’identité, Documents transport

Code-barres dans le template

Dans Adobe LiveCycle Designer :

  1. Insère un objet Barcode depuis la palette
  2. Choisis le type de code-barres (par ex. QR Code)
  3. Lie le champ Data au chemin XML
  4. Configure la taille et correction d’erreurs

XML avec données code-barres

" Données code-barres dans XML
METHOD 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 types
CLASS 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 SalesOrder
persistent table zsalesorder
lock master
authorization 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 PDF
CLASS 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

AspectDétails
Modèle licenceBasé sur abonnement sur SAP BTP
Métrique prixNombre d’appels API par mois
Free TierTypiquement 1000 rendus/mois
Standard Tier~0,01-0,05 EUR par rendu (selon volume)
EnterpriseAccord individuel

Facteurs de coût

  1. Volume rendu : Nombre de générations PDF
  2. Taille document : Nombre de pages influence temps rendu
  3. Stockage template : Espace stockage pour templates XDP
  4. Région : Prix varient selon région BTP

Optimisation des coûts

" Mise en cache templates et PDFs fréquents
CLASS 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 standards
METHOD 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ète
CLASS 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

RecommandationDescription
ModulaireSubforms réutilisables pour en-tête, pied de page, tableaux
FlexibleTableaux dynamiques plutôt que lignes fixes
TestableJeu de données test pour tous les cas limites
MaintenableConventions de nommage claires pour les champs

Performance

  1. Mise en cache template : Ne pas uploader les templates à chaque appel
  2. Génération batch : Plusieurs PDFs en une requête (si API le supporte)
  3. Asynchrone : Générer gros PDFs en arrière-plan
  4. Compression : Activer compression PDF

Sécurité

  1. Données sensibles : Pas de credentials dans les templates
  2. Validation entrée : Valider données XML/JSON avant transfert
  3. Contrôle accès : RAP Authorization pour actions PDF
  4. Audit : Journaliser les générations PDF

Sujets complémentaires

Résumé

SAP Forms by Adobe dans ABAP Cloud offre une génération PDF professionnelle :

  1. Configuration service : Communication Arrangement avec OAuth 2.0 Credentials
  2. Conception template : Créer templates XDP avec Adobe LiveCycle Designer
  3. Rendu PDF : Appeler API REST avec données XML/JSON
  4. Tableaux dynamiques : Saut de page automatique et regroupement
  5. Intégration codes-barres : QR Codes pour paiements, Code128 pour suivi
  6. Intégration RAP : Actions pour génération et téléchargement PDF
  7. Licences : Basé sur abonnement, coûts selon volume de rendu