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) :
- 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.
- 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. - 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'exceptionCX_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)
- 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. - Les éléments de texte font partie de la base,
get_sourcene contient pas TEXTPOOL. Avant de pousser du texte de manière écrasante, vous devezread_textelementspour 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. - Ne jamais faire directement UPDATE / MODIFY sur une table standard. Pour modifier une pièce FI déjà comptabilisée, utilisez
FI_DOCUMENT_CHANGE(groupeFACG, ne remplissezT_ACCHGqu'avecFDNAME+NEWVAL, ne transmettez pasI_BUZEIen en-tête, transmettez-le uniquement dans les lignes). - 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. - N'utilisez pas la classe
Update*pour faire des appels exploratoires sur une interface. Elle écrit réellement — nous avons déjà utiliséUpdateFunctionModuleen 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. - Livraison en quatre étapes : Générer → Pousser le code source → Pousser les éléments de texte (
PROG/PXné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-xxxest toujours vide à l'exécution. - 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é. - Supprimez les objets de test/sonde immédiatement après usage. D'abord
get_sourcepour archiver dansbackup/→ supprimer → revérifierTADIRpour 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. - 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.
- 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=activeet inactive.TRDIR-RSTATn'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éfixechkl:+<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 ; sipush_textelementsn'a queGET→LOCK→PUT→UNLOCKsans ACTIVATE, PUT n'écrit que la version inactive. En transmettantPROG/P, SAP considère qu'il n'y a rien à activer et retourneactivationExecuted="false"sans aucun msg ⇒ échec silencieux. L'interface d'activation n'accepte pascorrNr(retourne 400). - Le PUT de
push_textelementsest 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_FILEdoit être donné en entier avec lesselectionsexistants. - 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; ②UpdateDataElementretournelocked by another useralors que c'est un 400 « existe déjà » — leisAlreadyExistsError()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,UpdateDomainn'a pas cette restriction) ; ③403 … not specified for this clientsignifie que le mandant est incorrect. - Le vrai verrou ne peut être supprimé que via SM12 — Les trois types de verrous
ESDICT/ESRDIRE/SEOCLSENQsont attachés à la session ADT.DEQUEUE_ALL,_scope='3',DEQUEUE_EESDICTsont tous inefficaces. Après confirmation parENQUEUE_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
Acceptd'ADT LOCK doit inclure le type de données — Si vous envoyez uniquementapplication/xml, SAP a verrouillé avec succès mais retourne 406 sansLOCK_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()dansfail()du script sautefinally⇒ verrou d'entrée de file d'attente résiduel — Après une sortie anormale d'un script de type push, le verrouSEOCLSENQpeut 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 utilisezprocess.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/programscré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écuterINSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(STATEdoit être avantPROGRAM 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*_proxyet définissezNO_PROXY=*; pour toutes les requêtes d'écriture, utilisezproxy: 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).D020Sest l'en-tête d'écran dynpro, pas la table d'état GUI ;RS_CUA_GET_STATUSne peut pas être utilisé pour la détermination, utilisez uniquementRS_CUA_INTERNAL_FETCH(les 10 TABLES obligatoires doivent toutes être fournies,ORDER BY ppositionpour é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(retourneNumeric literals cannot be used as field names) ⇒ utilisezTYPE 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 compilationincompatible, mais les symboles de texte font exception mais retournent toujours 500Type conflict when calling a FORMà l'exécution ⇒ pour les longueurs variables, utilisez le type génériqueTYPE c; ③ Impossible d'utiliser l'inlineTYPE STANDARD TABLE OF(le paramètre formel est compté comme 8, retourneDifferent number of parameters) ⇒ déclarez d'abord avecTYPES; ④FIELD-SYMBOLdéclaré dans un FORM est local, il doit être global pour être utilisé entre FORM. FORM ... TABLESdoit être écrit avantUSING/CHANGING— Si vous écrivezUSING ... CHANGING ... TABLES, vous obtenezField "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'appelPERFORM.- L'ordre des sections
CALL FUNCTIONest obligatoire —EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. Si vous écrivezTABLESavantIMPORTING, vous obtenezUnable to interpret "EV_OBJNR", sans aucun lien apparent avec l'ordre. Les paramètresEXPORTINGde la fonction appelée doivent être dans la sectionIMPORTINGde l'appelant ; si inversé, vous obtenezCALL_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 — Retourneelements in the "SELECT LIST" list must be separated using commas. C'est la même chose pour les affectations multiples dansUPDATE ... SET; les constantes (y comprisCONSTANTS) dansWHEREnécessitent également@. - La position de
INTOetUP TO n ROWSdépend de l'erreur de l'activateur — Les instructions avec JOIN/sous-requêtes/expressions SQL sont en mode strict, forçantINTOà la fin (SELECT 563) ;UP TO n ROWSdoit être avantWHERE. 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 supprimezLENGTH, en cas de dépassement d'indice, vous obtenez un DUMPInvalid subfield access. La bonne solution utilisecl_abap_container_utilities=>fill_container_c( )(qui est l'inverse de la fonction standardREAD_CONTAINER_C).TYPEdansDESCRIBE FIELDdoit être écrit avantCOMPONENTS— Même pour obtenir uniquement le nombre de composants, vous devez écrire :DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.CONSTANTSdoit avoirVALUE—CONSTANTS: c_x TYPE flag.retourne"VALUE ..." is not specified.MESSAGE TEXT-xxx TYPE 'S' WITH nn'est pas valide — Un symbole de texte ne peut pas être utilisé comme texte de message avecWITH. 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/char220sont tous invalides, retournentType "X" is unknown; utilisez toujoursTYPE c LENGTH n. Deviner le nom du type = gaspiller un cycle d'activation ; si vous n'êtes pas sûr, vérifiez dansDD04Lou viaget_source. class=>methodne peut être utilisé qu'avec des méthodes statiques — Pour une méthode d'instance, vous devez faireCREATE OBJECT lo. lo->method( ), sinon vous obtenezcan only be specified with static methods.- Un template de chaîne ne peut pas être utilisé comme paramètre réel de
PERFORM ... USING— RetourneField "|" is unknown; assignez-le d'abord à une variable, puis transmettez-la. CALL TRANSACTION ... WITH f1 v1n'est pas une syntaxe valide —WITHn'accepte queAUTHORITY-CHECK. Le transfert de paramètres ne peut se faire que viaUSING bdcdata; le premier écran de SM30 n'a qu'un seul champ pour le nom de la table (VIMDYNFLDSn'a pas de champ de mode de mise à jour) ; pour entrer en mode modification, utilisezVIEW_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 boucleDO strlen TIMES+ filtrelv_txt+lv_i(1).CONCATENATEn'accepte pas les types numériques —lv_cnt TYPE iretourne must be character-like ; les fonctions intégrées (strlen/lines) ne peuvent pas être utilisées directement avec&&(retourneThe 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 retourneStatement is not accessible(CheckProgramne le signale pas) ; les commentaires entreENDMETHODet leMETHODsuivant dans une classe retournentunknown 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éeselectionsdans 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
SELECTavec une liste de colonnes expliciteINTO 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 :DOMVALdeDD07Ttombe dansDOMNAME,WKGBTRdeCOEPtombe dansMEGBTR(le montant est toujours vide),ZTEPIC3Asignalecomponent "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 ; ouINTO CORRESPONDING FIELDS OF TABLE.- Un champ de la table de droite d'un
LEFT JOINutilisé dansWHERE⇒ la sémantique dégénère enINNER 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 leON. DELETE ADJACENT DUPLICATES ... COMPARING <champ non-clé>peut supprimer des données — La clé primaire de la table condition A017 contientDATBI,KNUMHn'est pas une clé. La déduplication avecCOMPARING knumhsupprime toutes les périodes de validité suivantes pour le même KNUMH. Faites d'abord unSORT BYsur toutes les clés, puis unCOMPARINGsur toutes les clés.AT NEW/AT END OFse base sur l'adjacence physique ; après une réécriture ALV, elle n'est plus valide —REUSE_ALV_*réorganiset_outtabsur place (déclenché par un tri de l'utilisateur sur une colonne de message), les mêmes groupes ne sont plus adjacents,AT END OFest 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.APPENDmodifiesy-tabix— AprèsAPPEND INITIAL LINE ... ASSIGNING <fs>,sy-tabixdonne 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'abordlv_tabix = sy-tabix..- Si
READ TABLE ... WITH KEYne trouve pas de correspondance,sy-tabixest mis à 0 — Ensuite,APPEND sy-tabix TO lt_delenregistre 0 dans la liste de suppression, etDELETE ... INDEX 0provoque directement un DUMPTABLE_INVALID_INDEX. En début de boucle, stockez le numéro de ligne + avant la suppression, faitesIF idx >= 1 AND idx <= lines( ). - Une déclaration inline
DATA(...)dans une brancheIFne crée jamais la table interne si la condition n'est pas satisfaite ⇒LOOPsuivant provoque un short dump. Déplacez la déclaration en tête de méthode, ou faites une vérificationIS INITIALavant 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
xd'une BAPI doit être transmise par paire avec la structure d'origine — Si vous transmettezdepreciationareassansdepreciationareasx⇒ quand vous remplissez AFASL, vous obtenezNo 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_CHANGEutiliseGET_CHANGEABLE_FIELDS, qui ne parcourt que les champs oùWHERE rollname = 'CHAR1' OR 'CHAR01'; ceux avecBAPIUPDATEsont 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-VALUEPART1n'a que 240 caractères — Les fonctions standard lisent séquentiellementVALUEPART1~4par décalage (total 960). Si vous ne remplissez que la première partie, la fin est tronquée silencieusement (BAPE_VBAPmesuré à 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—BAPISDITMn'a aucun champ de référence versKWMENG; la catégorie d'article de ce type n'a pas de configuration dansTVEPZ⇒ aucune ligne de planification n'est générée, leBAPISCHDLémis est silencieusement ignoré, retour S maisKWMENG=0,NETWR=0,VBEP0 ligne. Le critère est la présence d'une entrée dansTVEPZ, pasTVAP-LFREL(LFRELdeZ001etZ014sont tous deux vides), niVBTYP. Même piège côté lecture :VBAP-KWMENGest toujours 0, la quantité est dansVBAP-ZMENG⇒ la formule de réaffectation est « priorité àKWMENGnon vide, sinonZMENG». - 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 unmove-correspondingde la valeurEXTENSIONINdansE_VBAKKOM. SiVBELNest vide, il écrase le numéro de document avec une valeur vide ⇒V1 045suivi deV4 219(la cause racine est le message E précédantV4 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 retourneV2 068 sans catégorie de document mais avec C(&2vide). Si le numéro de ligne est 0 ou si le document n'existe pas, c'est le même message mais&3est vide. La catégorie est leVBAK-VBTYPdu document de référence lui-même, pas leVGTYPd'en-tête. - L'entrée de
SD_SALESDOCUMENT_CHANGEtrie deux tableaux séparément — Pour supprimer une ligne,ITEM_INetITEM_INXdoivent tous deux avoirITM_NUMBER; ne remplir queITEM_INXprovoque un décalage et retourne une erreur totalement non pertinenteT184 pas de catégorie d'article disponible. BAPI_USER_EXISTENCE_CHECKretourne 0 pour n'importe quel compte dans ce système (mêmeZZZZZZ999est accepté) ⇒ la vérification est inefficace, un document transféré à un compte invalide est invisible dans les deux interfaces. Vérifiez directementUSR02+ protection de longueur (0 < len <= 12).DELETE_TEXTsur un texte inexistant lèveRAISING NOT_FOUND— La fonction standard ajoute ce message E àBAPIRET2, qui est considéré comme un échec parfrm_check_return⇒ une opération « vider » transforme l'interface en E. Avant de vider, vérifiez d'abordSTXH(TDOBJECT+TDNAME+TDID+TDSPRAS), si inexistant, sautez (ce qui rend l'opération idempotente). Aussi : dansSD_SALES_DOCU_MAINTAIN, le code fonction pour vider est003(DELETE_TEXT) ; transmettre&&/005ne 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 queZP01est à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 piloterUPDATEFLAG(I/U). - Lors de la deuxième décomposition d'une interface,
OBJ_KEYne 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 deOBJ_KEYestnumé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-BUZEIestNUMC(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 de989 détails + 1 différence; la limite doit couvrir 999/1000/1997 (1997 serait divisé à>999donnant 1000 lignes, ce qui dépasse).BAPIACHE09n'a pas de champEXCH_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 ligneCURR_TYPE='10'doit aussi avoirexch_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_ACCdoit avoir deux entréesCURRENCYAMOUNT, et le sous-total de chaque devise doit être 0 — S'il manque une ligne10, retourneInconsistent currency information; la ligne de différence générée par la décomposition doit également être complétée. UMSKZn'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'enhancementEXTENSION2, ou en passant parPOSTING_INTERFACE_*(saisie par lot FB01).- Le paramètre
NAMEdeREAD_TEXTestTDOBNAME(70), le paramètre réelTDNAME(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 500Type conflict during a function module callse produit. Déclarez le paramètre avecTYPE tdobname, ou lisez directement la table clusterSTXL.
3.3 Texte / description / titre de colonne vide
- Champ avec un type anonyme intégré ⇒
DD03Ta un texte vide ⇒ les titres de colonnes ALV sont blancs — Le champ doit être lié à un élément de données (une fois lié,DD03Ta 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.labelpar champ ; les champs de typeSTRU/TTYPimbriqués n'ont jamais de texteDD03T(c'est également le cas pour les structures standard SAP).CreateStructureavecfields[].descriptionn'entre pas dansDD03T. - Éléments de texte non activés / seulement version inactive ⇒
TEXT-xxxtoujours 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
DD07Tdoit inclureAS4LOCAL='A'(sinon, elle prend les lignes inactives),ORDER BY VALPOS(sinon, elle prend l'ordre lexicographique1,10,11,2,3…), et la langue par défaut est la langue maîtresseDD01L-DOMMASTER(le nom du champ n'est pasMASTERLANG). MESSAGE ... WITHa une limite d'environ 70 caractères pour le texte entier ⇒ Les longues erreurs BAPI sont silencieusement tronquées enField 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é surBAPI_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-CHECKavec une valeur vide signifie généralement « pas d'autorisation » ⇒ les enregistrements concernés sont définitivement invisibles — Les rôles n'attribuent pasEKGRP = 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 IDne 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,ROLLBACKconsomme également l'instantané des paramètres d'entréeindx=01(mais pasindx=02). De plus, le FM de comptabilisation sous-jacent peut faireBAPI_TRANSACTION_ROLLBACKen 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 unCOMMIT WORKindépendant, leROLLBACKconserve les données. Avantage supplémentaire : faire leCOMMIT WORKde 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_CHANGEavecCALL_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 ⇒ supprimerCOMMIT WORK AND WAITpuis garder une vérification de relectureSELECTconduit nécessairement à un faux diagnostic « indicateur non mis à jour ». Dans la branche d'échec de la comptabilisation, vous devez ajouterROLLBACK 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_respn'est pas visible sur la plateforme ; le moniteurserializesansname_mappingssort 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 lignesave( )écrite au seul endroit où le message est généré (à la fin defrm_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
RETURNCODEdePOPUP_GET_VALUESest contre-intuitive —'A'= Annuler ; espace = Confirmer etFIELDSa été renseigné (LSPO4U01:IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). Si vous écrivezIF 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_VALUESn'affiche F4 que lorsque le champ peut être lié à une aide à la recherche DDIC — Le critère estF4IF_DETERMINE_SEARCHHELP(0=existe / 2=le champ existe mais pas d'aide / 1=le champ n'existe pas). Ne pas essayer d'utiliserSKA1-SAKNRpour 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ôtPOPUP_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ètresUSING(tabname/fieldname/display_flag/returncode/value), pour écrire la valeur sélectionnée dans le 5ème paramètre et mettrereturncodeà'F'pour que la popup se rafraîchisse (ne peut pas obtenirsy-stepl, ne passez pas par dynprofield pour la réaffectation).POPUP_GET_VALUESne supporte pas le masquage conditionnel de champs —SVALne peut contrôler queFIELD_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_REQUESTavecFIELD_TABdoit pouvoir déduireINTTYPEpour chaque ligne — La chaîne de déduction estTABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME. Les appels deSCVdirectement avecFIELD_TABne 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 standardfieldnamepasse parENQUEUE/DEQUEUEpour le champ de saisie,F4IFpeut fournirhelpprocessor, utilisez d'abord cette voie.
6. Interface / JSON / Passerelle (OData / RFC)
CALL TRANSFORMATIONvsCL_THE_XML_DOCUMENT: le premier ne supporte pas la dé-sérialisation des nombres en type C — XML<AMOUNT>100.50</AMOUNT>correspondant au champCHAR20(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 êtreamt TYPE string. Le problème inverse n'existe pas,STRINGpeut recevoir n'importe quoi./UI2/CL_JSON=>DESERIALIZEne convertit pas automatiquement les chaînes en nombres — Le JSON{ "WRKST": "1" }correspond au champWRKST TYPE p LENGTH 3 DECIMALS 1→ le nombre est mappé à la chaîne1dans le champ P, ce qui donne1(correct). Mais les nombres décimaux saisis comme01.50sont lus comme01, ce qui donne0.1.... Pour les montants, utilisez toujours/forcez manuellementlv_amt = |{ lv_amt ALPHA = OUT }|.- La clé composite issue de l'écriture de
JSONn'est pas une chaîne unique — Lorsque vous faites correspondre les identifiants deBAPIRET2aux lignes de la table interne, la jointure parROW_IDéchoue carROW_IDest la clé composite épisséeparent_key +|+ row_id, ce qui contient des sauts de ligne. Le champROW_IDdans le JSON est la clé de la table interne. Si vous attendez un format de clé joint, parsezproxy_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
ODATAnatif + le modèle de la passerelle n'est pas cohérent — Le serviceAPI_SALES_ORDER_SRVs'attend à ce queto_Itemsoit 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 niveaud. 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_Itemajoutée côté JSON devient une paire clé-valeur au même niveau qued.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_CHANGEnécessite que les paramètres d'entrée soient dansINPUT, pas dansIT_CHANGED_ITEMS. RegardezSE37→Paramètres d'importationpour voir les définitions ; n'écrivez pas deEXTENSIONINà 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_bodycapture le JSON transmis par le fournisseur,get_textcapture 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 TRANSFORMATIONdoit ê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'objetsvalueavec leurs propres valeurs de description. Si vous transmettez un tableauvalue_range, la transmission peut échouer. L'ordre des valeurs doit correspondre à l'ordre dansDD07V.createDataElementdoit être appelé aprèscreateDomain— 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.updateDataElementpeut échouer si le nom de l'élément de données existe déjà — Vérifiez d'abord viaGETouread, si l'élément de données existe, utilisezUPDATEau lieu deCREATE. L'interface de mise à jour nécessite unLOCKà faire avant. Si l'élément de données n'existe pas, faites d'abord unCREATE.- Les champs de type
STRINGdans les structures ne sont pas pris en charge par la table de base DDIC —TABLne peut pas contenirSTRING, il ne peut être utilisé que dansSTRUC. 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 parcreateStructure— 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 lesLOG, mais la structure elle-même ne peut pas les contenir ; vous devez les créer en tant que composants séparés. createTablene 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_objectde l'interface DDIC nécessitename+type— Si vous ne transmettez pas letype, 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. UtilisezGET BADIou 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 viaSM12. - 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, utilisezSUBMITavecAND RETURNpour é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
SM12ouSE38. - 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
callstackpour 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 = 0mais le résultat n'est pas comme prévu — UtilisezASSERTouCHECKpour 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)
- « La compilation réussit mais l'activation échoue / L'activation réussit mais l'exécution ne fonctionne pas » → §1. Activation
- « Retourne S mais la valeur n'a pas changé » → §3.2 BAPI / Fonctions standard
- « Le message d'erreur n'a rien à voir avec la cause réelle » → §0 Règle d'or 1 + §2 Syntaxe
- « Le bouton a été cliqué mais ne réagit pas / La popup ne s'affiche pas » → §5 Popup / F4
- « Les données sont inexplicablement décalées / perdues » → §3.1 Décalage de données
- « Les titres de colonnes ALV sont vides / les messages sont vides » → §3.3 Texte / Description
- « La transaction ne peut pas être sauvegardée / le rollback ne fonctionne pas » → §4 Transaction / LUW
- « L'interface JSON/OData échoue / le mappage est incorrect » → §6 Interface / JSON
- « La création DDIC échoue / la mise à jour échoue » → §7 DDIC
- « ST22 short dump / crash inexplicable » → §8 Techniques ST22 / Probes
- « Le script de patch échoue / les données sont écrasées » → §9 Scripts de patch
- « 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 :
- Confirmer l'adresse de l'environnement cible (développement / test / production)
- Confirmer les autorisations (utilisateur / rôle / clé de licence)
- Configurer le client / le code de la société / l'exercice comptable
- Lire la documentation du projet s'il s'agit d'un projet existant (objets, modifications)
- Obtenir le numéro de package de transport et le numéro de demande
Phase d'action :
- Créer / lire les objets de base (domaine / élément de données / table / structure)
- Écrire le code source ABAP (programme / classe / FM)
- Écrire les éléments de texte (sélection / texte / en-tête)
- Activer et vérifier (activation réussie / exécution correcte)
- Journaliser et archiver (code source / éléments de texte / journal d'activation)
Phase d'auto-vérification du code :
- Vérifier les règles de syntaxe ABAP (§2)
- Vérifier les échecs silencieux (§3)
- Vérifier la logique de transaction/LUW (§4)
- Vérifier l'interface utilisateur (popup, F4, ALV) (§5)
- Vérifier les interfaces JSON/OData (§6)
- Vérifier les créations DDIC (§7)
Phase de livraison :
- Pousser le code source (via l'outil de transport)
- Pousser les éléments de texte (via l'outil de transport, activation séparée)
- Générer l'état GUI (CUA, via l'outil de transport)
- Vérifier en production (activation réussie / exécution correcte / données correctes)
- Journaliser et archiver (code source / éléments de texte / journal d'activation)
Phase de clôture :
- Supprimer les objets de test / sondes (§0 Règle 8)
- Confirmer que les autorisations sont correctes
- Mettre à jour la documentation (modifications / problèmes / solutions)
- Vérifier le journal d'erreurs (ST22, pas d'anomalie)
- Confirmer avec l'utilisateur que les données sont correctes