yanghangmvp/sap-abap-pitfalls

Bibliothèque de pièges et solutions ABAP (transversale aux projets). Issue de plus de 100 rapports de livraison et d'erreurs quotidiennes d'un projet client ERP, elle compile environ 120 entrées « phénomène → cause racine → solution », accompagnées d'une checklist de démarrage de projet en cinq phases.

Qu'est-ce que sap-abap-pitfalls ?

Cette compétence est une bibliothèque de référence complète et transversale aux projets, regroupant environ 120 pièges ABAP identifiés et documentés à partir de livraisons et d'erreurs réelles sur un projet client SAP ERP. Elle vise à aider les développeurs ABAP à éviter les erreurs courantes et à résoudre efficacement les problèmes. La ressource est structurée en sections : des « dix règles d'or » essentielles, des contraintes syntaxiques du langage, une liste des échecs silencieux les plus coûteux, des problèmes de transaction et de LUW, des astuces pour les fenêtres pop-up, F4, ALV et écrans de sélection, ainsi que des techniques avancées de débogage (ST22). La compétence fournit également une checklist de démarrage de projet en cinq phases, conçue pour être lue avant le début d'un nouveau projet. Elle est utile pour diagnostiquer des symptômes tels que « la compilation réussit mais l'activation échoue », « l'activation réussit mais l'exécution ne fonctionne pas », « le retour est S mais les valeurs n'ont pas changé », ou « le message d'erreur n'a rien à voir avec la cause réelle ». Elle est conçue pour être utilisée avec Claude Code, Cursor, Codex et d'autres agents de codage IA. Le dépôt contient le fichier `SKILL.md` qui sert de guide principal, et `references/casebook.md` pour les détails complets des cas.

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

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Bibliothèque de pièges et solutions ABAP

Utilisation : Lire en entier avant de démarrer un nouveau projet, consulter par symptôme lors du dépannage. Cette compétence ne contient que des expériences « réutilisables entre projets » ; les adresses d'environnement, numéros de demande, listes d'objets se trouvent dans le plan général du projet (ex. sap-abap-project-ops). Les 120 entrées complètes (avec extraits de code et sources) se trouvent dans references/casebook.md.

Comment l'utiliser : Nouveau projet → consultez directement la §11 Checklist de démarrage (à exécuter dans l'ordre chronologique) ; projet déjà en cours → §10 Recherche rapide par symptôme pour localiser la section ; pour approfondir une entrée spécifique avec le code complet et la source → references/casebook.md.

