CommunityCodierung & Entwicklunggithub.com

yanghangmvp/sap-abap-pitfalls

ABAP Entwicklung – Fallstricke und Lösungen (projektübergreifend nutzbar). Basierend auf über 100 Auslieferungsberichten und täglichen Fehlerkorrekturen aus einem SAP-ERP-Kundenprojekt – ca. 120 Einträge mit „Phänomen → Ursache → Lösung“ plus einer Checkliste für neue Projekte.

Was ist sap-abap-pitfalls?

Dieser Skill ist eine umfassende Wissensdatenbank für ABAP-Entwickler, die häufig wiederkehrende Fehler, subtile Fallstricke und bewährte Lösungen aus realen SAP-Projekten zusammenfasst. Er enthält etwa 120 detaillierte Einträge, die nach dem Schema „Phänomen → Ursache → Lösung“ aufgebaut sind und alle kritischen Bereiche abdecken: ADT-Aktivierung (falsche Erfolge, falsche Sperren), ABAP-Syntax-Zwänge (Parameter-Reihenfolge, Typkonflikte), stille Fehler (BAPI-Rückgabe S, aber Daten unverändert), Transaktionen und LUW, Dialogfenster, F4-Hilfe und ALV, JSON-Schnittstellen und Gateway, DDIC-Objektanlage sowie ST22-Analyse und Debugging-Techniken. Der Skill bietet außerdem eine fünfteilige Checkliste für den Start neuer Projekte (Bestandsaufnahme, Vorbereitung, Code-Selbstprüfung, Auslieferung und Abschluss). Entwickler sollten diesen Skill vor Beginn eines neuen Projekts lesen und bei Problemen wie „Kompilierung OK, Aktivierung fehlgeschlagen / Aktivierung OK, Laufzeitfehler / S-Rückgabe, aber Wert unverändert / Fehlermeldung stimmt nicht mit Ursache überein / Button reagiert nicht / Daten vertauscht“ als erste Anlaufstelle nutzen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/yanghangmvp/MCP_ABAP/tree/HEAD/skills/sap-abap-pitfalls

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

ABAP Entwicklung – Fallstricke und Lösungen

Verwendung: Vor Projektstart komplett durchlesen, bei Fehlern Symptome nachschlagen. Dieser Skill enthält nur projektübergreifend nutzbare Erfahrungen. Umgebungsadressen, Transportaufträge und Objektlisten sind im übergeordneten Projekt-Handbuch (z. B. sap-abap-project-ops) zu finden. Die vollständigen 120 Einträge (mit Code-Ausschnitten und Quellen) befinden sich in references/casebook.md.

Verwendung: Neues Projekt → direkt §11 Checkliste (chronologisch abarbeiten); laufendes Projekt mit Fehler → §10 Symptom-Suche → zur entsprechenden Sektion; für vollständigen Code und Quellen eines Eintrags → references/casebook.md.

Drei Meta-Regeln (wichtiger als jeder einzelne Fallstrick):

  1. Fehlermeldung stimmt oft nicht mit der Ursache überein – Der ABAP-Compiler zeigt meist die Stelle an, wo er den Fehler erkennt, nicht die Fehlerstelle. Gehen Sie zuerst von Reihenfolge/Schlüsselwortreihenfolge/Typkonflikt aus, nicht vom Syntax selbst.
  2. Das Gefährlichste in SAP ist nicht der Fehler, sondern das stille Versagen – Die dreifache Täuschung „Rückgabe S, Aktivierung 200, Kompilierung OK“ verbirgt stille Datenfehler. Bei BAPI, SELECT INTO TABLE, EXTENSIONIN, Textelementen und Enhancement-Feldern ist ein Datenabgleich nach Datenbankupdate zwingend erforderlich – vertrauen Sie nicht auf den Rückgabewert.
  3. Bei Fehlern sofort ST22 prüfen, nicht raten – Der Kurzdump Error analysis liefert direkt die procedure "XXX" "(FORM)" und die CX_SY_* Ausnahmeklasse – das ist eine Größenordnung schneller als das Testen einzelner FORM.

