We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
SAP-Integration
Anleitung zur Anbindung von SAP ECC und S/4HANA an die e-document.io API. IDoc INVOIC02 erzeugen, PDF generieren und beides automatisch an die API senden.
Übersicht
e-document.io empfängt SAP IDoc INVOIC02 und optionale Rechnungs-PDFs über eine REST API. Die Konvertierung in XRechnung (CII, UBL oder ZUGFeRD) erfolgt automatisch. Der Versand läuft über Peppol oder E-Mail — je nach Geschäftspartner-Konfiguration.
1 IDoc erzeugen
Nachrichtensteuerung erzeugt IDoc INVOIC02 bei Faktura-Buchung.
2 PDF erzeugen
SAPscript/Smart Forms/Adobe Forms generiert Rechnungs-PDF.
3 API senden
ABAP-Klasse sendet IDoc-XML + PDF per base64 an die API.
Voraussetzungen
| Komponente | Beschreibung |
|---|---|
API-Key |
Im Dashboard unter API-Keys erstellen. Beginnt mit edoc_ |
Org-ID |
Ihre Organisations-ID (sichtbar im Dashboard unter Einstellungen) |
| Geschäftspartner | Partner mit GP-Nummer anlegen, die der SAP-Kundennummer (KUNNR) entspricht |
| RFC-Destination | SM59 → Typ G → Host: e-document.io, Port 443, SSL aktiv, Pfadpräfix: /api/v1 |
| IDoc-Basis | Nachrichtenart, Partnervereinbarung und Port für INVOIC02 konfiguriert (WE20/WE21) |
| Formular | SAPscript, Smart Form oder Adobe Form für die Rechnungsausgabe (z.B. RVORDER01) |
Architektur
Der gesamte Prozess wird durch die SAP-Nachrichtensteuerung (NAST) ausgelöst:
SAP-Faktura (VF01/VF02)
│
├── Nachrichtensteuerung (NACE/NAST)
│ │
│ ├── Nachricht ZE01: IDoc INVOIC02 erzeugen
│ │ └── IDOC_OUTPUT_INVOIC02 → IDoc-XML
│ │
│ └── Nachricht ZE02: PDF erzeugen
│ └── Smart Form → OTF → PDF
│
├── Z-Programm: ABAP-Klasse ZCL_EDOCUMENT_CLIENT
│ │
│ ├── IDoc-XML + PDF → Base64 kodieren
│ │
│ └── POST /api/v1/invoices/send
│ {
│ "document_type": "idoc",
│ "xml": "base64...",
│ "pdf": "base64...",
│ "recipient": {"partner_number": "KUNNR"}
│ }
│
▼
e-document.io
├── IDoc → XRechnung (CII/UBL/ZUGFeRD) konvertieren
├── Format nach Empfänger-Präferenz wählen
└── Versand via Peppol oder E-Mail
Nachrichtensteuerung (NACE)
Die Nachrichtensteuerung löst bei jeder Faktura-Buchung automatisch die IDoc- und PDF-Erzeugung aus. Konfiguration über Transaktion NACE, Anwendung V3 (Faktura).
Schritt 1: Nachrichtenart anlegen
| Feld | Wert | Beschreibung |
|---|---|---|
| Transaktion | NACE |
Nachrichtensteuerung pflegen |
| Anwendung | V3 |
Faktura |
| Nachrichtenart | ZE01 |
E-Rechnung via e-document.io |
| Versandart | 1 (Sofort) |
Wird bei Faktura-Buchung sofort ausgeführt |
Schritt 2: Verarbeitungsroutine zuordnen
In NACE unter der Nachrichtenart ZE01 die Verarbeitungsroutine pflegen:
| Feld | Wert |
|---|---|
| Programm (Formular) | ZEDOCUMENT_SEND_INVOICE |
| FORM-Routine | ENTRY_EDOCUMENT |
| Medium | 8 (Sonderfunktion) |
Schritt 3: Konditionssätze pflegen
In Transaktion VV31 (oder MN04) die Konditionssätze für die Nachrichtenart ZE01 pflegen. Zuordnung pro Verkaufsorganisation/Kunde oder generell für alle Fakturen.
IDoc INVOIC02 erzeugen
Das IDoc INVOIC02 enthält alle Rechnungsdaten (Kopf, Positionen, Partner, Steuern, Bankverbindung). Es wird im ABAP-Programm direkt über den Funktionsbaustein erzeugt.
IDoc INVOIC02 aus Faktura erzeugen
*&---------------------------------------------------------------------*
*& IDoc INVOIC02 aus Fakturabeleg erzeugen
*&---------------------------------------------------------------------*
METHOD generate_idoc_xml.
" iv_vbeln: Fakturanummer
" rv_xml: IDoc als XML-String (RETURNING)
DATA: ls_control TYPE edidc,
lt_data TYPE TABLE OF edidd,
lt_idocs TYPE TABLE OF edidc,
lv_docnum TYPE edi_docnum.
" Faktura lesen
SELECT SINGLE * FROM vbrk INTO @DATA(ls_vbrk)
WHERE vbeln = @iv_vbeln.
CHECK sy-subrc = 0.
SELECT * FROM vbrp INTO TABLE @DATA(lt_vbrp)
WHERE vbeln = @iv_vbeln.
" IDoc-Kontrollsatz
ls_control-mestyp = 'INVOIC'.
ls_control-idoctp = 'INVOIC02'.
ls_control-rcvprt = 'LS'.
ls_control-rcvprn = 'EDOCUMENT'.
ls_control-sndprt = 'LS'.
ls_control-sndprn = sy-sysid.
" === Kopfdaten (E1EDK01) ===
DATA: ls_e1edk01 TYPE e1edk01.
ls_e1edk01-curcy = ls_vbrk-waerk.
ls_e1edk01-zterm = ls_vbrk-zterm.
APPEND VALUE edidd( segnam = 'E1EDK01'
sdata = ls_e1edk01 ) TO lt_data.
" === Referenzen (E1EDK02) ===
" Rechnungsnummer (QUALF 012 = Rechnungsnummer)
APPEND VALUE edidd( segnam = 'E1EDK02' sdata = VALUE e1edk02(
qualf = '012' belnr = iv_vbeln
datum = ls_vbrk-fkdat ) ) TO lt_data.
" Bestellreferenz (QUALF 001 = Bestellnummer)
IF ls_vbrk-bstnk_vf IS NOT INITIAL.
APPEND VALUE edidd( segnam = 'E1EDK02' sdata = VALUE e1edk02(
qualf = '001' belnr = ls_vbrk-bstnk_vf ) ) TO lt_data.
ENDIF.
" === Steuer-IDs (E1EDK14) ===
" USt-IdNr. Verkäufer (QUALF 006)
APPEND VALUE edidd( segnam = 'E1EDK14' sdata = VALUE e1edk14(
qualf = '006' orgid = ls_vbrk-stceg ) ) TO lt_data.
" USt-IdNr. Käufer (QUALF 007) — aus Kundenstamm
SELECT SINGLE stceg FROM kna1
INTO @DATA(lv_buyer_vat)
WHERE kunnr = @ls_vbrk-kunrg.
APPEND VALUE edidd( segnam = 'E1EDK14' sdata = VALUE e1edk14(
qualf = '007' orgid = lv_buyer_vat ) ) TO lt_data.
" === Partner (E1EDKA1) ===
" Rechnungssteller (RS), Regulierer (RG), Rechnungsempfänger (RE)
fill_partner( EXPORTING iv_parvw = 'RS' iv_kunnr = ls_vbrk-bukrs CHANGING ct_data = lt_data ).
fill_partner( EXPORTING iv_parvw = 'RG' iv_kunnr = ls_vbrk-kunrg CHANGING ct_data = lt_data ).
fill_partner( EXPORTING iv_parvw = 'RE' iv_kunnr = ls_vbrk-kunre CHANGING ct_data = lt_data ).
" === Positionen (E1EDP01) ===
LOOP AT lt_vbrp INTO DATA(ls_vbrp).
DATA: ls_e1edp01 TYPE e1edp01.
ls_e1edp01-posex = ls_vbrp-posnr.
ls_e1edp01-menge = ls_vbrp-fkimg.
ls_e1edp01-menee = ls_vbrp-vrkme.
APPEND VALUE edidd( segnam = 'E1EDP01'
sdata = ls_e1edp01 ) TO lt_data.
" Materialreferenz (E1EDP19 — QUALF 002 = EAN, 003 = Käufer-Artikel)
APPEND VALUE edidd( segnam = 'E1EDP19' sdata = VALUE e1edp19(
qualf = '002' idtnr = ls_vbrp-matnr ) ) TO lt_data.
" Preise (E1EDP26 — QUALF 003 = Bruttopreis)
APPEND VALUE edidd( segnam = 'E1EDP26' sdata = VALUE e1edp26(
betrg = ls_vbrp-netwr / ls_vbrp-fkimg
qualf = '003' ) ) TO lt_data.
" Steuer (E1EDP05 — MWSKZ = Steuerkennzeichen)
APPEND VALUE edidd( segnam = 'E1EDP05' sdata = VALUE e1edp05(
mwskz = ls_vbrp-mwskz mwsbt = ls_vbrp-mwsbp ) ) TO lt_data.
ENDLOOP.
" === Summen (E1EDS01) ===
" Nettobetrag
APPEND VALUE edidd( segnam = 'E1EDS01' sdata = VALUE e1eds01(
sumid = '010' summe = ls_vbrk-netwr ) ) TO lt_data.
" Bruttobetrag
DATA(lv_brutto) = ls_vbrk-netwr + ls_vbrk-mwsbk.
APPEND VALUE edidd( segnam = 'E1EDS01' sdata = VALUE e1eds01(
sumid = '011' summe = lv_brutto ) ) TO lt_data.
" === Bankverbindung (E1EDK17) ===
SELECT SINGLE bankn, bankl FROM t012k
INTO @DATA(ls_bank)
WHERE bukrs = @ls_vbrk-bukrs.
IF sy-subrc = 0.
" IBAN aus TIBAN lesen
SELECT SINGLE iban, swift FROM tiban
INTO @DATA(ls_iban)
WHERE banks = ls_vbrk-bukrs+0(2)
AND bankl = ls_bank-bankl
AND bankn = ls_bank-bankn.
APPEND VALUE edidd( segnam = 'E1EDK17' sdata = VALUE e1edk17(
qualf = '001'
bankn = ls_iban-iban
banks = ls_iban-swift ) ) TO lt_data.
ENDIF.
" === IDoc als XML serialisieren ===
CALL FUNCTION 'IDOC_XML_TRANSFORM'
EXPORTING
idoc_control = ls_control
TABLES
idoc_data = lt_data
IMPORTING
xml_string = rv_xml.
ENDMETHOD.
Hinweis: QUALF = Qualifier, BELNR = Belegnummer, IDTNR = Identnummer, BETRG = Betrag. e-document.io erwartet die Standard-INVOIC02-Struktur.
IDoc-Segmente Übersicht
| Segment | Beschreibung | SAP-Quelle |
|---|---|---|
E1EDK01 | Kopfdaten (Währung, Zahlungsbedingung) | VBRK |
E1EDK02 | Referenzen (Rechnungsnr., Bestellung, Datum) | VBRK |
E1EDK14 | Steuer-IDs (USt-IdNr. Verkäufer/Käufer, GLN) | VBRK, KNA1 |
E1EDKA1 | Partner (Rechnungssteller RS, Empfänger RE) | KNA1, T001 |
E1EDP01 | Positionen (Menge, Einheit) | VBRP |
E1EDP19 | Materialreferenz pro Position | VBRP-MATNR |
E1EDP26 | Preise pro Position | VBRP-NETWR |
E1EDP05 | Steuer pro Position | VBRP-MWSBP |
E1EDS01 | Summen (Netto, Brutto) | VBRK-NETWR |
E1EDK17 | Bankverbindung (IBAN, SWIFT) | T012K, TIBAN |
E1EDKT1 | Texte (Positionsbezeichnungen) | VBRP-ARKTX |
BAdI-Erweiterung (Kundenerweiterung)
Über ein BAdI (Business Add-In) können Kunden die IDoc-Erzeugung an definierten Stellen erweitern — z.B. eigene Segmente hinzufügen, Felder überschreiben oder zusätzliche Datenquellen anbinden. Das BAdI ist mehrstufig aufgebaut: vor der IDoc-Erzeugung, nach jeder Position und nach Abschluss.
Warum BAdI?
BAdI ist der SAP-Standard-Erweiterungsmechanismus ab ECC 6.0. Im Gegensatz zu User-Exits oder Customer-Exits unterstützt BAdI Mehrfach-Implementierungen, Filter und ist upgrade-sicher. In S/4HANA ist BAdI der einzig empfohlene Ansatz.
Schritt 1: BAdI-Interface definieren
Interface ZIF_EDOC_IDOC_ENRICH
Das Interface definiert drei Eingriffspunkte bei der IDoc-Erzeugung:
INTERFACE zif_edoc_idoc_enrich PUBLIC.
TYPES:
BEGIN OF ty_context,
vbeln TYPE vbeln_vf, " Fakturanummer
bukrs TYPE bukrs, " Buchungskreis
kunag TYPE kunag, " Auftraggeber
kunrg TYPE kunrg, " Regulierer
kunre TYPE kunre, " Rechnungsempfänger
vkorg TYPE vkorg, " Verkaufsorganisation
fkart TYPE fkart, " Fakturaart
waerk TYPE waerk, " Währung
END OF ty_context.
METHODS:
" Wird VOR der IDoc-Erzeugung aufgerufen.
" Hier können Kopf-Segmente ergänzt oder Standardwerte
" überschrieben werden.
before_create
IMPORTING
is_context TYPE ty_context
is_vbrk TYPE vbrk
CHANGING
ct_data TYPE edidd_tt, " IDoc-Segmente
" Wird NACH jeder Faktura-Position aufgerufen.
" Hier können positions-spezifische Segmente ergänzt werden
" (z.B. Chargen, Seriennummern, Zusatztexte).
after_line_item
IMPORTING
is_context TYPE ty_context
is_vbrp TYPE vbrp " aktuelle Position
CHANGING
ct_data TYPE edidd_tt, " IDoc-Segmente
" Wird NACH der kompletten IDoc-Erzeugung aufgerufen.
" Letzte Chance, Segmente zu ändern, hinzuzufügen
" oder zu entfernen. Auch für Validierung geeignet.
after_create
IMPORTING
is_context TYPE ty_context
CHANGING
ct_data TYPE edidd_tt " IDoc-Segmente
RAISING
zcx_edoc_error. " Abbruch möglich
ENDINTERFACE.
Schritt 2: BAdI-Definition anlegen
Anlage in Transaktion SE18 (BAdI-Pflege):
| Feld | Wert |
|---|---|
| BAdI-Name | ZEDOC_IDOC_ENRICH |
| Interface | ZIF_EDOC_IDOC_ENRICH |
| Mehrfach-Implementierung | Ja (mehrere Kunden-Erweiterungen gleichzeitig) |
| Filter-abhängig | Optional: nach Verkaufsorganisation (VKORG) oder Fakturaart (FKART) |
| Fallback-Klasse | ZCL_EDOC_IDOC_ENRICH_DEFAULT (leere Standardimplementierung) |
*&---------------------------------------------------------------------*
*& Fallback-Klasse (leere Standardimplementierung)
*&---------------------------------------------------------------------*
CLASS zcl_edoc_idoc_enrich_default DEFINITION PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_edoc_idoc_enrich.
ENDCLASS.
CLASS zcl_edoc_idoc_enrich_default IMPLEMENTATION.
METHOD zif_edoc_idoc_enrich~before_create.
" Standard: keine Änderung
ENDMETHOD.
METHOD zif_edoc_idoc_enrich~after_line_item.
" Standard: keine Änderung
ENDMETHOD.
METHOD zif_edoc_idoc_enrich~after_create.
" Standard: keine Änderung
ENDMETHOD.
ENDCLASS.
Schritt 3: BAdI-Aufruf in generate_idoc_xml einbauen
Die generate_idoc_xml-Methode ruft das BAdI an drei Stellen auf:
METHOD generate_idoc_xml.
DATA: lt_data TYPE TABLE OF edidd.
" Faktura + Positionen lesen
SELECT SINGLE * FROM vbrk INTO @DATA(ls_vbrk)
WHERE vbeln = @iv_vbeln.
SELECT * FROM vbrp INTO TABLE @DATA(lt_vbrp)
WHERE vbeln = @iv_vbeln.
" Kontext für BAdI aufbauen
DATA(ls_ctx) = VALUE zif_edoc_idoc_enrich=>ty_context(
vbeln = iv_vbeln bukrs = ls_vbrk-bukrs
kunag = ls_vbrk-kunag kunrg = ls_vbrk-kunrg
kunre = ls_vbrk-kunre vkorg = ls_vbrk-vkorg
fkart = ls_vbrk-fkart waerk = ls_vbrk-waerk ).
" BAdI-Instanz holen
DATA: lo_badi TYPE REF TO zif_edoc_idoc_enrich.
GET BADI lo_badi.
" === Kopfdaten aufbauen (E1EDK01, E1EDK02, E1EDK14, E1EDKA1) ===
" ... (wie zuvor gezeigt) ...
" ╔══════════════════════════════════════════════════════╗
" ║ BAdI-Aufruf 1: BEFORE_CREATE ║
" ║ → Kopf-Segmente ergänzen/überschreiben ║
" ╚══════════════════════════════════════════════════════╝
CALL BADI lo_badi->before_create
EXPORTING is_context = ls_ctx
is_vbrk = ls_vbrk
CHANGING ct_data = lt_data.
" === Positionen aufbauen (E1EDP01, E1EDP19, E1EDP26, E1EDP05) ===
LOOP AT lt_vbrp INTO DATA(ls_vbrp).
" ... Position-Segmente aufbauen (wie zuvor) ...
" ╔══════════════════════════════════════════════════════╗
" ║ BAdI-Aufruf 2: AFTER_LINE_ITEM ║
" ║ → pro Position Segmente ergänzen ║
" ║ (z.B. Chargen, Seriennummern, Zusatztexte) ║
" ╚══════════════════════════════════════════════════════╝
CALL BADI lo_badi->after_line_item
EXPORTING is_context = ls_ctx
is_vbrp = ls_vbrp
CHANGING ct_data = lt_data.
ENDLOOP.
" === Summen + Bank aufbauen (E1EDS01, E1EDP02) ===
" ... (wie zuvor gezeigt) ...
" ╔══════════════════════════════════════════════════════╗
" ║ BAdI-Aufruf 3: AFTER_CREATE ║
" ║ → letzte Chance: Segmente ändern, validieren ║
" ║ → Exception = IDoc wird NICHT gesendet ║
" ╚══════════════════════════════════════════════════════╝
TRY.
CALL BADI lo_badi->after_create
EXPORTING is_context = ls_ctx
CHANGING ct_data = lt_data.
CATCH zcx_edoc_error INTO DATA(lx_err).
" Abbruch: IDoc wird nicht erzeugt
CLEAR rv_xml.
RETURN.
ENDTRY.
" === IDoc als XML serialisieren ===
" ... (wie zuvor mit IDOC_XML_TRANSFORM) ...
ENDMETHOD.
Schritt 4: Kunden-Implementierung (Beispiel)
Der Kunde erstellt eine eigene BAdI-Implementierung in SE19. Hier ein Beispiel, das Chargen-Informationen und eine Projektreferenz hinzufügt:
Beispiel: Kunden-BAdI-Implementierung
*&---------------------------------------------------------------------*
*& Kunden-spezifische BAdI-Implementierung
*& SE19 → Implementierung ZEDOC_IDOC_ENRICH_CUST anlegen
*&---------------------------------------------------------------------*
CLASS zcl_edoc_idoc_enrich_cust DEFINITION PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_edoc_idoc_enrich.
ENDCLASS.
CLASS zcl_edoc_idoc_enrich_cust IMPLEMENTATION.
METHOD zif_edoc_idoc_enrich~before_create.
" Beispiel: Projektreferenz aus kundenspez. Tabelle ergänzen
SELECT SINGLE projekt FROM zcust_proj
INTO @DATA(lv_projekt)
WHERE vbeln = @is_context-vbeln.
IF sy-subrc = 0.
" Eigene Referenz als E1EDK02 mit Qualifier 015
APPEND VALUE edidd( segnam = 'E1EDK02'
sdata = VALUE e1edk02(
qualf = '015'
belnr = lv_projekt ) ) TO ct_data.
ENDIF.
" Beispiel: Verkäufer-Kontakt aus SD-Auftrag ergänzen
SELECT SINGLE ernam FROM vbak
INTO @DATA(lv_contact)
WHERE vbeln = @is_context-vbeln.
IF sy-subrc = 0.
" Kontakt als Seller-Contact (E1EDKA1)
APPEND VALUE edidd( segnam = 'E1EDKA1'
sdata = VALUE e1edka1(
parvw = 'RS'
bname = lv_contact ) ) TO ct_data.
ENDIF.
ENDMETHOD.
METHOD zif_edoc_idoc_enrich~after_line_item.
" Beispiel: Chargen-Info pro Position hinzufügen
IF is_vbrp-charg IS NOT INITIAL.
" Charge als Positions-Text (E1EDKT1)
APPEND VALUE edidd( segnam = 'E1EDKT1'
sdata = VALUE e1edkt1(
tdid = '0002'
tdline = |Charge: { is_vbrp-charg }| ) ) TO ct_data.
ENDIF.
" Beispiel: Kundenartikelnummer ergänzen
SELECT SINGLE kdmat FROM knmt
INTO @DATA(lv_kdmat)
WHERE matnr = @is_vbrp-matnr
AND kunnr = @is_context-kunag.
IF sy-subrc = 0.
" Kunden-Materialnummer als E1EDP19 Qualifier 001
APPEND VALUE edidd( segnam = 'E1EDP19'
sdata = VALUE e1edp19(
qualf = '001'
idtnr = lv_kdmat ) ) TO ct_data.
ENDIF.
ENDMETHOD.
METHOD zif_edoc_idoc_enrich~after_create.
" Beispiel: Validierung — IDoc nur senden wenn Netto > 0
LOOP AT ct_data INTO DATA(ls_seg)
WHERE segnam = 'E1EDS01'.
DATA(ls_sum) = CONV e1eds01( ls_seg-sdata ).
IF ls_sum-sumid = '010' AND ls_sum-summe <= 0.
" Abbruch: Gutschriften nicht per E-Rechnung senden
RAISE EXCEPTION TYPE zcx_edoc_error
EXPORTING textid = zcx_edoc_error=>no_credit_note.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Erweiterungspunkte im Überblick
| Methode | Zeitpunkt | Typische Anwendungsfälle |
|---|---|---|
before_create |
Nach Kopf-Segmenten, vor Positionen | Zusätzliche Referenzen (Projektnr., Vertragsnr.), Kontaktdaten, Steuer-Sonderbehandlung, abweichende Bankverbindung |
after_line_item |
Nach jeder Position | Chargen-/Seriennummern, Kunden-Artikelnummer, Positionstexte, Rabatt-Details, Lieferschein-Referenz |
after_create |
Nach komplettem IDoc | Validierung (z.B. keine Gutschriften), Segmente entfernen/umsortieren, kundenspez. Summen-Segmente, Logging |
Hinweis für e-document.io
Alle Segmente, die der Kunde über das BAdI hinzufügt, werden von e-document.io beim Parsen des IDoc INVOIC02 automatisch berücksichtigt. Der IdocParser erkennt die Standard-IDoc-Segmente und ihre Qualifizierer. Kundenspezifische Segmente (Z-Segmente) werden aktuell ignoriert — die Daten sollten daher in Standard-Segmenten mit freien Qualifizierern transportiert werden.
Tipp: Filter-BAdI
Definieren Sie das BAdI mit Filter nach Verkaufsorganisation (VKORG). So können verschiedene Buchungskreise unterschiedliche BAdI-Implementierungen verwenden — z.B. ein Werk ergänzt Chargen, ein anderes ergänzt Projektreferenzen.
PDF-Erzeugung
Das PDF wird über das bestehende Rechnungsformular (SAPscript, Smart Form oder Adobe Form) erzeugt. Der OTF-Spool wird in PDF konvertiert.
PDF aus Faktura-Formular erzeugen
*&---------------------------------------------------------------------*
*& PDF aus SAP-Rechnungsformular erzeugen
*&---------------------------------------------------------------------*
METHOD generate_pdf.
" iv_vbeln: Fakturanummer
" rv_pdf: PDF als XSTRING
DATA: lt_otf TYPE TABLE OF itcoo,
lt_lines TYPE TABLE OF tline,
lv_bin_size TYPE i.
" --- Variante 1: Smart Forms ---
DATA: lv_fm_name TYPE rs38l_fnam.
" Smart Form Funktionsbaustein ermitteln
CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
EXPORTING formname = 'ZSF_INVOICE'
IMPORTING fm_name = lv_fm_name.
" Smart Form aufrufen — OTF erzeugen (nicht drucken)
DATA: ls_control TYPE ssfctrlop,
ls_output TYPE ssfcrescl.
ls_control-no_dialog = abap_true.
ls_control-getotf = abap_true. " OTF statt Druck
ls_control-preview = abap_false.
CALL FUNCTION lv_fm_name
EXPORTING
control_parameters = ls_control
iv_vbeln = iv_vbeln
IMPORTING
job_output_info = ls_output.
lt_otf = ls_output-otfdata.
" OTF → PDF konvertieren
CALL FUNCTION 'CONVERT_OTF'
EXPORTING
format = 'PDF'
IMPORTING
bin_filesize = lv_bin_size
TABLES
otf = lt_otf
lines = lt_lines.
" Binärtabelle → XSTRING
CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
EXPORTING input_length = lv_bin_size
IMPORTING buffer = rv_pdf
TABLES binary_tab = lt_lines.
ENDMETHOD.
ABAP-Klasse ZCL_EDOCUMENT_CLIENT
Die zentrale Klasse kapselt die gesamte API-Kommunikation. Sie erzeugt IDoc + PDF, kodiert beides in Base64, und sendet es an die e-document.io API.
Klassendefinition
CLASS zcl_edocument_client DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_send_result,
success TYPE abap_bool,
document_id TYPE string,
status TYPE string,
channel TYPE string,
http_code TYPE i,
error_msg TYPE string,
END OF ty_send_result.
METHODS:
constructor
IMPORTING
iv_api_key TYPE string
iv_org_id TYPE string
iv_rfc_dest TYPE rfcdest DEFAULT 'EDOCUMENT_IO',
" Kompletter Versand: IDoc + PDF erzeugen und an API senden
send_invoice
IMPORTING
iv_vbeln TYPE vbeln_vf " Fakturanummer
iv_kunnr TYPE kunnr " Kundennummer (= GP-Nummer)
iv_with_pdf TYPE abap_bool DEFAULT abap_true
RETURNING
VALUE(rs_result) TYPE ty_send_result,
" Status eines gesendeten Dokuments abfragen
check_status
IMPORTING
iv_document_id TYPE string
RETURNING
VALUE(rv_status) TYPE string,
" --- Geschäftspartner-Sync (GP-Export) ---
" Einzelnen Debitor an e-document.io synchronisieren (Upsert)
sync_partner
IMPORTING
is_partner TYPE ty_partner_data
RETURNING
VALUE(rs_result) TYPE ty_partner_result,
" Mehrere Debitoren als Batch synchronisieren
sync_partners
IMPORTING
it_partners TYPE ty_partner_data_t
RETURNING
VALUE(rt_results) TYPE ty_partner_result_t.
TYPES:
BEGIN OF ty_partner_data,
partner_number TYPE string, " KNA1-KUNNR
name TYPE string, " KNA1-NAME1
company TYPE string, " KNA1-NAME2
email TYPE string, " ADR6-SMTP_ADDR
peppol_id TYPE string,
peppol_scheme TYPE string,
invoice_format TYPE string, " cii | ubl | zugferd
status TYPE string, " active | inactive
END OF ty_partner_data,
ty_partner_data_t TYPE STANDARD TABLE OF ty_partner_data WITH EMPTY KEY.
TYPES:
BEGIN OF ty_partner_result,
success TYPE abap_bool,
partner_id TYPE string,
partner_number TYPE string,
action TYPE string, " created | updated
http_code TYPE i,
error_msg TYPE string,
END OF ty_partner_result,
ty_partner_result_t TYPE STANDARD TABLE OF ty_partner_result WITH EMPTY KEY.
PRIVATE SECTION.
DATA: mv_api_key TYPE string,
mv_org_id TYPE string,
mv_rfc_dest TYPE rfcdest.
METHODS:
generate_idoc_xml
IMPORTING iv_vbeln TYPE vbeln_vf
RETURNING VALUE(rv_xml) TYPE string,
generate_pdf
IMPORTING iv_vbeln TYPE vbeln_vf
RETURNING VALUE(rv_pdf) TYPE xstring,
call_api
IMPORTING iv_json TYPE string
RETURNING VALUE(rs_result) TYPE ty_send_result,
fill_partner
IMPORTING iv_parvw TYPE parvw " Partnerrolle (RS, RG, RE)
iv_kunnr TYPE kunnr " Kundennummer
CHANGING ct_data TYPE edidd_tt.
ENDCLASS.
Implementierung: send_invoice
METHOD send_invoice.
" 1. IDoc-XML erzeugen
DATA(lv_idoc_xml) = generate_idoc_xml( iv_vbeln ).
IF lv_idoc_xml IS INITIAL.
rs_result-error_msg = 'IDoc-Erzeugung fehlgeschlagen'.
RETURN.
ENDIF.
" 2. XML → Base64
DATA(lv_xml_xstring) = cl_abap_codepage=>convert_to(
source = lv_idoc_xml codepage = 'UTF-8' ).
DATA(lv_xml_b64) = cl_http_utility=>encode_x_base64( lv_xml_xstring ).
" 3. Optional: PDF erzeugen + Base64
DATA: lv_pdf_b64 TYPE string.
IF iv_with_pdf = abap_true.
DATA(lv_pdf) = generate_pdf( iv_vbeln ).
IF lv_pdf IS NOT INITIAL.
lv_pdf_b64 = cl_http_utility=>encode_x_base64( lv_pdf ).
ENDIF.
ENDIF.
" 4. JSON-Payload zusammenbauen
DATA(lv_filename) = |INVOIC02_{ iv_vbeln }.xml|.
DATA(lv_partner_nr) = CONV string( iv_kunnr ).
" JSON manuell aufbauen (oder /ui2/cl_json verwenden)
DATA(lv_json) = |\{ &&
|"org_id":"{ mv_org_id }", &&
|"document_type":"idoc", &&
|"xml":"{ lv_xml_b64 }", &&
|"filename":"{ lv_filename }",|.
IF lv_pdf_b64 IS NOT INITIAL.
lv_json = lv_json && |"pdf":"{ lv_pdf_b64 }",|.
ENDIF.
lv_json = lv_json &&
|"recipient":\{"partner_number":"{ lv_partner_nr }"\}| &&
|\}|.
" 5. API aufrufen
rs_result = call_api( lv_json ).
ENDMETHOD.
Implementierung: call_api (HTTP-Client)
METHOD call_api.
DATA: lo_client TYPE REF TO if_http_client.
" HTTP-Client über RFC-Destination erzeugen
CALL METHOD cl_http_client=>create_by_destination
EXPORTING destination = mv_rfc_dest
IMPORTING client = lo_client.
" Request konfigurieren
lo_client->request->set_header_field(
name = '~request_uri' value = '/api/v1/invoices/send' ).
lo_client->request->set_header_field(
name = '~request_method' value = 'POST' ).
lo_client->request->set_header_field(
name = 'Content-Type' value = 'application/json' ).
lo_client->request->set_header_field(
name = 'X-API-Key' value = mv_api_key ).
" JSON-Payload setzen
lo_client->request->set_cdata( data = iv_json ).
" Senden + Empfangen
lo_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
rs_result-error_msg = 'HTTP-Verbindungsfehler'.
RETURN.
ENDIF.
lo_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
rs_result-error_msg = 'HTTP-Empfangsfehler'.
lo_client->close( ).
RETURN.
ENDIF.
" Response auswerten
lo_client->response->get_status(
IMPORTING code = rs_result-http_code ).
DATA(lv_response) = lo_client->response->get_cdata( ).
lo_client->close( ).
IF rs_result-http_code = 202. " Accepted
rs_result-success = abap_true.
" JSON parsen — id und channel extrahieren
" (Vereinfacht — in Produktion /ui2/cl_json verwenden)
FIND REGEX '"id"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-document_id.
FIND REGEX '"status"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-status.
FIND REGEX '"channel"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-channel.
ELSE.
" Fehlermeldung aus Response extrahieren
FIND REGEX '"error"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-error_msg.
IF rs_result-error_msg IS INITIAL.
rs_result-error_msg = |HTTP { rs_result-http_code }: { lv_response }|.
ENDIF.
ENDIF.
ENDMETHOD.
Implementierung: constructor
METHOD constructor.
mv_api_key = iv_api_key.
mv_org_id = iv_org_id.
mv_rfc_dest = iv_rfc_dest.
ENDMETHOD.
Implementierung: check_status
METHOD check_status.
DATA: lo_client TYPE REF TO if_http_client.
CALL METHOD cl_http_client=>create_by_destination
EXPORTING destination = mv_rfc_dest
IMPORTING client = lo_client.
DATA(lv_uri) = |/api/v1/ausgang/{ iv_document_id }|.
lo_client->request->set_header_field(
name = '~request_uri' value = lv_uri ).
lo_client->request->set_header_field(
name = '~request_method' value = 'GET' ).
lo_client->request->set_header_field(
name = 'X-API-Key' value = mv_api_key ).
lo_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
rv_status = 'CONNECTION_ERROR'.
RETURN.
ENDIF.
lo_client->receive( EXCEPTIONS OTHERS = 1 ).
DATA(lv_response) = lo_client->response->get_cdata( ).
lo_client->close( ).
" Status aus JSON-Response extrahieren
FIND REGEX '"status"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rv_status.
IF rv_status IS INITIAL.
rv_status = 'UNKNOWN'.
ENDIF.
ENDMETHOD.
Implementierung: fill_partner
METHOD fill_partner.
" E1EDKA1-Segment für eine Partnerrolle befüllen
DATA: ls_e1edka1 TYPE e1edka1.
ls_e1edka1-parvw = iv_parvw.
IF iv_parvw = 'RS'. " Rechnungssteller = eigene Firma
SELECT SINGLE butxt, adrnr FROM t001
INTO @DATA(ls_t001)
WHERE bukrs = @iv_kunnr.
ls_e1edka1-name1 = ls_t001-butxt.
" Adresse aus ADRC lesen
SELECT SINGLE street, house_num1, post_code1, city1, country
FROM adrc INTO @DATA(ls_adr)
WHERE addrnumber = @ls_t001-adrnr.
ls_e1edka1-stras = ls_adr-street.
ls_e1edka1-hausnr = ls_adr-house_num1.
ls_e1edka1-pstlz = ls_adr-post_code1.
ls_e1edka1-ort01 = ls_adr-city1.
ls_e1edka1-land1 = ls_adr-country.
ELSE. " RG, RE = Kundenstamm
SELECT SINGLE name1, name2, stras, pstlz, ort01, land1
FROM kna1 INTO CORRESPONDING FIELDS OF @ls_e1edka1
WHERE kunnr = @iv_kunnr.
ENDIF.
APPEND VALUE edidd( segnam = 'E1EDKA1'
sdata = ls_e1edka1 ) TO ct_data.
ENDMETHOD.
Aufruf aus der Nachrichtensteuerung
Dieses Programm wird als Verarbeitungsroutine in NACE hinterlegt:
*&---------------------------------------------------------------------*
*& Report ZEDOCUMENT_SEND_INVOICE
*&---------------------------------------------------------------------*
REPORT zedocument_send_invoice.
*&---------------------------------------------------------------------*
*& Entry-Point für Nachrichtensteuerung (NACE)
*&---------------------------------------------------------------------*
FORM entry_edocument USING us_return TYPE nast_return
us_screen TYPE nast_screen.
" Nachrichtensatz lesen (globale Variable NAST)
DATA(lv_vbeln) = CONV vbeln_vf( nast-objky ). " Fakturanummer
" Kundennummer aus Faktura lesen
SELECT SINGLE kunag FROM vbrk INTO @DATA(lv_kunnr)
WHERE vbeln = @lv_vbeln.
IF sy-subrc <> 0.
us_return-retco = 1.
us_return-text = |Faktura { lv_vbeln } nicht gefunden|.
RETURN.
ENDIF.
" API-Key und Org-ID aus Customizing (z.B. Z-Tabelle oder TVARVC)
DATA: lv_api_key TYPE string VALUE 'edoc_IhrApiKeyHier',
lv_org_id TYPE string VALUE 'ihre-org-id'.
" E-Rechnung erzeugen und senden
DATA(lo_client) = NEW zcl_edocument_client(
iv_api_key = lv_api_key
iv_org_id = lv_org_id ).
DATA(ls_result) = lo_client->send_invoice(
iv_vbeln = lv_vbeln
iv_kunnr = lv_kunnr
iv_with_pdf = abap_true ).
IF ls_result-success = abap_true.
us_return-retco = 0. " Erfolg
us_return-text = |E-Rechnung gesendet: { ls_result-document_id }|.
" Optional: Dokument-ID in Z-Tabelle oder VBRK-Zusatzfeld speichern
" für spätere Status-Abfrage
ELSE.
us_return-retco = 1. " Fehler
us_return-text = ls_result-error_msg.
" Nachricht bleibt auf "nicht verarbeitet" — kann erneut gesendet werden
ENDIF.
ENDFORM.
Wichtig: GP-Nummer = SAP-Kundennummer
Die SAP-Kundennummer (KUNNR) wird als GP-Nummer (partner_number) an e-document.io gesendet. Stellen Sie sicher, dass die Geschäftspartner in e-document.io mit der entsprechenden GP-Nummer angelegt sind. Die ABAP-Klasse nutzt den neuen Endpunkt POST /api/v1/invoices/send — einfacher als multipart/form-data, da nur JSON mit Base64.
Geschäftspartner-Synchronisation
Damit Rechnungen korrekt zugestellt werden, müssen die Geschäftspartner (Debitoren) in e-document.io vorhanden sein. Die SAP-Kundennummer (KUNNR) dient als eindeutiger Schlüssel (partner_number). Die API bietet einen Upsert-Endpunkt, der pro KUNNR automatisch anlegt oder aktualisiert.
API-Endpunkte
| Methode | Endpunkt | Beschreibung |
|---|---|---|
PUT |
/api/v1/partners/upsert |
Anlegen oder Aktualisieren anhand partner_number (= KUNNR). Empfohlen für SAP-Sync. |
GET |
/api/v1/partners |
Alle Geschäftspartner auflisten (paginiert, mit Filtern) |
POST |
/api/v1/partners |
Neuen Geschäftspartner anlegen |
GET |
/api/v1/partners/:id |
Einzelnen Partner abrufen |
PUT |
/api/v1/partners/:id |
Partner aktualisieren |
DELETE |
/api/v1/partners/:id |
Partner löschen |
Felder
| Feld | Typ | SAP-Feld | Beschreibung |
|---|---|---|---|
org_id |
string | — | Pflicht. Mandanten-ID. |
partner_number |
string (max 10) | KNA1-KUNNR |
Pflicht für Upsert. SAP-Kundennummer als eindeutiger Schlüssel. |
name |
string | KNA1-NAME1 |
Pflicht. Name des Geschäftspartners. |
company |
string | KNA1-NAME2 |
Firmenname / Zusatz. |
email |
string | ADR6-SMTP_ADDR |
E-Mail-Adresse. Fallback-Kanal wenn kein Peppol. |
peppol_id |
string | — | Peppol-Teilnehmer-ID (z.B. DE367927617). |
peppol_scheme |
string | — | Peppol-Schema (z.B. 0204 für Leitweg-ID). |
invoice_format |
string | — | Bevorzugtes Format: cii, ubl oder zugferd (Standard: cii). |
status |
string | KNA1-LOEVM |
active oder inactive (Standard: active). |
Die Typen ty_partner_data, ty_partner_result und die Methoden sync_partner / sync_partners sind bereits in der Klassendefinition oben enthalten.
Implementierung: sync_partner
METHOD sync_partner.
" JSON-Payload für Upsert aufbauen
DATA(lv_json) = |\{ &&
|"org_id":"{ mv_org_id }", &&
|"partner_number":"{ is_partner-partner_number }", &&
|"name":"{ is_partner-name }", &&
|"company":"{ is_partner-company }", &&
|"email":"{ is_partner-email }", &&
|"status":"{ is_partner-status }", &&
|"invoice_format":"{ is_partner-invoice_format }", &&
|"peppol_id":"{ is_partner-peppol_id }", &&
|"peppol_scheme":"{ is_partner-peppol_scheme }" &&
|\}|.
" HTTP-Client über RFC-Destination erzeugen
DATA: lo_client TYPE REF TO if_http_client.
CALL METHOD cl_http_client=>create_by_destination
EXPORTING destination = mv_rfc_dest
IMPORTING client = lo_client.
" Request konfigurieren (PUT /api/v1/partners/upsert)
lo_client->request->set_header_field(
name = '~request_uri' value = '/api/v1/partners/upsert' ).
lo_client->request->set_header_field(
name = '~request_method' value = 'PUT' ).
lo_client->request->set_header_field(
name = 'Content-Type' value = 'application/json' ).
lo_client->request->set_header_field(
name = 'X-API-Key' value = mv_api_key ).
lo_client->request->set_cdata( data = lv_json ).
" Senden + Empfangen
lo_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
rs_result-error_msg = 'HTTP-Verbindungsfehler'.
RETURN.
ENDIF.
lo_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
rs_result-error_msg = 'HTTP-Empfangsfehler'.
lo_client->close( ).
RETURN.
ENDIF.
" Response auswerten
lo_client->response->get_status(
IMPORTING code = rs_result-http_code ).
DATA(lv_response) = lo_client->response->get_cdata( ).
lo_client->close( ).
IF rs_result-http_code = 200 OR rs_result-http_code = 201.
rs_result-success = abap_true.
rs_result-partner_number = is_partner-partner_number.
" Partner-ID und Action aus Response parsen
FIND REGEX '"id"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-partner_id.
FIND REGEX '"action"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-action.
ELSE.
FIND REGEX '"error"\s*:\s*"([^"]+)"' IN lv_response
SUBMATCHES rs_result-error_msg.
IF rs_result-error_msg IS INITIAL.
rs_result-error_msg = |HTTP { rs_result-http_code }: { lv_response }|.
ENDIF.
ENDIF.
ENDMETHOD.
Implementierung: sync_partners (Batch)
METHOD sync_partners.
" Alle übergebenen Partner einzeln synchronisieren
LOOP AT it_partners INTO DATA(ls_partner).
DATA(ls_result) = sync_partner( ls_partner ).
APPEND ls_result TO rt_results.
IF ls_result-success = abap_true.
WRITE: / |✓ { ls_result-partner_number }: { ls_result-action }|.
ELSE.
WRITE: / |✗ { ls_result-partner_number }: { ls_result-error_msg }|.
ENDIF.
ENDLOOP.
ENDMETHOD.
Report: Debitoren-Sync (SM36 / manuell)
Dieser Report liest alle relevanten Debitoren aus SAP und synchronisiert sie mit e-document.io. Er kann manuell gestartet oder als Hintergrundjob in SM36 eingeplant werden (z.B. täglich).
*&---------------------------------------------------------------------*
*& Report ZEDOCUMENT_SYNC_PARTNERS
*& Debitoren-Stammdaten an e-document.io synchronisieren
*&---------------------------------------------------------------------*
REPORT zedocument_sync_partners.
PARAMETERS:
p_bukrs TYPE bukrs OBLIGATORY, " Buchungskreis
p_vkorg TYPE vkorg, " Verkaufsorganisation (optional)
p_kunnr TYPE RANGE OF kunnr, " Kundennummern-Selektion
p_only TYPE abap_bool AS CHECKBOX, " Nur aktive Debitoren
p_test TYPE abap_bool AS CHECKBOX DEFAULT 'X'. " Testlauf
START-OF-SELECTION.
" API-Key und Org-ID aus Customizing
DATA: lv_api_key TYPE string VALUE 'edoc_IhrApiKeyHier',
lv_org_id TYPE string VALUE 'ihre-org-id'.
" Debitoren aus KNA1 + KNB1 lesen
SELECT k~kunnr, k~name1, k~name2, k~loevm
FROM kna1 AS k
INNER JOIN knb1 AS b ON b~kunnr = k~kunnr
WHERE b~bukrs = @p_bukrs
AND k~kunnr IN @p_kunnr
INTO TABLE @DATA(lt_debitoren).
IF sy-subrc <> 0.
WRITE: / 'Keine Debitoren gefunden.'.
RETURN.
ENDIF.
WRITE: / |{ lines( lt_debitoren ) } Debitoren gefunden.|.
ULINE.
" Partner-Tabelle aufbauen
DATA: lt_partners TYPE zcl_edocument_client=>ty_partner_data_t.
LOOP AT lt_debitoren INTO DATA(ls_deb).
" Optional: Nur aktive
IF p_only = abap_true AND ls_deb-loevm IS NOT INITIAL.
CONTINUE.
ENDIF.
" E-Mail aus Adresse lesen (ADR6)
DATA(lv_email) = VALUE string( ).
SELECT SINGLE smtp_addr FROM adr6
INTO @lv_email
WHERE addrnumber = ( SELECT adrnr FROM kna1
WHERE kunnr = @ls_deb-kunnr )
AND flgdefault = 'X'.
APPEND VALUE #(
partner_number = CONV string( ls_deb-kunnr )
name = ls_deb-name1
company = ls_deb-name2
email = lv_email
invoice_format = 'cii' " Standard: XRechnung CII
status = COND #( WHEN ls_deb-loevm IS INITIAL
THEN 'active' ELSE 'inactive' )
) TO lt_partners.
ENDLOOP.
IF p_test = abap_true.
WRITE: / 'TESTLAUF — keine Daten werden gesendet.'.
LOOP AT lt_partners INTO DATA(ls_p).
WRITE: / | { ls_p-partner_number } — { ls_p-name } ({ ls_p-email })|.
ENDLOOP.
RETURN.
ENDIF.
" Client erzeugen und Batch-Sync starten
DATA(lo_client) = NEW zcl_edocument_client(
iv_api_key = lv_api_key
iv_org_id = lv_org_id ).
DATA(lt_results) = lo_client->sync_partners( lt_partners ).
" Ergebnis ausgeben
DATA: lv_ok TYPE i, lv_err TYPE i.
LOOP AT lt_results INTO DATA(ls_res).
IF ls_res-success = abap_true.
lv_ok = lv_ok + 1.
ELSE.
lv_err = lv_err + 1.
ENDIF.
ENDLOOP.
ULINE.
WRITE: / |Ergebnis: { lv_ok } erfolgreich, { lv_err } Fehler.|.
Empfohlener Ablauf
1. Einmalig: Report ZEDOCUMENT_SYNC_PARTNERS mit Testlauf starten, um alle Debitoren zu prüfen. 2. Produktivlauf starten, um alle GP anzulegen. 3. SM36-Job einplanen (z.B. täglich nachts), um Änderungen automatisch zu synchronisieren. 4. Danach werden Rechnungen automatisch dem richtigen GP zugeordnet — über die SAP-Kundennummer (KUNNR = partner_number).
Peppol-ID im Kundenstamm
Falls Ihre Kunden eine Peppol-ID haben (z.B. Leitweg-ID für Behörden), können Sie diese in einem Z-Feld im Kundenstamm (KNA1 Append) oder einer eigenen Z-Tabelle pflegen und beim Sync mitgeben. e-document.io prüft automatisch, ob der Empfänger per Peppol erreichbar ist.
Rechnungsempfang (Eingang)
SAP ruft regelmäßig die API ab (Polling), um neue eingehende Rechnungen zu verarbeiten. Empfohlen: Hintergrundjob alle 5 Minuten (SM36).
e-document.io (Empfang via Peppol / E-Mail / API)
│
├── Automatische Validierung + Konvertierung
│
▼
SAP (Hintergrundjob alle 5 Min.)
├── GET /api/v1/erfolgreich (neue Belege auflisten)
├── GET /api/v1/erfolgreich/:id/download?type=xml
├── XML parsen → BAPI_INCOMINGINVOICE_CREATE
└── POST /api/v1/erfolgreich/:id/abholen
| XRechnung-Feld (UBL) | SAP-Feld | Beschreibung |
|---|---|---|
cbc:ID | XBLNR | Externe Belegnummer |
cbc:IssueDate | BLDAT | Belegdatum |
cbc:DueDate | ZFBDT | Fälligkeitsdatum |
cac:AccountingSupplierParty | LIFNR | Lieferantennummer |
cbc:PayableAmount | WRBTR | Rechnungsbetrag |
cbc:DocumentCurrencyCode | WAERS | Währung |
cbc:TaxAmount | WMWST | Steuerbetrag |
SAP-BAPIs: BAPI_INCOMINGINVOICE_CREATE (MM-Rechnungsprüfung) oder BAPI_ACC_DOCUMENT_POST (FI-Beleg)
Fehlerbehandlung
| Code | Bedeutung | SAP-Reaktion |
|---|---|---|
202 |
Angenommen (Queued) | Dokument-ID speichern, Nachricht als verarbeitet markieren |
400 |
Ungültige Anfrage | Parameter prüfen (org_id, xml, recipient fehlt?) |
401 |
Nicht authentifiziert | API-Key in TVARVC prüfen |
404 |
Partner nicht gefunden | GP-Nummer in e-document.io anlegen (KUNNR → partner_number) |
422 |
IDoc-Konvertierung fehlgeschlagen | IDoc-Segmente prüfen (Pflichtfelder vorhanden?) |
502 |
Serverfehler | Retry nach 60 Sekunden (max. 3 Versuche) |
Nachrichtensteuerung bei Fehler
Wenn ENTRY_EDOCUMENT den Returncode 1 setzt, bleibt die Nachricht auf Status 'nicht verarbeitet'. Sie kann über VF31 (Nachrichtenausgabe wiederholen) oder RSNAST00 erneut gesendet werden.
Middleware / iPaaS
Alternativ zur direkten ABAP-Programmierung können Sie eine Middleware einsetzen:
SAP BTP Integration Suite
iFlow-Designer, native SAP-Konnektoren, IDoc-Trigger. Offiziell von SAP.
Microsoft Power Automate
Low-Code mit SAP-Konnektor. Gut für Microsoft-basierte Umgebungen.
Make (Integromat)
Visueller Workflow-Builder. Schnell aufgesetzt, kostengünstig.
n8n
Open-Source, self-hosted. REST-API-Nodes, Webhook-Trigger.
Middleware-Flow mit neuem API-Endpunkt
Trigger: SAP IDoc-Ausgang (WE20 → Middleware als Partner)
Middleware empfängt IDoc INVOIC02 als XML
Optional: PDF vom SAP-Spool abrufen
Base64-kodieren und JSON zusammenbauen
POST /api/v1/invoices/send
Response-ID speichern für Status-Tracking