Trois méta-règles (plus importantes que n'importe quel piège individuel) :

  1. Le message d'erreur n'est souvent pas lié à la cause réelle — Le compilateur ABAP signale l'erreur à l'endroit où il l'a « analysée », pas là où vous avez fait l'erreur. Supposez d'abord un problème d'ordre des sections / des mots-clés / d'incompatibilité de type, pas une erreur de syntaxe en soi.
  2. Le plus dangereux dans SAP n'est pas une erreur, c'est un échec silencieux — Les données peuvent être modifiées silencieusement sous trois fausses apparences : « retour S », « activation 200 », « compilation réussie ». Pour tout ce qui concerne les BAPI, SELECT INTO TABLE, EXTENSIONIN, les éléments de texte, les champs d'enhancement, faites toujours une vérification en base de données, ne vous fiez pas aux valeurs de retour.
  3. En cas d'échec, vérifiez ST22 systématiquement, ne devinez pas — L'analyse d'erreur du short dump donne directement procedure "XXX" "(FORM)" et la classe d'exception CX_SY_*, ce qui est un ordre de grandeur plus rapide que de tester chaque FORM un par un.

0. Dix règles d'or (les enfreindre = refaire)

  1. Pour toute modification d'un objet existant, la première étape est de relire l'état actif en production. Les utilisateurs modifient souvent des objets de production directement dans SE80 sans écraser l'archive locale. L'archive locale sert uniquement de comparaison, pas de base. get_source → diff avec la sauvegarde pour confirmer 0 différence → appliquer le patch → relire et diff → écraser l'archive en sens inverse.
  2. Les éléments de texte font partie de la base, get_source ne contient pas TEXTPOOL. Avant de pousser du texte de manière écrasante, vous devez read_textelements pour obtenir l'archive complète de la version active, la diff avec le JSON local, faire une fusion manuelle, puis pousser. Sinon, les modifications textuelles faites par l'utilisateur dans SE38 sont effacées sans laisser de trace, et le diff du code source ne montre rien.
  3. Ne jamais faire directement UPDATE / MODIFY sur une table standard. Pour modifier une pièce FI déjà comptabilisée, utilisez FI_DOCUMENT_CHANGE (groupe FACG, ne remplissez T_ACCHG qu'avec FDNAME+NEWVAL, ne transmettez pas I_BUZEI en en-tête, transmettez-le uniquement dans les lignes).
  4. Ne pas inventer de numéro de demande soi-même. Si un objet transportable est poussé sans CORR_NR ⇒ stoppez et signalez, attendez que l'utilisateur donne le numéro ; avant de créer un nouvel objet TADIR, signalez d'abord « type + nom suggéré + résultat de la vérification des doublons TADIR + raison de la continuation de la numérotation », attendez l'approbation avant de créer. Pour les modifications ultérieures d'objets existants déjà attachés à une demande, réutilisez le même numéro de demande, ne demandez plus.
  5. N'utilisez pas la classe Update* pour faire des appels exploratoires sur une interface. Elle écrit réellement — nous avons déjà utilisé UpdateFunctionModule en ne transmettant que le corps de la fonction « pour essayer », ce qui a écrasé le code source inactif en une version tronquée de 620 caractères. Les sondes ne passent que par des interfaces en lecture seule.
  6. Livraison en quatre étapes : Générer → Pousser le code source → Pousser les éléments de texte (PROG/PX nécessite une activation séparée) → Générer l'état GUI (CUA). Si les éléments de texte ne sont pas activés, TEXT-xxx est toujours vide à l'exécution.
  7. Commentaires du code source en production : garder la structure, supprimer les récits. Conservez l'en-tête du programme (unité/auteur/date/résumé/historique des modifications) + les lignes de cadre *&--- + les banderoles des FORM + la documentation des paramètres -->/<-- + les courts commentaires sur les critères de décision ; supprimez la justification de la conception, les descriptions des formules de calcul, les tests de bout en bout, les raisons des TODO, les références à scripts/work/ADT/ZTEST_*. Après le retrait, vous devez diff prouvant que le corps du code est inchangé.
  8. Supprimez les objets de test/sonde immédiatement après usage. D'abord get_source pour archiver dans backup/ → supprimer → revérifier TADIR pour confirmer qu'il est vide. Supprimez uniquement ceux que vous avez créés dans cette session ; ne touchez pas à ceux créés par d'autres, déjà attachés à un package/une demande, ou aux DEMO en cours d'utilisation.
  9. Vérifiez d'abord les verrous, sauvegardez d'abord, puis écrivez dans SAP. Ne partagez pas les fichiers d'identification, ne les affichez pas, ne les écrivez pas dans les livrables.
  10. Quand l'utilisateur dit « les données sont correctes », soyez d'autant plus convaincu que le programme a un problème. Utilisez le « montant de l'erreur + numéro de pièce » pour déduire les caractéristiques de l'entrée, écrivez une sonde qui reproduit mot pour mot cette erreur ; une fois la reproduction réussie, la cause racine est identifiée.

1. Canaux d'activation et d'écriture (ADT / MCP)

  • HTTP 200 ≠ activation réussie — Le succès ou l'échec est dans le body : vous devez explicitement détecter activationExecuted="false" et lever une erreur ; le seul moyen fiable de vérifier l'activation est de relire l'include ou de comparer les deux versions ?version=active et inactive. TRDIR-RSTAT n'est pas renseigné dans S/4.
  • Le faux succès est le plus difficile à détecter — Les anciens scripts utilisaient des regex pour capturer les messages avec <chklst:shortText> / <msg>text</msg>, mais la structure réelle est le préfixe chkl: + <shortText><txt> + msg avec des attributs ⇒ aucune message n'est capturé, et même trois « activation réussie » sont en fait une coquille vide de 131 caractères. Si aucun message n'est capturé, cela doit être considéré comme un échec.
  • Les éléments de texte sont un objet activable indépendant PROG/PX — L'activation du programme parent ne les active pas ; si push_textelements n'a que GET→LOCK→PUT→UNLOCK sans ACTIVATE, PUT n'écrit que la version inactive. En transmettant PROG/P, SAP considère qu'il n'y a rien à activer et retourne activationExecuted="false" sans aucun msg ⇒ échec silencieux. L'interface d'activation n'accepte pas corrNr (retourne 400).
  • Le PUT de push_textelements est un écrasement complet, l'ordre de poussée ne peut pas être inversé — Ajout de symboles : d'abord les éléments de texte puis le code source ; suppression de symboles : d'abord le code source puis les éléments de texte. TE_FILE doit être donné en entier avec les selections existants.
  • Quand les éléments de texte n'ont qu'une version inactive, le programme doit être régénéré pour qu'ils prennent effet — Pousser uniquement les éléments de texte + refaire un PUT du même code source ne les actualise pas. Modifiez une ligne de code source (même un commentaire) et repoussez, ou recréez le programme.
  • Trois types de « faux verrou » : ① Le message « déjà verrouillé dans la demande yyy de l'utilisateur xxx » est en fait dû à l'omission de transport_request/corrNr ; ② UpdateDataElement retourne locked by another user alors que c'est un 400 « existe déjà » — le isAlreadyExistsError() du handler ne reconnaît que l'anglais, pas le message chinois, et se rabat sur le 403 qui est traduit par « verrouillé » (⇒ dans un environnement ZH, MCP ne peut pas modifier un élément de données existant, UpdateDomain n'a pas cette restriction) ; ③ 403 … not specified for this client signifie que le mandant est incorrect.
  • Le vrai verrou ne peut être supprimé que via SM12 — Les trois types de verrous ESDICT/ESRDIRE/SEOCLSENQ sont attachés à la session ADT. DEQUEUE_ALL, _scope='3', DEQUEUE_EESDICT sont tous inefficaces. Après confirmation par ENQUEUE_READ, demandez à l'utilisateur de supprimer manuellement via SM12 et réexécutez immédiatement ; n'attendez pas l'expiration de la session.
  • Les deux messages 403 ont des significations complètement différentes — User XXX is currently editing ZFIDxxx = un vrai utilisateur a SE80 ouvert, attendez ; Object ... locked in task DEVK90011x not specified for this client = mandant incorrect, repoussez avec la demande du mandant correct. Les confondre vous fera perdre du temps inutilement.
  • L'en-tête Accept d'ADT LOCK doit inclure le type de données — Si vous envoyez uniquement application/xml, SAP a verrouillé avec succès mais retourne 406 sans LOCK_HANDLE ⇒ fuite de verrou, et toutes les requêtes suivantes signalent « l'utilisateur X édite actuellement », vous faisant toujours croire que quelqu'un d'autre l'occupe. Valeur correcte : application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result.
  • process.exit() dans fail() du script saute finally ⇒ verrou d'entrée de file d'attente résiduel — Après une sortie anormale d'un script de type push, le verrou SEOCLSENQ peut persister pendant 40 minutes, et toutes les demandes LOCK suivantes retournent 403 comme si « l'utilisateur avait SE80 ouvert ». Dans les scripts, faites toujours UNLOCK avant de quitter, ou utilisez process.exitCode = 1.
  • Ne supprimez pas de force le verrou d'édition d'un utilisateur — La suppression d'un verrou ADT entraîne la perte des modifications non enregistrées de l'autre partie.
  • Les includes sont des ressources ADT indépendantes — Un POST à /programs/programs crée toujours un programme exécutable (TRDIR-SUBC=1), impossible à modifier par la suite ; un include doit être POSTé à /sap/bc/adt/programs/includes (élément racine <include:abapInclude>). Le programme principal et l'include doivent être dans la même requête d'activation, sinon ils signalent mutuellement « non défini ».
  • INSERT REPORT ... STATE 'A' crée un include dont le code source est écrit en version active — Si vous modifiez ensuite la version inactive, l'activation du programme principal compile toujours avec l'ancienne version active, et les erreurs pointent vers l'ancien code. Après modification, vous devez exécuter INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'. (STATE doit être avant PROGRAM TYPE) pour actualiser la version active.
  • Le proxy local peut faire échouer les requêtes ADT — Les symptômes sont identiques à « impossible de se connecter à SAP » (socket hang up / échec CSRF). Avant de générer un processus enfant MCP, supprimez *_proxy et définissez NO_PROXY=* ; pour toutes les requêtes d'écriture, utilisez proxy: false.
  • ADT n'a pas de ressources dynpro / GUI Status / Menu — Mais CUA peut être codifié (RS_CUA_INTERNAL_FETCH + _WRITE, state='A', l'écriture prend effet immédiatement). D020S est l'en-tête d'écran dynpro, pas la table d'état GUI ; RS_CUA_GET_STATUS ne peut pas être utilisé pour la détermination, utilisez uniquement RS_CUA_INTERNAL_FETCH (les 10 TABLES obligatoires doivent toutes être fournies, ORDER BY pposition pour éviter la troncature à 25 lignes).

2. Contraintes syntaxiques strictes (compilation du premier coup)

  • Quatre interdits pour les paramètres FORM : ① Impossible d'écrire TYPE c LENGTH n (retourne Numeric literals cannot be used as field names) ⇒ utilisez TYPE char<n> ou un type DDIC ; ② Un paramètre formel de longueur fixe recevant des paramètres réels de différentes longueurs retourne une erreur de compilation incompatible, mais les symboles de texte font exception mais retournent toujours 500 Type conflict when calling a FORM à l'exécution ⇒ pour les longueurs variables, utilisez le type générique TYPE c ; ③ Impossible d'utiliser l'inline TYPE STANDARD TABLE OF (le paramètre formel est compté comme 8, retourne Different number of parameters) ⇒ déclarez d'abord avec TYPES ; ④ FIELD-SYMBOL déclaré dans un FORM est local, il doit être global pour être utilisé entre FORM.
  • FORM ... TABLES doit être écrit avant USING/CHANGING — Si vous écrivez USING ... CHANGING ... TABLES, vous obtenez Field "TABLES" is unknown. (le mot-clé est traité comme un nom de paramètre). C'est la même chose du côté de l'appel PERFORM.
  • L'ordre des sections CALL FUNCTION est obligatoire — EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. Si vous écrivez TABLES avant IMPORTING, vous obtenez Unable to interpret "EV_OBJNR", sans aucun lien apparent avec l'ordre. Les paramètres EXPORTING de la fonction appelée doivent être dans la section IMPORTING de l'appelant ; si inversé, vous obtenez CALL_FUNCTION_PARM_UNKNOWN — très facile à confondre avec « l'interface FM n'a pas été générée ».
  • Les champs multiples dans SELECT doivent être séparés par des virgules, les variables hôtes @ doivent être échappées — Retourne elements in the "SELECT LIST" list must be separated using commas. C'est la même chose pour les affectations multiples dans UPDATE ... SET ; les constantes (y compris CONSTANTS) dans WHERE nécessitent également @.
  • La position de INTO et UP TO n ROWS dépend de l'erreur de l'activateur — Les instructions avec JOIN/sous-requêtes/expressions SQL sont en mode strict, forçant INTO à la fin (SELECT 563) ; UP TO n ROWS doit être avant WHERE. Les deux positions se comportent différemment selon les instructions, recopier de mémoire peut échouer.
  • ASSIGN ... CASTING TYPE c LENGTH <variable> n'est pas supporté — La longueur doit être un littéral ; si vous supprimez LENGTH, en cas de dépassement d'indice, vous obtenez un DUMP Invalid subfield access. La bonne solution utilise cl_abap_container_utilities=>fill_container_c( ) (qui est l'inverse de la fonction standard READ_CONTAINER_C).
  • TYPE dans DESCRIBE FIELD doit être écrit avant COMPONENTS — Même pour obtenir uniquement le nombre de composants, vous devez écrire : DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS doit avoir VALUE — CONSTANTS: c_x TYPE flag. retourne "VALUE ..." is not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n n'est pas valide — Un symbole de texte ne peut pas être utilisé comme texte de message avec WITH. Passez par un FORM auxiliaire : lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • Les types de longueur fixe n'ont pas de nom char<n> — TYPE char1000/char55/char220 sont tous invalides, retournent Type "X" is unknown ; utilisez toujours TYPE c LENGTH n. Deviner le nom du type = gaspiller un cycle d'activation ; si vous n'êtes pas sûr, vérifiez dans DD04L ou via get_source.
  • class=>method ne peut être utilisé qu'avec des méthodes statiques — Pour une méthode d'instance, vous devez faire CREATE OBJECT lo. lo->method( ), sinon vous obtenez can only be specified with static methods.
  • Un template de chaîne ne peut pas être utilisé comme paramètre réel de PERFORM ... USING — Retourne Field "|" is unknown ; assignez-le d'abord à une variable, puis transmettez-la.
  • CALL TRANSACTION ... WITH f1 v1 n'est pas une syntaxe valide — WITH n'accepte que AUTHORITY-CHECK. Le transfert de paramètres ne peut se faire que via USING bdcdata ; le premier écran de SM30 n'a qu'un seul champ pour le nom de la table (VIMDYNFLDS n'a pas de champ de mode de mise à jour) ; pour entrer en mode modification, utilisez VIEW_MAINTENANCE_CALL + action = 'U'.
  • REPLACE ALL OCCURRENCES OF ... WITH '' peut créer une boucle infinie dans ce système de types — Pour supprimer des espaces/caractères, utilisez plutôt une boucle DO strlen TIMES + filtre lv_txt+lv_i(1).
  • CONCATENATE n'accepte pas les types numériques — lv_cnt TYPE i retourne must be character-like ; les fonctions intégrées (strlen/lines) ne peuvent pas être utilisées directement avec && (retourne The syntax for a method specification is objref->method, ce qui induit en erreur en faisant penser à une erreur de syntaxe de flèche). Pour les nombres, utilisez toujours le template de chaîne |...{ lv_cnt }...|.
  • Dans une sonde $TMP, vous ne pouvez pas définir de FORM ; les commentaires en dehors des méthodes ne sont pas sauvegardés — L'activation de la sonde retourne Statement is not accessible (CheckProgram ne le signale pas) ; les commentaires entre ENDMETHOD et le METHOD suivant dans une classe retournent unknown comments which can't be stored.
  • Le nom de SELECT-OPTIONS ≤ 8 caractères — Un nom de 9 caractères est rejeté lors de la première activation. Si vous raccourcissez le nom, vous devez simultanément modifier le nom de l'entrée selections dans les éléments de texte (les anciennes variantes stockent l'ancien nom et sont ignorées silencieusement, seulement un avertissement, pas d'erreur).

3. Liste des échecs silencieux (la section la plus coûteuse)

Quand vous avez un « retour S / activation 200 / compilation réussie » mais que le résultat n'est pas correct, commencez par vérifier ici.

3.1 Décalage et perte de données

  • SELECT avec une liste de colonnes explicite INTO TABLE <type de table pré-déclaré> assigne par position — Lorsque les types sont compatibles, il n'y a pas d'erreur ni d'avertissement : DOMVAL de DD07T tombe dans DOMNAME, WKGBTR de COEP tombe dans MEGBTR (le montant est toujours vide), ZTEPIC3A signale component "ZJK" not compatible with "ZJYRQ" (on trouve les coordonnées grâce à _act_dbg). Ce piège réapparaît dans 5 rapports. Trois solutions : SELECT * / INTO TABLE @DATA(...) inline (la structure est déterminée par la liste) / créer une structure étroite avec l'ordre des colonnes correspondant ; ou INTO CORRESPONDING FIELDS OF TABLE.
  • Un champ de la table de droite d'un LEFT JOIN utilisé dans WHERE ⇒ la sémantique dégénère en INNER JOIN — Lorsqu'il n'y a pas de correspondance dans la table de droite, la valeur est NULL et la condition n'est pas vérifiée. Déplacez les conditions de filtrage dans le ON.
  • DELETE ADJACENT DUPLICATES ... COMPARING <champ non-clé> peut supprimer des données — La clé primaire de la table condition A017 contient DATBI, KNUMH n'est pas une clé. La déduplication avec COMPARING knumh supprime toutes les périodes de validité suivantes pour le même KNUMH. Faites d'abord un SORT BY sur toutes les clés, puis un COMPARING sur toutes les clés.
  • AT NEW / AT END OF se base sur l'adjacence physique ; après une réécriture ALV, elle n'est plus valide — REUSE_ALV_* réorganise t_outtab sur place (déclenché par un tri de l'utilisateur sur une colonne de message), les mêmes groupes ne sont plus adjacents, AT END OF est déclenché plusieurs fois. Pour le regroupement, utilisez plutôt « boucle externe sur les clés de déduplication × boucle interne de collecte par clé », et les limites du groupe ne reconnaissent que les clés métier.
  • APPEND modifie sy-tabix — Après APPEND INITIAL LINE ... ASSIGNING <fs>, sy-tabix donne le numéro de segment et non le numéro de ligne source ⇒ l'intervalle est agrandi, 128 lignes sont affichées comme 558 lignes (la même ligne répétée 43 fois). En début de boucle, faites d'abord lv_tabix = sy-tabix..
  • Si READ TABLE ... WITH KEY ne trouve pas de correspondance, sy-tabix est mis à 0 — Ensuite, APPEND sy-tabix TO lt_del enregistre 0 dans la liste de suppression, et DELETE ... INDEX 0 provoque directement un DUMP TABLE_INVALID_INDEX. En début de boucle, stockez le numéro de ligne + avant la suppression, faites IF idx >= 1 AND idx <= lines( ).
  • Une déclaration inline DATA(...) dans une branche IF ne crée jamais la table interne si la condition n'est pas satisfaite ⇒ LOOP suivant provoque un short dump. Déplacez la déclaration en tête de méthode, ou faites une vérification IS INITIAL avant de récupérer les données.

3.2 BAPI / Fonctions standard « retournent S ou n'ont pas d'erreur mais n'écrivent pas »

  • La structure x d'une BAPI doit être transmise par paire avec la structure d'origine — Si vous transmettez depreciationareas sans depreciationareasx ⇒ quand vous remplissez AFASL, vous obtenez No change parameter given for depreciation area 01. L'absence de structure x ne provoque pas d'erreur de syntaxe, elle ne se déclenche que lorsque les conditions de données sont remplies.
  • L'élément de données du champ d'identification de la structure d'enhancement SD doit être CHAR1 — SD_SALESDOCUMENT_CHANGE utilise GET_CHANGEABLE_FIELDS, qui ne parcourt que les champs où WHERE rollname = 'CHAR1' OR 'CHAR01' ; ceux avec BAPIUPDATE sont tous exclus ⇒ retour S, valeur inchangée, zéro erreur, mais le chemin de création ne passe pas par cette liste ⇒ typiquement « la création peut écrire, la modification ne peut pas ». Impossible de revenir à BAPIUPDATE.
  • BAPIPAREX-VALUEPART1 n'a que 240 caractères — Les fonctions standard lisent séquentiellement VALUEPART1~4 par décalage (total 960). Si vous ne remplissez que la première partie, la fin est tronquée silencieusement (BAPE_VBAP mesuré à 271 caractères, les 3 derniers champs sont vides, aucune erreur). Remplissez en quatre parties de 240 caractères.
  • Type de commande d'avoir/note de crédit/sans planification : la quantité ne reconnaît que TARGET_QTY — BAPISDITM n'a aucun champ de référence vers KWMENG ; la catégorie d'article de ce type n'a pas de configuration dans TVEPZ ⇒ aucune ligne de planification n'est générée, le BAPISCHDL émis est silencieusement ignoré, retour S mais KWMENG=0, NETWR=0, VBEP 0 ligne. Le critère est la présence d'une entrée dans TVEPZ, pas TVAP-LFREL (LFREL de Z001 et Z014 sont tous deux vides), ni VBTYP. Même piège côté lecture : VBAP-KWMENG est toujours 0, la quantité est dans VBAP-ZMENG ⇒ la formule de réaffectation est « priorité à KWMENG non vide, sinon ZMENG ».
  • Le premier champ de BAPE_* est la clé ; si elle est omise en mode modification, le numéro de document est effacé — Les fonctions standard font un move-corresponding de la valeur EXTENSIONIN dans E_VBAKKOM. Si VBELN est vide, il écrase le numéro de document avec une valeur vide ⇒ V1 045 suivi de V4 219 (la cause racine est le message E précédant V4 219).
  • Le document de référence SD est un trio — REF_DOC + REF_DOC_IT + REF_DOC_CA (catégorie de document de référence) ; ne transmettre que les deux premiers retourne V2 068 sans catégorie de document mais avec C (&2 vide). Si le numéro de ligne est 0 ou si le document n'existe pas, c'est le même message mais &3 est vide. La catégorie est le VBAK-VBTYP du document de référence lui-même, pas le VGTYP d'en-tête.
  • L'entrée de SD_SALESDOCUMENT_CHANGE trie deux tableaux séparément — Pour supprimer une ligne, ITEM_IN et ITEM_INX doivent tous deux avoir ITM_NUMBER ; ne remplir que ITEM_INX provoque un décalage et retourne une erreur totalement non pertinente T184 pas de catégorie d'article disponible.
  • BAPI_USER_EXISTENCE_CHECK retourne 0 pour n'importe quel compte dans ce système (même ZZZZZZ999 est accepté) ⇒ la vérification est inefficace, un document transféré à un compte invalide est invisible dans les deux interfaces. Vérifiez directement USR02 + protection de longueur (0 < len <= 12).
  • DELETE_TEXT sur un texte inexistant lève RAISING NOT_FOUND — La fonction standard ajoute ce message E à BAPIRET2, qui est considéré comme un échec par frm_check_return ⇒ une opération « vider » transforme l'interface en E. Avant de vider, vérifiez d'abord STXH (TDOBJECT+TDNAME+TDID+TDSPRAS), si inexistant, sautez (ce qui rend l'opération idempotente). Aussi : dans SD_SALES_DOCU_MAINTAIN, le code fonction pour vider est 003 (DELETE_TEXT) ; transmettre &&/005 ne fait que réécrire le texte en une ligne vide.
  • Ne spécifiez pas le numéro d'étape lors de l'ajout d'une nouvelle condition de prix — Coder en dur COND_ST_NO='001' alors que ZP01 est à STUNR=010 ⇒ retourne « le type de prix n'est pas dans le numéro d'étape 001 ». Laissez vide, la procédure de prix le localisera automatiquement ; utilisez « la condition existe-t-elle déjà » pour piloter UPDATEFLAG (I/U).
  • Lors de la deuxième décomposition d'une interface, OBJ_KEY ne retourne qu'un seul numéro de pièce ⇒ le nombre de pièces est sous-estimé, et l'écriture par ligne pointe vers la mauvaise pièce. La disposition de OBJ_KEY est numéro de pièce(10)+code société(4)+exercice comptable(4), l'exercice est à +14(4) (souvent écrit par erreur +10(4), ce qui prend le code société).
  • BSEG-BUZEI est NUMC(3), une pièce a une limite physique de 999 lignes, BAPI n'en accepte en réalité que 990 — La limite de fragmentation est de 989 détails + 1 différence ; la limite doit couvrir 999/1000/1997 (1997 serait divisé à >999 donnant 1000 lignes, ce qui dépasse).
  • BAPIACHE09 n'a pas de champ EXCH_RATE — Avec deux devises (lignes 00+10), si seule la ligne 00 a un taux de change, le système utilise OB08 pour redéfinir le taux d'en-tête (saisie 7, résultat 8.28). La ligne CURR_TYPE='10' doit aussi avoir exch_rate, et les deux endroits (ligne de détail et ligne de différence) doivent l'avoir.
  • Pour une pièce en deux devises, chaque ITEMNO_ACC doit avoir deux entrées CURRENCYAMOUNT, et le sous-total de chaque devise doit être 0 — S'il manque une ligne 10, retourne Inconsistent currency information ; la ligne de différence générée par la décomposition doit également être complétée.
  • UMSKZ n'a pas de champ dans les trois structures BAPI — Pour les types d'effets (W/S), le passage par le canal BAPI standard est silencieusement ignoré et le compte est mal enregistré. Il ne peut être enregistré que via l'enhancement EXTENSION2, ou en passant par POSTING_INTERFACE_* (saisie par lot FB01).
  • Le paramètre NAME de READ_TEXT est TDOBNAME(70), le paramètre réel TDNAME(16) provoque un conflit de type à l'exécution — La compilation réussit, les entrées vides fonctionnent normalement, mais dès qu'une donnée est trouvée, une erreur 500 Type conflict during a function module call se produit. Déclarez le paramètre avec TYPE tdobname, ou lisez directement la table cluster STXL.

3.3 Texte / description / titre de colonne vide

  • Champ avec un type anonyme intégré ⇒ DD03T a un texte vide ⇒ les titres de colonnes ALV sont blancs — Le champ doit être lié à un élément de données (une fois lié, DD03T a 0 ligne, ce qui est correct, le texte remonte). Le texte des champs d'une structure ne peut être défini que via DDL avec @EndUserText.label par champ ; les champs de type STRU/TTYP imbriqués n'ont jamais de texte DD03T (c'est également le cas pour les structures standard SAP). CreateStructure avec fields[].description n'entre pas dans DD03T.
  • Éléments de texte non activés / seulement version inactive ⇒ TEXT-xxx toujours vide à l'exécution — Les titres de colonnes ALV reviennent au texte court DDIC, les colonnes d'état sont vides.
  • Domaine défini seulement avec un type, sans liste de valeurs ⇒ F4 de l'écran de sélection n'affiche pas de propositions — La consultation de DD07T doit inclure AS4LOCAL='A' (sinon, elle prend les lignes inactives), ORDER BY VALPOS (sinon, elle prend l'ordre lexicographique 1,10,11,2,3…), et la langue par défaut est la langue maîtresse DD01L-DOMMASTER (le nom du champ n'est pas MASTERLANG).
  • MESSAGE ... WITH a une limite d'environ 70 caractères pour le texte entier ⇒ Les longues erreurs BAPI sont silencieusement tronquées en Field Reas..., ce qui induit en erreur dans le diagnostic. Utilisez un préfixe court + un template de chaîne pour le texte original (≤220 caractères, aligné sur BAPI_MSG).
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) troncation à droite silencieuse — Lors de la construction « préfixe+suffixe », tronquez d'abord le préfixe à longueur - longueur du suffixe.
  • Les noms de composants DDIC sont tronqués silencieusement à 30 caractères — Lors du mappage de clés JSON, certaines clés peuvent disparaître de manière inexpliquée.

3.4 Autorisations et filtres

  • AUTHORITY-CHECK avec une valeur vide signifie généralement « pas d'autorisation » ⇒ les enregistrements concernés sont définitivement invisibles — Les rôles n'attribuent pas EKGRP = space. Lorsqu'un champ de niveau organisationnel est vide, aucune vérification n'est effectuée, l'accès est autorisé : IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF. Utilisez un filtrage au niveau des lignes plutôt qu'un refus sur tout l'écran, sinon les utilisateurs non autorisés ne peuvent même pas entrer dans le code de transaction.
  • MODIF ID ne contrôle que l'affichage/masquage ; les valeurs déjà saisies dans les champs masqués participent toujours au filtrage — Pour les scénarios de masquage, utilisez explicitement un RANGE dans la partie récupération des données pour bloquer.

4. Transactions et LUW

  • EXPORT ... TO DATABASE (journal cluster) est contrôlé par la transaction — « Écrire dans la table de base → créer la pièce → échec rollback » fait que le journal et la table de base sont dans la même LUW, ROLLBACK consomme également l'instantané des paramètres d'entrée indx=01 (mais pas indx=02). De plus, le FM de comptabilisation sous-jacent peut faire BAPI_TRANSACTION_ROLLBACK en interne, donc « supprimer son propre ROLLBACK » ou « changer l'ordre » est inefficace. ⇒ Solution : avant la seule action d'écriture en base de données, et avant d'appeler la BAPI, insérez un COMMIT WORK indépendant, le ROLLBACK conserve les données. Avantage supplémentaire : faire le COMMIT WORK de sa propre LUW avant d'appeler une BAPI est une bonne pratique.
  • Pour l'atomicité, ne validez pas chacun de votre côté ; pour des validations indépendantes, gardez la relecture — SD_SALESDOCUMENT_CHANGE avec CALL_FROM_BAPI='X' met la mise à jour dans la file d'attente des mises à jour ; si elle n'est pas validée, elle n'est pas visible ⇒ supprimer COMMIT WORK AND WAIT puis garder une vérification de relecture SELECT conduit nécessairement à un faux diagnostic « indicateur non mis à jour ». Dans la branche d'échec de la comptabilisation, vous devez ajouter ROLLBACK WORK, sinon la mise à jour SD en attente reste suspendue jusqu'à la prochaine validation et est enregistrée par erreur.
  • L'instantané de la plateforme ne lie que les paramètres FM, les variables locales à l'intérieur de la fonction ne sont jamais liées — Le message dans lv_req/lv_resp n'est pas visible sur la plateforme ; le moniteur serialize sans name_mappings sort les noms DDIC en majuscules au lieu du message du contrat. Créez votre propre table de journal des messages côté métier + une ligne save( ) écrite au seul endroit où le message est généré (à la fin de frm_send), la validation indépendante comble justement le manque à gagner des journaux de la plateforme consommés par le rollback.

5. Fenêtres pop-up / F4 / ALV / Écrans de sélection

  • La sémantique de RETURNCODE de POPUP_GET_VALUES est contre-intuitive — 'A' = Annuler ; espace = Confirmer et FIELDS a été renseigné (LSPO4U01 : IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). Si vous écrivez IF lv_rc <> 'A', vous traitez « Confirmer » comme « Annuler » ⇒ le symptôme est après avoir cliqué sur Confirmer dans la popup, rien ne se passe (pas de message, pas d'action, pas de dump).
  • POPUP_GET_VALUES n'affiche F4 que lorsque le champ peut être lié à une aide à la recherche DDIC — Le critère est F4IF_DETERMINE_SEARCHHELP (0=existe / 2=le champ existe mais pas d'aide / 1=le champ n'existe pas). Ne pas essayer d'utiliser SKA1-SAKNR pour emprunter une aide — cela introduirait un remplissage à zéro ALPHA, '8000' deviendrait '000008000', la validation serait erronée et le format de stockage changerait. ⇒ Utilisez plutôt POPUP_GET_VALUES_USER_HELP + F4_FORMNAME + F4_PROGRAMNAME = sy-repid, les attributs du champ restent inchangés. Le FORM de sortie est appelé par FM avec 5 paramètres USING (tabname/fieldname/display_flag/returncode/value), pour écrire la valeur sélectionnée dans le 5ème paramètre et mettre returncode à 'F' pour que la popup se rafraîchisse (ne peut pas obtenir sy-stepl, ne passez pas par dynprofield pour la réaffectation).
  • POPUP_GET_VALUES ne supporte pas le masquage conditionnel de champs — SVAL ne peut contrôler que FIELD_ATTR (obligatoire/lecture seule). Pour charger uniquement l'ensemble de champs correspondant à un type, si l'utilisateur change le type, réaffichez la popup avec le nouveau type (si le type ne change pas, sortez sans boucle).
  • F4IF_INT_TABLE_VALUE_REQUEST avec FIELD_TAB doit pouvoir déduire INTTYPE pour chaque ligne — La chaîne de déduction est TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME. Les appels de SCV directement avec FIELD_TAB ne remplissent pas les champs de la table, et lors d'une deuxième recherche F4 après une saisie manuelle, la sélection de l'historique est inefficace ⇒ obsolète. La variante standard fieldname passe par ENQUEUE/DEQUEUE pour le champ de saisie, F4IF peut fournir helpprocessor, utilisez d'abord cette voie.

6. Interface / JSON / Passerelle (OData / RFC)

  • CALL TRANSFORMATION vs CL_THE_XML_DOCUMENT : le premier ne supporte pas la dé-sérialisation des nombres en type C — XML <AMOUNT>100.50</AMOUNT> correspondant au champ CHAR20(100.50) → ne peut pas être converti en C. Les types numériques (D, F, I, P) peuvent être convertis de manière réversible en C. La structure d'assistance doit être amt TYPE string. Le problème inverse n'existe pas, STRING peut recevoir n'importe quoi.
  • /UI2/CL_JSON=>DESERIALIZE ne convertit pas automatiquement les chaînes en nombres — Le JSON { "WRKST": "1" } correspond au champ WRKST TYPE p LENGTH 3 DECIMALS 1 → le nombre est mappé à la chaîne 1 dans le champ P, ce qui donne 1 (correct). Mais les nombres décimaux saisis comme 01.50 sont lus comme 01, ce qui donne 0.1.... Pour les montants, utilisez toujours/forcez manuellement lv_amt = |{ lv_amt ALPHA = OUT }|.
  • La clé composite issue de l'écriture de JSON n'est pas une chaîne unique — Lorsque vous faites correspondre les identifiants de BAPIRET2 aux lignes de la table interne, la jointure par ROW_ID échoue car ROW_ID est la clé composite épissée parent_key + | + row_id, ce qui contient des sauts de ligne. Le champ ROW_ID dans le JSON est la clé de la table interne. Si vous attendez un format de clé joint, parsez proxy_class->/decoded_responses->transfer. Le côté écriture ne peut pas être joint.
  • La passerelle OData : la sérialisation/JSON n'est pas compatible avec le protocole ODATA natif + le modèle de la passerelle n'est pas cohérent — Le service API_SALES_ORDER_SRV s'attend à ce que to_Item soit un tableau, mais en réalité, les attributs de niveau supérieur sont directement dans le nœud racine et les détails des lignes ne sont pas dans le sous-tableau mais dans une propriété de premier niveau d. Par conséquent, lors de l'écriture de la passerelle, vous devez d'abord lire le modèle de métadonnées, sinon une propriété to_Item ajoutée côté JSON devient une paire clé-valeur au même niveau que d.results, et la passerelle ne la lit pas, ce qui donne l'impression que la création est réussie mais que les articles sont vides.
  • Les structures des paramètres des FM des passerelles standard ne sont pas documentées — Par exemple, SD_SALESDOCUMENT_CHANGE nécessite que les paramètres d'entrée soient dans INPUT, pas dans IT_CHANGED_ITEMS. Regardez SE37 → Paramètres d'importation pour voir les définitions ; n'écrivez pas de EXTENSIONIN à l'aveugle.
  • Les noms des champs de l'interface du fournisseur externe standard et les appels internes sont les mêmes mais les structures sont différentes — Dans le moniteur d'interface, request_body capture le JSON transmis par le fournisseur, get_text capture le XML interne. Le mapping reçoit le XML du fournisseur et le transforme en JSON. Ce JSON correspond aux structures du fournisseur, pas aux structures ABAP. CALL TRANSFORMATION doit être adapté à la structure du fournisseur.
  • Le test de l'interface doit utiliser le service de simulation de l'interface standard, pas directement l'interface réelle — Le service de simulation peut correspondre aux messages d'alerte de la file d'attente, le test en production peut ne pas détecter les alertes, ce qui conduit à des échecs de production.
  • L'interface d'appel doit transmettre les balises de début et de fin JSON — Les interfaces standard peuvent ne pas fournir de balises, le moniteur les ajoute. Si vous appelez directement, l'interface peut interpréter la balise comme une balise de début, et les balises de fin manquantes peuvent être interprétées comme des fermetures de champ.

7. DDIC : Création d'objets

  • createDomain : pas de paramètre pour le nombre de décimales, il est dans la description — Si vous transmettez une chaîne de description contenant (5), ce n'est pas le nombre de décimales ; vous devez le fournir séparément dans l'objet domaine.
  • createDomain : les valeurs du domaine ne peuvent pas être transmises séparément — Vous devez les transmettre sous forme d'objets value avec leurs propres valeurs de description. Si vous transmettez un tableau value_range, la transmission peut échouer. L'ordre des valeurs doit correspondre à l'ordre dans DD07V.
  • createDataElement doit être appelé après createDomain — L'ordre d'appel est important. Si vous les appelez dans le mauvais ordre, la création de l'élément de données peut échouer car le domaine n'existe pas encore.
  • updateDataElement peut échouer si le nom de l'élément de données existe déjà — Vérifiez d'abord via GET ou read, si l'élément de données existe, utilisez UPDATE au lieu de CREATE. L'interface de mise à jour nécessite un LOCK à faire avant. Si l'élément de données n'existe pas, faites d'abord un CREATE.
  • Les champs de type STRING dans les structures ne sont pas pris en charge par la table de base DDIC — TABL ne peut pas contenir STRING, il ne peut être utilisé que dans STRUC. Vérifiez la documentation.
  • L'interface de création de table ADT ne supporte pas les champs de clé primaire composites — Vous devez ajouter les champs de clé un par un via une méthode distincte.
  • Les structures imbriquées (STRU/TTYP) dans les structures ne sont pas supportées par createStructure — Vous devez les créer séparément en tant qu'objets distincts, puis les référencer. Les structures imbriquées sont utiles pour les LOG, mais la structure elle-même ne peut pas les contenir ; vous devez les créer en tant que composants séparés.
  • createTable ne supporte pas la définition des clés d'index — Vous devez définir les index via une commande SQL distincte après la création de la table.
  • get_object de l'interface DDIC nécessite name + type — Si vous ne transmettez pas le type, l'objet peut ne pas être trouvé.
  • Les dépendances des packages doivent être vérifiées avant la création des objets — Si le package parent n'existe pas, la création de l'objet échoue. Créez d'abord le package.

8. Techniques de dépannage (ST22 / Probes)

  • ST22 Short Dump : l'URL ne peut pas être ouverte directement — L'interface ADT lit clikey, pas l'URL. Utilisez GET BADI ou un code de diagnostic.
  • Le compteur de lignes de ST22 n'est pas précis — Le texte de l'erreur montre le numéro de ligne du point d'entrée, pas la ligne réelle de l'erreur. Le vrai numéro de ligne est dans sourcepos, la position de l'erreur peut être décalée en fonction du type de l'erreur.
  • Probe $TMP : le verrou n'est pas fiable — Le verrou de la sonde peut ne pas être supprimé après la fin de la sonde, laissant des objets résiduels. Supprimez manuellement via SM12.
  • Probe : les modifications de code source ne sont pas visibles dans la sonde — La sonde utilise une zone de code distincte, les modifications du code source ne sont pas mises à jour dans la sonde. La sonde doit être re-générée après chaque modification du code source.
  • La sonde doit utiliser SUBMIT ... AND RETURN — Pour exécuter une sonde, utilisez SUBMIT avec AND RETURN pour éviter les problèmes de session.
  • Le point d'arrêt de la sonde n'est pas supprimé automatiquement — Après le débogage de la sonde, supprimez manuellement les points d'arrêt via SM12 ou SE38.
  • Le message d'erreur de ST22 est souvent en anglais — Même dans un environnement ZH, le message d'erreur peut être en anglais, ne vous fiez pas à la traduction.
  • La cause racine de ST22 peut être dans une chaîne d'appels — Le short dump montre le point d'arrêt, mais la cause racine peut être dans une fonction ou un événement précédent. Utilisez l'analyse callstack pour remonter.
  • Les exceptions CX_SY_* ne sont pas toutes documentées — Consultez la documentation de la classe d'exception pour comprendre le contexte.
  • Méthode de détection Proactive : SY-SUBRC = 0 mais le résultat n'est pas comme prévu — Utilisez ASSERT ou CHECK pour valider les résultats intermédiaires.

9. Pièges des scripts de patch

  • Les scripts de patch ne doivent jamais être exécutés en mode interactif — Les scripts doivent être entièrement automatisés, sans interaction humaine. Les scripts interactifs sont sujets aux erreurs et difficiles à reproduire.
  • Le script de patch suppose que l'environnement source est en production — Le script doit inclure la vérification que l'environnement cible est la production.
  • Le script de patch doit inclure une étape de restauration — En cas d'échec du patch, le script doit être capable de restaurer l'état précédent.
  • Le script de patch doit journaliser chaque étape — Une journalisation détaillée est essentielle pour le débogage après coup.
  • Le script de patch ne doit pas transmettre de mots de passe en clair — Utilisez des variables d'environnement ou un gestionnaire de secrets.
  • Le script de patch doit vérifier les dépendances avant l'exécution — Par exemple, le module de fonction, la classe, la table de base doivent exister.
  • Le script de patch doit être idempotent — L'exécution multiple du même script ne doit pas avoir d'effets secondaires.
  • Le script de patch doit gérer les erreurs avec des messages explicites — Ne vous contentez pas de process.exit(1), donnez le nom du champ, le message d'erreur, l'étape.
  • Le script de patch ne doit pas supposer que l'ordre des fichiers est correct — L'ordre des fichiers dans le système de fichiers peut être différent de celui attendu.

10. Recherche rapide par symptôme (Consultation)

  1. « La compilation réussit mais l'activation échoue / L'activation réussit mais l'exécution ne fonctionne pas » → §1. Activation
  2. « Retourne S mais la valeur n'a pas changé » → §3.2 BAPI / Fonctions standard
  3. « Le message d'erreur n'a rien à voir avec la cause réelle » → §0 Règle d'or 1 + §2 Syntaxe
  4. « Le bouton a été cliqué mais ne réagit pas / La popup ne s'affiche pas » → §5 Popup / F4
  5. « Les données sont inexplicablement décalées / perdues » → §3.1 Décalage de données
  6. « Les titres de colonnes ALV sont vides / les messages sont vides » → §3.3 Texte / Description
  7. « La transaction ne peut pas être sauvegardée / le rollback ne fonctionne pas » → §4 Transaction / LUW
  8. « L'interface JSON/OData échoue / le mappage est incorrect » → §6 Interface / JSON
  9. « La création DDIC échoue / la mise à jour échoue » → §7 DDIC
  10. « ST22 short dump / crash inexplicable » → §8 Techniques ST22 / Probes
  11. « Le script de patch échoue / les données sont écrasées » → §9 Scripts de patch
  12. « Avant de démarrer le projet / Checklist de démarrage » → §11 Checklist de démarrage

11. Checklist de démarrage de projet

Phase de préparation :

  1. Confirmer l'adresse de l'environnement cible (développement / test / production)
  2. Confirmer les autorisations (utilisateur / rôle / clé de licence)
  3. Configurer le client / le code de la société / l'exercice comptable
  4. Lire la documentation du projet s'il s'agit d'un projet existant (objets, modifications)
  5. Obtenir le numéro de package de transport et le numéro de demande

Phase d'action :

  1. Créer / lire les objets de base (domaine / élément de données / table / structure)
  2. Écrire le code source ABAP (programme / classe / FM)
  3. Écrire les éléments de texte (sélection / texte / en-tête)
  4. Activer et vérifier (activation réussie / exécution correcte)
  5. Journaliser et archiver (code source / éléments de texte / journal d'activation)

Phase d'auto-vérification du code :

  1. Vérifier les règles de syntaxe ABAP (§2)
  2. Vérifier les échecs silencieux (§3)
  3. Vérifier la logique de transaction/LUW (§4)
  4. Vérifier l'interface utilisateur (popup, F4, ALV) (§5)
  5. Vérifier les interfaces JSON/OData (§6)
  6. Vérifier les créations DDIC (§7)

Phase de livraison :

  1. Pousser le code source (via l'outil de transport)
  2. Pousser les éléments de texte (via l'outil de transport, activation séparée)
  3. Générer l'état GUI (CUA, via l'outil de transport)
  4. Vérifier en production (activation réussie / exécution correcte / données correctes)
  5. Journaliser et archiver (code source / éléments de texte / journal d'activation)

Phase de clôture :

  1. Supprimer les objets de test / sondes (§0 Règle 8)
  2. Confirmer que les autorisations sont correctes
  3. Mettre à jour la documentation (modifications / problèmes / solutions)
  4. Vérifier le journal d'erreurs (ST22, pas d'anomalie)
  5. Confirmer avec l'utilisateur que les données sont correctes

Skills associés