0. Zehn eiserne Regeln (Verstoß bedeutet sofortige Nacharbeit)

  1. Vor jeder Änderung eines existierenden Objekts, Schritt 1: Rücklesen aus dem aktuellen System. Benutzer ändern oft direkt in SE80 Produktivobjekte, ohne das Archiv zu aktualisieren. Das lokale Archiv dient nur zum Vergleich, nicht als Basis. get_source → Vergleich mit Backup (0 Differenz) → Patch → Rücklesen + erneuter Vergleich → Rückwärts ins Archiv.
  2. Textelemente gehören ebenfalls zur Basis, get_source enthält kein TEXTPOOL. Vor dem Überschreiben von Texten mit push_textelements muss read_textelements den gesamten aktivierten Text archivieren, mit dem lokalen JSON vergleichen, manuell zusammenführen und dann pushen. Sonst werden Änderungen des Benutzers in SE38 spurlos überschrieben – der Quellcode-Diff zeigt nichts.
  3. NIEMALS direkt UPDATE/MODIFY auf Standardtabellen. Änderungen an gebuchten FI-Belegen erfolgen über FI_DOCUMENT_CHANGE (Gruppe FACG, T_ACCHG nur mit FDNAME+NEWVAL, Kopfzeilen ohne I_BUZEI, nur Positionen mit).
  4. Transportauftrag nicht selbst erzeugen. Fehlt CORR_NR beim Push eines transportierbaren Objekts → Abbruch mit Meldung, Benutzer auffordern, die Nummer zu geben; vor dem Anlegen eines neuen TADIR-Objekts zuerst „Typ+Vorschlagsname+TADIR-Doppelcheck+Nummernvergabe-Begründung“ melden, bestätigen lassen, dann anlegen. Bereits einem Transport zugeordnete Objekte verwenden die vorhandene Nummer weiter – keine erneute Rückfrage.
  5. NIEMALS Update*-Klassen für testweise Aufrufe von Schnittstellen verwenden. Sie schreiben wirklich – es gab einen Fall, wo UpdateFunctionModule mit nur einem Funktionsbaustein zum Testen den inaktiven Quellcode auf 620 Zeichen verstümmelt hat. Sonden nur lesend.
  6. Vier Schritte zur Auslieferung: Generieren → Quellcode pushen → Textelemente pushen (PROG/PX muss separat aktiviert werden) → GUI-Status generieren (CUA). Ohne Aktivierung der Textelemente sind TEXT-xxx zur Laufzeit immer leer.
  7. Produktivquellcode-Kommentare: Gerüst behalten, Erläuterungen entfernen. Programm-Kopf (Firma/Autor/Datum/Zusammenfassung/Änderungshistorie) + *&--- Rahmenlinien + FORM-Banner + -->/<-- Parameter-Dokumentation + kurze Kommentare zu Bedingungen. Entfernen: Entwurfsbegründungen, Berechnungslogik, Tests, TODO-Begründungen, scripts/work/ADT/ZTEST_* Referenzen. Nach dem Strippen muss ein Diff belegen, dass der Code unverändert ist.
  8. Test-/Sondenobjekte nach Test sofort löschen. Zuerst get_source ins backup/ → Löschen → TADIR auf Null prüfen. Nur selbst erstellte Objekte löschen; von anderen erstellte, einem Paket/Transport zugeordnete, oder aktive DEMOs unberührt lassen.
  9. Vor dem Schreiben in SAP: erst Sperre prüfen, dann Backup. Credentials nicht teilen, nicht anzeigen, nicht in Auslieferungspakete aufnehmen.
  10. Wenn der Benutzer sagt „Daten sind in Ordnung“, umso mehr vom Programmfehler ausgehen. Anhand der Fehler-„Betrag+Belegnummer“ die Eingabe rekonstruieren, eine Sonde schreiben, die den Fehler exakt reproduziert; Reproduktion = Ursache gefunden.

1. Aktivierung und Schreibkanäle (ADT / MCP)

  • HTTP 200 ≠ Aktivierung erfolgreich – Erfolg/Misserfolg steht im Body: explizit auf activationExecuted="false" prüfen und Fehler werfen. Der einzige zuverlässige Weg, die Aktivierung zu verifizieren, ist der Rücklesevergleich von include oder ?version=active mit der inaktiven Version. TRDIR-RSTAT wird in S/4 nicht geschrieben.
  • Scheinerfolg am schwersten zu erkennen – Alte Skripte suchten mit Regex nach <chklst:shortText> / <msg>text</msg>. Die echte Struktur ist chkl: Präfix + <shortText><txt> + msg mit Attributen => Keine einzige Meldung wird gefunden, selbst nach dreimaliger Meldung „Aktivierung erfolgreich“ ist es ein 131-Zeichen-Leergehäuse. Wird keine Meldung gefunden, muss als Fehler behandelt werden.
  • Textelemente sind eigenständig aktivierbare Objekte PROG/PX – Aktivierung des Elternprogramms aktiviert sie nicht mit; push_textelements ohne GET→LOCK→PUT→UNLOCK und ohne ACTIVATE schreibt nur die inaktive Version. PROG/P senden = SAP sieht nichts Aktivierbares, retourniert activationExecuted="false" und keine einzige Meldung => stille Wirkungslosigkeit. Der Aktivierungsendpunkt akzeptiert kein corrNr (sonst 400).
  • push_textelements PUT überschreibt komplett, Reihenfolge darf nicht vertauscht werden – Neue Symbole: erst Textelemente, dann Quellcode; Löschen von Symbolen: erst Quellcode, dann Textelemente. TE_FILE muss zusammen mit allen vorhandenen selections vollständig übergeben werden.
  • Bei nur inaktiver Version von Textelementen: Programm muss neu generiert werden, um Änderungen zu aktivieren – Nur Textelemente pushen + dann Quellcode mit identischem Inhalt PUT => keine Aktualisierung. Eine Codezeile ändern (auch Kommentar) und dann pushen oder Programm neu anlegen.
  • Drei Arten von „Pseudo-Sperren“ – 1) Meldung „Bereits in Transportauftrag yyy von Benutzer xxx gesperrt“ ist eigentlich fehlender transport_request/corrNr; 2) UpdateDataElement meldet locked by another user – tatsächlich 400 „bereits vorhanden“ – Handler isAlreadyExistsError() erkennt nur englische Meldungen, nicht chinesische, fällt auf 403-Fallback mit Übersetzung „gesperrt“ (⇒ in chinesischer Umgebung können vorhandene Datenelemente nicht geändert werden, UpdateDomain dagegen hat diese Einschränkung nicht); 3) 403 … not specified for this client = falscher Mandant.
  • Echte Sperren nur über SM12 lösbar – Die drei Sperrtypen ESDICT/ESRDIRE/SEOCLSENQ hängen an ADT-Sessions. DEQUEUE_ALL, _scope='3', DEQUEUE_EESDICT alle wirkungslos. Nach Bestätigung via ENQUEUE_READ Benutzer bitten, die Sperre in SM12 manuell zu löschen und sofort neu zu starten; nicht auf Session-Timeout warten.
  • Zwei 403-Meldungen haben völlig unterschiedliche Bedeutung – User XXX is currently editing ZFIDxxx = echter Benutzer mit SE80 offen, warten; Object ... locked in task DEVK90011x not specified for this client = falscher Mandant, mit dem Transportauftrag des richtigen Mandanten neu pushen. Verwechslung führt zu sinnlosen Suchschleifen.
  • ADT LOCK Accept-Header muss Datentyp enthalten – Nur application/xml senden ⇒ SAP sperrt erfolgreich, bekommt aber 406 und kann LOCK_HANDLE nicht auslesen ⇒ Sperrleck, alle folgenden Requests melden „Benutzer X bearbeitet gerade“ und täuschen vor, ein anderer sei schuld. Richtig: application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result.
  • Skript fail() mit process.exit() überspringt finally ⇒ hängende Enqueue-Sperre – Nach abnormalem Beenden eines Push-Skripts bleibt SEOCLSENQ 40 Minuten lang, alle folgenden LOCK-Requests erhalten 403 wie „Benutzer mit SE80 offen“. Skripte schreiben: zuerst UNLOCK, dann beenden, oder process.exitCode = 1 verwenden.
  • Niemals Editier-Sperren für Benutzer gewaltsam löschen – ADT-Sperre löschen würde ungespeicherte Änderungen des Benutzers vernichten.
  • Includes sind eigenständige ADT-Ressourcen – POST an /programs/programs erzeugt immer ein ausführbares Programm (TRDIR-SUBC=1), später nicht änderbar; Include-POST an /sap/bc/adt/programs/includes (Root-Element <include:abapInclude>). Hauptprogramm und Include müssen mit derselben activation-Request aktiviert werden, sonst Fehler wegen undefinierter Referenzen.
  • INSERT REPORT ... STATE 'A' erzeugte Includes schreiben Quellcode als aktive Version – Spätere Änderung der inaktiven Version → Aktivierung des Hauptprogramms kompiliert mit der alten aktiven Version, Fehler verweisen auf alten Code. Nach Änderung muss INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'. (STATE vor PROGRAM TYPE) die aktive Version aktualisieren.
  • Lokaler Proxy kann ADT-Requests blockieren – Symptom identisch mit „Verbindung zu SAP verloren“ (socket hang up / CSRF-Fehler). Vor spawnen des MCP-Prozesses *_proxy löschen und NO_PROXY=* setzen; schreibende Requests immer mit proxy: false.
  • ADT hat keine dynpro / GUI Status / Menu Ressourcen – CUA kann aber codiert werden (RS_CUA_INTERNAL_FETCH + _WRITE, state='A', sofort wirksam). D020S ist der dynpro-Screen-Header, nicht die GUI-Status-Tabelle; RS_CUA_GET_STATUS ist nicht zur Beurteilung geeignet, nur RS_CUA_INTERNAL_FETCH (alle 10 Pflicht-TABLES angeben, ORDER BY pposition gegen 25-Zeilen-Abschneidung).

2. Syntax-Zwänge (einmal durch den Compiler)

  • FORM-Parameter vier Tabus – 1) Kein TYPE c LENGTH n (meldet Numeric literals cannot be used as field names) ⇒ TYPE char<n> oder DDIC-Typ; 2) Feste Länge empfängt unterschiedlich lange Parameter – Compiler warnt inkompatibel, Textelemente ausgenommen, aber zur Laufzeit dennoch 500 Type conflict when calling a FORM ⇒ generisch mit TYPE c; 3) Kein Inline TYPE STANDARD TABLE OF (Parameter wird als 8 gezählt, meldet Different number of parameters) ⇒ vorher TYPES definieren; 4) FIELD-SYMBOL in FORM lokal – für Form-übergreifende Verwendung global deklarieren.
  • FORM ... TABLES muss vor USING/CHANGING stehen – Sonst Field "TABLES" is unknown. (Schlüsselwort als Parametername interpretiert). PERFORM-Aufruf analog.
  • CALL FUNCTION Parameter-Reihenfolge ist zwingend – EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. TABLES vor IMPORTING ⇒ Unable to interpret "EV_OBJNR", Ursache (Reihenfolge) nicht ersichtlich. EXPORTING des gerufenen Moduls muss im aufrufenden IMPORTING-Abschnitt stehen – sonst CALL_FUNCTION_PARM_UNKNOWN – leicht fälschlich als „FM-Schnittstelle nicht generiert“ interpretiert.
  • Mehrere Felder in SELECT durch Komma trennen, @ für Host-Variablen zwingend – elements in the "SELECT LIST" list must be separated using commas. UPDATE ... SET Mehrfachzuweisung analog; Konstanten (CONSTANTS) in WHERE ebenfalls mit @.
  • Position von INTO und UP TO n ROWS: wie vom Aktivator gemeldet. Bei JOIN/Unterabfragen/SQL-Ausdrücken ist INTO am Ende zwingend (SELECT 563); UP TO n ROWS vor WHERE. Verschiedene Statements verhalten sich unterschiedlich – auswendig lernen führt zu Fehlern.
  • ASSIGN ... CASTING TYPE c LENGTH <Variable> nicht erlaubt – Länge muss Literal sein; Weglassen von LENGTH führt bei Index-Überschreitung zu DUMP Invalid subfield access. Korrekt: cl_abap_container_utilities=>fill_container_c( ) (invers zur Standardfunktion READ_CONTAINER_C).
  • DESCRIBE FIELD – TYPE vor COMPONENTS – Auch wenn nur die Anzahl der Komponenten benötigt wird: DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS benötigt VALUE – CONSTANTS: c_x TYPE flag. ⇒ "VALUE ..." is not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n ungültig – Textsymbol kann nicht als Nachrichtentext mit WITH verwendet werden. Hilfs-FORM: lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • Feste Längen haben keinen Typnamen char<n> – TYPE char1000/char55/char220 alle illegal, melden Type "X" is unknown. Immer TYPE c LENGTH n. Falschen Typnamen erraten = eine Aktivierungsrunde verschwendet. Im Zweifel DD04L oder get_source prüfen.
  • class=>method nur für statische Methoden – Instanzmethoden: CREATE OBJECT lo. lo->method( ), sonst can only be specified with static methods.
  • Stringvorlage nicht als PERFORM ... USING-Argument zulässig – Field "|" is unknown; erst Variable zuweisen, dann übergeben.
  • CALL TRANSACTION ... WITH f1 v1 ist keine gültige Syntax – WITH nur bei AUTHORITY-CHECK. Parameterübergabe nur über USING bdcdata; SM30-Startbildschirm hat nur das Tabellenfeld (VIMDYNFLDS ohne Update-Modus-Feld), Änderungsmodus mit VIEW_MAINTENANCE_CALL + action = 'U'.
  • REPLACE ALL OCCURRENCES OF ... WITH '' führt bei diesem Typsystem zur Endlosschleife – Leerzeichen/Zeichen löschen: zeichenweise DO strlen TIMES + lv_txt+lv_i(1) Filtern.
  • CONCATENATE akzeptiert keine numerischen Werte – lv_cnt TYPE i ⇒ muss character sein; eingebaute Funktionen (strlen/lines) nicht direkt in && (meldet The syntax for a method specification is objref->method, was fälschlich auf Pfeilsyntax hindeutet). Numerische Werte immer mit Stringvorlage |...{ lv_cnt }...|.
  • In $TMP Sonden: kein FORM, Kommentare außerhalb von Methoden werden nicht gespeichert – Sonde aktivieren meldet Statement is not accessible (CheckProgram dagegen nicht); Kommentare zwischen ENDMETHOD und nächstem METHOD in Klasse melden unknown comments which can't be stored.
  • SELECT-OPTIONS Name ≤ 8 Zeichen – 9 Zeichen: erste Aktivierung wird abgelehnt. Kürzer umbenennen erfordert gleichzeitige Änderung des Eintrags im Textelement selections (alte Variante mit altem Namen wird still ignoriert, nur Hinweis, kein Fehler).

3. Stille Fehlerliste (die teuerste Sektion)

Alles, was „S / Aktivierung 200 / Kompilierung OK“ meldet, aber nicht richtig funktioniert – zuerst hier checken.

3.1 Datenverschiebung und -verlust

  • SELECT mit expliziter Feldliste INTO TABLE <vorher deklarierte Tabelle mit vollem Typ> – Zuordnung nach Position – Bei passendem Typ kein Fehler, keine Warnung: DD07T-DOMVAL landet in DOMNAME, COEP-WKGBTR in MEGBTR (Betrag immer leer), ZTEPIC3A meldet component "ZJK" not compatible with "ZJYRQ" (Position mit _act_dbg ermittelt). Ein Fallstrick tritt in 5 Berichten wiederholt auf. Drei Lösungen: SELECT * / INTO TABLE @DATA(...) inline (Struktur durch Liste bestimmt) / eigene schmale Struktur in Spaltenreihenfolge; oder INTO CORRESPONDING FIELDS OF TABLE.
  • LEFT JOIN rechte Tabellenfelder in WHERE ⇒ semantische Degradation zu INNER JOIN – Bei fehlendem Match der rechten Tabelle = NULL, Bedingung nicht erfüllt. Filterbedingungen in ON verschieben.
  • DELETE ADJACENT DUPLICATES ... COMPARING <nicht Schlüsselfeld> löscht Daten – Beispiel A017: Primärschlüssel enthält DATBI, KNUMH ist kein Schlüssel. COMPARING knumh löscht alle nachfolgenden Gültigkeitszeiträume mit gleichem KNUMH. Vor dem Löschen SORT BY über alle Schlüssel, dann COMPARING über alle Schlüssel.
  • AT NEW / AT END OF basiert auf physischer Nachbarschaft, nach ALV-Rückschreiben ungültig – REUSE_ALV_* sortiert t_outtab um (Benutzer sortiert nach einer Spalte), dann nicht mehr benachbart, AT END OF wird mehrfach ausgelöst. Gruppierung ersetzen durch „äußere Schleife über die eindeutigen Schlüssel × innere Schleife, die alle zugehörigen Einträge sammelt“ – Gruppengrenze nur über Geschäftsschlüssel.
  • APPEND überschreibt sy-tabix – Nach APPEND INITIAL LINE ... ASSIGNING <fs> ist sy-tabix die Abschnittsnummer, nicht die Zeilennummer ⇒ Intervall wird vergrößert, 128 Zeilen werden zu 558 (derselbe Eintrag 43 mal wiederholt). In der Schleife zuerst lv_tabix = sy-tabix. sichern.
  • READ TABLE ... WITH KEY ohne Treffer setzt sy-tabix auf 0 – Anschließend APPEND sy-tabix TO lt_del fügt 0 in Löscheliste, DELETE ... INDEX 0 führt sofort zu DUMP TABLE_INVALID_INDEX. In Schleife Zeilennummer speichern + vor Löschen IF idx >= 1 AND idx <= lines( ).
  • DATA(...) inline in IF-Zweig deklariert – Bei nicht erfüllter Bedingung wurde die interne Tabelle nie erzeugt ⇒ folgender LOOP verursacht Kurzdump. Deklaration an den Methodenkopf verschieben oder vor Datenzugriff auf Leerheit prüfen.

3.2 BAPI / Standard-FMs „Rückgabe S oder kein Fehler, aber nicht geschrieben“

  • BAPI x-Struktur muss zusammen mit der Ursprungsstruktur übergeben werden – Nur depreciationareas, kein depreciationareasx ⇒ AFASL gefüllt, Fehler No change parameter given for depreciation area 01. Fehlende x-Struktur: kein Syntaxfehler, nur ausgelöst, wenn Datenbedingungen erfüllt sind.
  • Daten-Element von SD-Erweiterungsfeld-ID-Struktur muss CHAR1 sein – SD_SALESDOCUMENT_CHANGE über GET_CHANGEABLE_FIELDS listet nur Felder mit rollname = 'CHAR1' OR 'CHAR01' auf. Felder mit BAPIUPDATE werden komplett ausgeschlossen ⇒ Rückgabe S, Wert unverändert, kein Fehler – im Erstellungsfall wird diese Liste nicht verwendet ⇒ typisch: „Anlegen geht, Ändern nicht“. Nicht auf BAPIUPDATE umstellbar.
  • BAPIPAREX-VALUEPART1 nur 240 Zeichen – Standard-FM liest VALUEPART1~4 (960 Zeichen) fortlaufend über Offset. Nur erstes Segment füllen ⇒ restliche still abgeschnitten (BAPE_VBAP 271 Zeichen, letzte 3 Felder leer, kein Fehler). Nach 240 aufteilen, 4 Segmente füllen.
  • Gutschrift-/Belastungs-/Auftragsarten ohne Planzeilen: Menge nur in TARGET_QTY – BAPISDITM hat kein Feld mit Referenz auf KWMENG; Positionskategorie hat keinen Eintrag in TVEPZ ⇒ keine Planzeilen generiert, übergebene BAPISCHDL werden still verworfen, Rückgabe S, aber KWMENG=0, NETWR=0, VBEP 0 Zeilen. Bestimmung: Eintrag in TVEPZ vorhanden?, nicht TVAP-LFREL (LFREL sowohl für Z001 als auch Z014 leer), nicht VBTYP. Lesen derselbe Fallstrick: VBAP-KWMENG immer 0, Menge in VBAP-ZMENG ⇒ Logik: „KWMENG nicht leer bevorzugen, sonst auf ZMENG zurückfallen“.
  • BAPE_* erstes Feld ist Schlüssel, bei Änderungsmodus nicht übergeben → Belegnummer gelöscht – Standard-FM kopiert EXTENSIONIN mit move-corresponding nach E_VBAKKOM, leeres VBELN überschreibt die Nummer ⇒ V1 045 gefolgt von V4 219 (Ursache in der E-Meldung vor V4 219 suchen).
  • SD-Referenzbelege: Dreier-Set – REF_DOC + REF_DOC_IT + REF_DOC_CA (Referenzbelegkategorie); nur die ersten beiden ⇒ V2 068 ohne Belegkategorie, aber mit C (&2 leer). Zeilennummer 0 oder Beleg nicht vorhanden = gleiche Meldung, aber &3 leer. Kategorie = VBAK-VBTYP des referenzierten Belegs, nicht Kopfzeile VGTYP.
  • SD_SALESDOCUMENT_CHANGE – Eingangstabellen jeweils sortieren – Zu löschende Zeilen müssen in ITEM_IN und ITEM_INX beide ITM_NUMBER enthalten; nur ITEM_INX führt zu falscher Zuordnung mit völlig unzusammenhängender Meldung T184 Keine Positionskategorie verfügbar.
  • BAPI_USER_EXISTENCE_CHECK liefert in diesem System für beliebige Benutzer 0 zurück (auch ZZZZZZ999 gilt als vorhanden) ⇒ Prüfung wirkungslos, Belege an ungültige Benutzer werden von beiden Oberflächen nicht gesehen. Direkt USR02 prüfen + Längenbeschränkung (0 < len <= 12).
  • DELETE_TEXT für nicht vorhandenen Text wirft RAISING NOT_FOUND – Standard-FM fügt die E-Meldung an BAPIRET2, von frm_check_return als Fehler gewertet ⇒ „Löschen“ führt zu Schnittstellenfehler. Vor dem Löschen STXH prüfen (TDOBJECT+TDNAME+TDID+TDSPRAS). Falls nicht vorhanden, überspringen (Operation wird damit idempotent). Zusätzlich: In SD_SALES_DOCU_MAINTAIN ist der Funktionscode für Löschen 003 (DELETE_TEXT); &&/005 schreibt nur eine leere Zeile.
  • Neue Konditionen: Schrittnummer nicht selbst vergeben – Hartcodiert COND_ST_NO='001', aber ZP01 hat STUNR=010 ⇒ Meldung „Konditionsart nicht in Schrittnummer 001“. Leer lassen, automatische Positionierung durch Konditionsverarbeitung; Flag UPDATEFLAG = I/U basierend auf Existenz der Kondition.
  • Bei Schnittstellen-Umbuchung gibt OBJ_KEY nur eine Belegnummer zurück ⇒ Anzahl Belege zu niedrig, Rückschreibung auf falschen Beleg. Layout: Belegnummer(10)+Buchungskreis(4)+Geschäftsjahr(4), das Jahr steht ab +14(4) (häufig fälschlich +10(4) verwendet, was den Buchungskreis ergibt).
  • BSEG-BUZEI ist NUMC(3), physisches Maximum 999 Zeilen pro Beleg, BAPI akzeptiert nur 990 – Aufteilung: max. 989 Details + 1 Saldo; Grenzfälle 999/1000/1997 müssen abgedeckt werden (1997 wird bei Prüfung >999 in 1000 Zeilen geschnitten, was überläuft).
  • BAPIACHE09 hat kein Feld EXCH_RATE – Bei Doppelwährung (00+10 Zeilen): nur 00er Zeile mit Kurs → System übernimmt Kurs aus OB08 für den Kopf (Eingabe 7, Ergebnis 8.28). Auch die CURR_TYPE='10'-Zeile muss exch_rate enthalten, sowohl für Detail- als auch Saldozeilen an zwei Stellen.
  • Doppelwährungsbeleg: Jeder ITEMNO_ACC benötigt zwei CURRENCYAMOUNT-Einträge, und jede Währungssumme muss 0 sein – Fehlende 10-Zeile ⇒ Inconsistent currency information; auch die durch Aufteilung entstandenen Saldozeilen müssen ergänzt werden.
  • UMSKZ hat in keiner der drei BAPI-Strukturen ein Feld – Bei Wechsel (W/S) über Standard-BAPI wird es still verworfen, Konto falsch. Nur über EXTENSION2 als Enhancement in die Datenbank schreiben oder komplett über POSTING_INTERFACE_* (FB01 Batch-Input).
  • READ_TEXT – Parameter NAME ist TDOBNAME(70), Argument TDNAME(16) verursacht Laufzeit-Typkonflikt – Kompilierung OK, mit leerem Eingabeparameter funktioniert es, aber bei vorhandenen Daten: 500 Type conflict during a function module call. Parameter mit TYPE tdobname deklarieren oder direkt die Cluster-Tabelle STXL lesen.

3.3 Texte / Beschreibungen / Spaltenüberschriften werden leer

  • Feld mit anonymem Inline-Typ ⇒ DD03T-Text leer ⇒ ALV-Spaltenüberschrift komplett leer – Feld muss ein Datenelement haben (danach ist DD03T mit 0 Zeilen korrekt, Text wandert nach oben). Strukturfeldtexte nur über DDL mit @EndUserText.label pro Feld; verschachtelte STRU/TTYP-Felder haben nie einen DD03T-Text (SAP-Standardstrukturen auch nicht). CreateStructure fields[].description landet nicht in DD03T.
  • Textelemente nicht aktiviert / nur inaktive Version ⇒ zur Laufzeit TEXT-xxx immer leer – ALV-Spaltenüberschrift fällt auf DDIC-Kurztext zurück, Statusspalte leer.
  • Domain nur Typ definiert, keine Werte gepflegt ⇒ F4 auf Selektionsbild öffnet keine Liste – DD07T benötigt AS4LOCAL='A' (sonst inaktive Zeilen), ORDER BY VALPOS (sonst lexikografisch 1,10,11,2,3…), Sprachfallback auf Domain-Hauptsprache DD01L-DOMMASTER (Feldname nicht MASTERLANG).
  • MESSAGE ... WITH hat eine Obergrenze von ca. 70 Zeichen für den gesamten Text ⇒ Lange BAPI-Fehler werden still abgeschnitten (z.B. Field Reas...), lenkt die Fehlersuche in die falsche Richtung. Stattdessen kurzes Präfix + Stringvorlage mit Originaltext (≤220 Zeichen, passend zu BAPI_MSG).
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) still rechts abgeschnitten – Beim Zusammensetzen von „Präfix+Suffix“ zuerst das Präfix auf Länge - Suffixlänge kürzen.
  • DDIC-Komponentenname (30 Zeichen) still abgeschnitten – JSON-Schlüsselzuordnung verliert dadurch unerwartet Schlüssel.

3.4 Berechtigungen und Filter

  • AUTHORITY-CHECK mit leerem Wert ist meistens „keine Berechtigung“ ⇒ zugehörige Datensätze dauerhaft unsichtbar – In Rollen wird EKGRP = space normalerweise nicht vergeben. Organisationsstufen-Felder leer: Prüfung überspringen und direkt zulassen – IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF.. Statt kompletter Bildschirmsperre zeilenweise filtern, sonst können Benutzer ohne Berechtigung den Transaktionscode gar nicht erst ausführen.
  • MODIF ID steuert nur Sichtbarkeit, ausgefüllte Werte in versteckten Feldern nehmen trotzdem am Filter teil – Verstecken-Szenario: auf der Datenseite explizit mit RANGE abfangen.

4. Transaktionen und LUW

  • EXPORT ... TO DATABASE (Cluster-Log) unterliegt der Transaktionssteuerung – „Datenbanktabelle schreiben → Beleg anlegen → Fehler: Rollback“ ⇒ Log und Tabelle im selben LUW, ROLLBACK löscht auch die Snapshot-Eingabe von indx=01 (indx=02 bleibt). Zudem führt die unterlagerte Buchungs-FM intern BAPI_TRANSACTION_ROLLBACK durch – daher wirkt weder das Entfernen des eigenen ROLLBACK noch das Ändern der Reihenfolge. ⇒ Lösung: Vor dem einzigen Schreibzugriff und vor dem Aufruf der BAPI ein separates COMMIT WORK einfügen – ROLLBACK bleibt erhalten. Zusatznutzen: Der eigene LUW wird vor dem BAPI-Aufruf konsolidiert – ohnehin empfohlene Vorgehensweise.
  • Atomarität erfordert gemeinsamen Commit; unabhängige Commits erfordern Rückleseprüfung – SD_SALESDOCUMENT_CHANGE mit CALL_FROM_BAPI='X' hängt das Update in die Update-Warteschlange – ohne Commit nicht sichtbar. Entfernt man COMMIT WORK AND WAIT, führt die nachfolgende SELECT-Rückleseprüfung fälschlich zu „Flag nicht aktualisiert“. Bei Fehler im Buchungszweig muss ROLLBACK WORK ergänzt werden, sonst bleibt das ausstehende SD-Update hängen und wird beim nächsten Commit fälschlich gespeichert.
  • Plattform-Snapshot bindet nur FM-Parameter, lokale Variablen der Funktion nicht – Nachrichten in lv_req/lv_resp sind von der Plattform aus nicht sichtbar; Monitor serialize ohne name_mappings gibt DDIC-Großbuchstaben-Namen aus, nicht den Vertrags-JSON. Geschäftsseitig eine eigene Nachrichten-Log-Tabelle + save( )-Methode an der einzigen Stelle, wo die Nachricht entsteht (Ende von frm_send) – ein separater Commit schließt genau die Lücke, die das Plattform-Rollback im Log hinterlässt.

5. Dialogfenster / F4 / ALV / Selektionsbild

  • POPUP_GET_VALUES RETURNCODE Semantik kontraintuitiv – 'A' = Abbrechen; Leerzeichen = Bestätigen und FIELDS sind zurückgeschrieben (LSPO4U01: IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). IF lv_rc <> 'A' behandelt „Bestätigen“ als „Abbrechen“ ⇒ Symptom: Nach Klick auf Bestätigen im Dialog passiert nichts (keine Meldung, keine Aktion, kein Dump).
  • POPUP_GET_VALUES öffnet F4 nur, wenn das Feld an eine DDIC-Suche Hilfe hängt – Prüfung: F4IF_DETERMINE_SEARCHHELP (0=vorhanden / 2=Feld vorhanden, keine Hilfe / 1=Feld nicht vorhanden). Nicht auf SKA1-SAKNR ausweichen – das fügt ALPHA-Nullen hinzu, '8000' wird zu '000008000', Prüfung schlägt fehl und Speicherformat ändert sich. ⇒ Stattdessen POPUP_GET_VALUES_USER_HELP + F4_FORMNAME + F4_PROGRAMNAME = sy-repid verwenden, Feldeigenschaften bleiben unverändert. Das exit-FORM wird vom FM mit 5 USING aufgerufen (tabname/fieldname/display_flag/returncode/value). Den ausgewählten Wert in den 5. Parameter schreiben und returncode auf 'F' setzen – dann aktualisiert der Dialog (kein sy-stepl, daher nicht über dynprofield zurückschreiben).
  • POPUP_GET_VALUES unterstützt kein bedingtes Ausblenden von Feldern – SVAL kann nur FIELD_ATTR steuern (Pflicht/Lesen). Typabhängig nur den passenden Feldsatz laden; bei Typwechsel des Benutzers Dialog neu öffnen (bei gleichbleibendem Typ sofort beenden, keine Endlosschleife).
  • F4IF_INT_TABLE_VALUE_REQUEST mit FIELD_TAB – jede Zeile muss INTTYPE ableitbar haben – Ableitungskette: TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME.

...

Verwandte Skills