ABAP 開発 落とし穴と解決法ライブラリ
用途: 次のプロジェクト開始前に一読し、トラブルシューティング時は症状から検索してください。このスキルには、プロジェクト横断で再利用可能な経験のみを収録しています。環境アドレス、リクエスト番号、オブジェクト一覧はプロジェクト側の総綱(例: sap-abap-project-ops)を参照してください。
完全な120件(コードスニペットと出典を含む)は references/casebook.md を参照してください。
使い方: 新規プロジェクト開始 → 直接 §11 開始チェックリスト(時系列順に実行)を確認する。既にプロジェクトが進行中で問題が発生した場合 → §10 症状クイックリファレンス で該当するセクションを特定する。特定の事例の詳細なコードと出典を深掘りしたい場合 → references/casebook.md を参照する。
3つのメタルール(個々の落とし穴よりも重要):
- エラーメッセージは実際の原因と無関係であることが多い — ABAPコンパイラのエラーポイントは「解析が止まった位置」であり、「実際に誤りがある位置」ではありません。まずは文の順序/キーワードの順序/型の不一致を疑い、文法そのものを疑わないでください。
- SAPで最も危険なのはエラーではなく、静かなる失敗である — 「戻り値 S、アクティベーション 200、コンパイル成功」という三重の偽装の下で、データが静かに壊れます。BAPI、
SELECT INTO TABLE、EXTENSIONIN、テキスト要素、拡張フィールドに関わる処理は、必ずデータベースへの書き込みを照合し、戻り値を信用しないでください。 - 失敗したら必ず ST22 を確認し、推測しない — 短絡ダンプの
Error analysisは、procedure "XXX" "(FORM)"とCX_SY_*例外クラスを直接示します。一つずつ FORM を試すより桁違いに高速です。
0. 10の鉄則(違反したらやり直し)
- 既存のオブジェクトを変更する場合、最初のステップは現行システムからソースを読み戻すこと。ユーザーはしばしば SE80 で本番オブジェクトを直接変更し、アーカイブに書き戻しません。ローカルアーカイブは比較のためだけに使い、ベースラインとしては使用しません。
get_source→ バックアップと差分を確認し、差異がゼロであることを確認 → パッチ適用 → 再読み込みして diff → アーカイブを逆方向に上書きします。 - テキスト要素もベースラインに含まれますが、
get_sourceは TEXTPOOL を含みません。上書きプッシュの前にread_textelementsでアクティブ版の全文をアーカイブし、ローカルの JSON と diff を確認し、手動でマージしてからプッシュする必要があります。そうしないと、ユーザーが SE38 のエディタで変更した文言が痕跡なく上書きされ、ソースコードの diff では全く検出できません。 - 標準テーブルを直接 UPDATE / MODIFY してはいけません。転記済みの FI 伝票を変更する場合は
FI_DOCUMENT_CHANGE(グループFACG、T_ACCHGにはFDNAME+NEWVALのみを設定、ヘッダにはI_BUZEIを渡さず、明細行のみに渡す)を使用します。 - リクエスト番号は自分で作成しない。トランスポート可能なオブジェクトをプッシュする際に
CORR_NRがない場合 → 処理を停止して報告し、ユーザーが番号を提供するのを待ちます。新しい TADIR オブジェクトを作成する前に、まず「タイプ+推奨名+TADIR 重複チェック結果+番号継続の理由」を報告し、承認を得てから作成します。既にリクエストにアタッチされている既存オブジェクトの後続の変更は、元の番号を再利用し、再度問い合わせる必要はありません。 Update*クラスをインターフェースのテスト呼び出しに使用しない。実際に書き込みが行われます — かつてUpdateFunctionModuleにファンクション本体だけを渡して「試しに」呼び出したところ、inactive なソースコードが 620 文字の不完全なバージョンで上書きされました。プローブは読み取り専用のインターフェースのみを使用します。- 納品の4ステップ: 生成 → ソースコードのプッシュ → テキスト要素のプッシュ(
PROG/PXは個別にアクティベートが必要) → GUI ステータスの生成(CUA)。テキスト要素がアクティベートされていないと、実行時にTEXT-xxxは常に空になります。 - 本番ソースコードのコメント: 骨格は残し、説明は削除。プログラムヘッダ(単位数/作成者/日付/概要/変更履歴)+
*&---枠線 + FORM バナー +-->/<--引数ドキュメント + 判断基準を示す短いコメントは残します。設計根拠、口径説明、結合テスト結果、TODO 理由、scripts/work/ADT/ZTEST_*の参照は削除します。削除後は、コード本体に全く変更がないことを diff で証明する必要があります。 - テスト/プローブオブジェクトはテスト完了後すぐに削除する。まず
get_sourceでbackup/にアーカイブ → 削除 →TADIRがクリアされたことを確認する。自分が今回作成したものだけを削除します。他者が作成したもの、既にパッケージやリクエストにアタッチされているもの、使用中の DEMO は一切触らないでください。 - SAP に書き込む前に、まずロックを確認し、バックアップを取る。資格情報ファイルは外部に送信せず、表示せず、納品物に書き込みません。
- ユーザーが「データに問題ない」と言ったときこそ、プログラムに問題があると考える。「エラー金額+伝票番号」から入力特性を逆推し、プローブを作成してそのエラーを一字一句再現します。再現に成功すれば、根本原因が特定されます。
1. アクティベーションと書き込みチャネル(ADT / MCP)
- HTTP 200 ≠ アクティベーション成功 — 成否はボディに書かれています:
activationExecuted="false"を明示的にチェックし、エラーをスローする必要があります。アクティベーションを検証する唯一の信頼できる方法は、include を読み戻すか、?version=activeと inactive の両方のバージョンを比較することです。TRDIR-RSTATは S/4 では値が書き込まれません。 - 偽の成功が最も発見しにくい — 古いスクリプトは
<chklst:shortText>/<msg>text</msg>を正規表現でメッセージをキャプチャしていましたが、実際の構造はchkl:プレフィックス +<shortText><txt>+ 属性付きの msg です。そのため、一つのメッセージも出力できず、3回「アクティベーション成功」と報告しても、実際は131文字の空のシェルでした。メッセージをキャプチャできない場合は、失敗として扱わなければなりません。 - テキスト要素は独立したアクティベート可能オブジェクト
PROG/PXである — 親プログラムのアクティベーションは连带しません。push_textelementsがGET→LOCK→PUT→UNLOCKだけでACTIVATEを含まない場合、PUT は inactive バージョンにのみ書き込みます。PROG/Pに対して送信すると、SAP はアクティベートする内容がないと判断し、activationExecuted="false"を返し、メッセージは一切ありません ⇒ 静かに無効になります。アクティベーションインターフェースはcorrNrを受け付けません(指定すると 400 エラー)。 push_textelementsの PUT は全体を上書きするため、プッシュの順序を間違えてはいけない — 新しいシンボルの追加: テキスト要素を先に、ソースコードを後に。シンボルの削除: ソースコードを先に、テキスト要素を後に。TE_FILEは既存の selections もすべて含めて完全なものを渡さなければなりません。- テキスト要素が inactive バージョンのみの場合、プログラムを再生成する必要がある — テキスト要素だけをプッシュし、その後同じ内容のソースコードを再度 PUT してもリフレッシュされません。ソースコードの一行を変更してから再プッシュするか、プログラムを再作成します。
- 3種類の「偽のロック」: ① 「ユーザー xxx のリクエスト yyy でロックされています」というエラーは、実際には
transport_request/corrNrの指定漏れです。②UpdateDataElementでlocked by another userというエラーは、実際には 400「既に存在する」です — handler のisAlreadyExistsError()は英語のみを認識するため、中国語メッセージは判別できず、403 でフォールバックして「ロックされている」と翻訳されます(⇒ ZH 環境では MCP は既存のデータ要素を変更できません。UpdateDomainにはこの前提条件がないため影響を受けません)。③403 … not specified for this clientはクライアントが間違っています。 - 本当のロックは SM12 でしか削除できない —
ESDICT/ESRDIRE/SEOCLSENQの3種類のロックは ADT セッションにぶら下がっており、DEQUEUE_ALL、_scope='3'、DEQUEUE_EESDICTはすべて無効です。ENQUEUE_READで確認後、ユーザーに SM12 で手動削除を依頼し、すぐに再実行してください。セッションのタイムアウトを待ってはいけません。 - 403 の2つのメッセージは全く意味が異なる —
User XXX is currently editing ZFIDxxx= 実際のユーザーが SE80 を開いています。待ちます。Object ... locked in task DEVK90011x not specified for this client= クライアントが間違っています。リクエストが属するクライアントに変更して再プッシュします。混同すると無駄な工数が発生します。 - ADT LOCK の
Acceptヘッダーには datatype を含める必要がある —application/xmlのみを送信すると、SAP はロックを正常に取得しますが、レスポンス 406 を返してLOCK_HANDLEを取得できません ⇒ ロックリークが発生し、後続のすべてのリクエストが「ユーザー X が現在編集中です」と報告され、他の誰かが占有しているのだと誤解し続けます。正しい値:application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result。 - スクリプトの
fail()内のprocess.exit()はfinallyをスキップする ⇒ エンキューされたロックが残る — プッシュスクリプトが異常終了すると、SEOCLSENQロックが最大40分間残り、後続のすべての LOCK リクエストが「ユーザーが SE80 を開いている」かのような 403 エラーを報告します。スクリプトを作成する際は、常に最初に UNLOCK してから終了するか、process.exitCode = 1に変更します。 - ユーザーに代わって編集ロックを強制削除しない — ADT でのロック削除は、相手の未保存の変更を失わせます。
- include は独立した ADT リソースです — POST を
/programs/programsに送信すると、常に実行可能プログラム(TRDIR-SUBC=1)が作成され、後から変更できません。include の場合は/sap/bc/adt/programs/includesに送信します(ルート要素は<include:abapInclude>)。メインプログラムと include は同じアクティベーションリクエスト内になければなりません。そうしないと、互いに未定義と報告されます。 INSERT REPORT ... STATE 'A'で作成された include は、ソースコードをアクティブバージョンとして書き込む — その後 inactive バージョンを変更しても、メインプログラムのアクティベーションは古いアクティブバージョンでコンパイルされ、エラーは古いコードを指します。変更後はINSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(STATEはPROGRAM TYPEの前に指定する必要があります)でアクティブバージョンをリフレッシュする必要があります。- ローカルプロキシは ADT リクエストをハングさせる — 症状は「SAP に接続できない」と全く同じです(
socket hang up/ CSRF 失敗)。MCP 子プロセスを spawn する前に*_proxyを削除し、NO_PROXY=*を設定します。書き込みリクエストは常にproxy: falseを指定します。 - ADT には dynpro / GUI Status / Menu リソースがない — ただし、CUA はコード化できます(
RS_CUA_INTERNAL_FETCH+_WRITE、state='A'、書き戻せばすぐに有効)。D020Sは dynpro 画面ヘッダーであり、GUI ステータステーブルではありません。RS_CUA_GET_STATUSは判定に使用できません。RS_CUA_INTERNAL_FETCHのみを使用します(10個の必須 TABLES をすべて指定し、ORDER BY ppositionで25行の切り詰めを防ぎます)。
2. 文法のハード制約(一度でコンパイルを通すために)
- FORM 仮引数の4つの禁忌: ①
TYPE c LENGTH nと書けない(Numeric literals cannot be used as field namesエラー)⇒TYPE char<n>または DDIC 型を使用。② 固定長の仮引数に異なる長さの実引数を受け渡すと、コンパイル時に incompatible エラー。テキストシンボルは例外だが、実行時には依然として 500Type conflict when calling a FORMエラー ⇒ 異なる長さを扱う場合は汎用型TYPE cを使用。③ インラインTYPE STANDARD TABLE OFは不可(仮引数が8つとカウントされ、Different number of parametersエラー)⇒ 事前にTYPESで名前付け。④FIELD-SYMBOLの FORM 内での宣言はローカルであり、FORM をまたぐ場合はグローバルに変更する必要があります。 FORM ... TABLESはUSING/CHANGINGの前に記述しなければならない —USING ... CHANGING ... TABLESと書くと、Field "TABLES" is unknown.エラー(キーワードを仮引数名として解釈)。PERFORM呼び出し側も同様です。CALL FUNCTIONの引数の順序は強制される —EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS。TABLESをIMPORTINGの前に書くと、Unable to interpret "EV_OBJNR"エラーが発生し、順序の問題とは全く見えません。呼び出される側のEXPORTING仮引数は、呼び出し側のIMPORTINGセクションに記述します。逆に書くと、CALL_FUNCTION_PARM_UNKNOWNエラー — 「FM インターフェースが生成されていない」と誤診しやすい。- 複数フィールドの SELECT はカンマで区切り、
@ホスト変数はエスケープする必要がある —elements in the "SELECT LIST" list must be separated using commasエラー。UPDATE ... SETの複数代入も同様。WHERE内の定数(CONSTANTSを含む)も同様に@が必要です。 INTOとUP TO n ROWSの位置は、アクティベータのエラーに従う — JOIN/サブクエリ/SQL 式を含むステートメントは厳密モードであり、INTOは最後に強制されます(SELECT 563)。UP TO n ROWSはWHEREの前に置く必要があります。2つの書き方はステートメントによって動作が異なり、丸暗記は危険です。ASSIGN ... CASTING TYPE c LENGTH <変数>はサポートされていない — 長さはリテラルでなければなりません。LENGTHを削除すると、インデックス範囲外でInvalid subfield accessダンプが発生します。正解はcl_abap_container_utilities=>fill_container_c( )を使用します(標準関数READ_CONTAINER_Cの逆関数)。DESCRIBE FIELDのTYPEはCOMPONENTSの前に記述する必要がある — コンポーネント数だけを取得したい場合でも記述が必要です:DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.CONSTANTSにはVALUEが必要 —CONSTANTS: c_x TYPE flag.は"VALUE ..." is not specified.エラー。MESSAGE TEXT-xxx TYPE 'S' WITH nは不正 — テキストシンボルはWITH付きのメッセージテキストとして使用できません。補助 FORM を使用します:lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.- 固定長型には
char<n>という名前はない —TYPE char1000/char55/char220はすべて不正で、Type "X" is unknownエラー。常にTYPE c LENGTH nを使用します。型名を間違えると、アクティベーションを1回無駄にします。不明な場合はDD04Lまたはget_sourceで確認します。 class=>methodは静的メソッドにのみ使用できる — インスタンスメソッドの場合はCREATE OBJECT lo. lo->method( )を使用する必要があります。そうしないと、can only be specified with static methodsエラー。- 文字列テンプレートは
PERFORM ... USINGの実引数として使用できない —Field "|" is unknownエラー。最初に変数に代入してから渡します。 CALL TRANSACTION ... WITH f1 v1は正しい文法ではない —WITHはAUTHORITY-CHECKにのみ続きます。パラメータの受け渡しはUSING bdcdataを使用する必要があります。SM30 の最初の画面にはテーブル名という1つのフィールドしかありません(VIMDYNFLDSには更新モードフィールドはありません)。修正モードに入るにはVIEW_MAINTENANCE_CALL+action = 'U'を使用します。REPLACE ALL OCCURRENCES OF ... WITH ''はこの型システムでは無限ループになる — スペースや文字を削除する場合は、DO strlen TIMES+lv_txt+lv_i(1)で1文字ずつフィルタリングします。CONCATENATEは数値型を受け付けない —lv_cnt TYPE iは must be character-like エラー。組み込み関数(strlen/lines)は直接&&で連結できません(The syntax for a method specification is objref->methodエラーが一度に大量に発生し、アロー構文の誤りと誤解されます)。数値は常に文字列テンプレート|...{ lv_cnt }...|を使用します。$TMPプローブ内では FORM を定義できない。メソッド本体外のコメントは保存されない — プローブのアクティベーションでStatement is not accessibleエラー(CheckProgramではエラーにならない)。クラス内のENDMETHODと次のMETHODの間のコメントはunknown comments which can't be storedエラー。SELECT-OPTIONS名は8文字以内 — 9文字だと最初のアクティベーションが拒否されます。短い名前に変更する場合は、テキスト要素のselectionsエントリ名も同時に変更する必要があります(古いバリアントは古い名前で格納されており、静かに無視され、エラーは表示されずに警告のみが表示されます)。
3. 静かなる失敗リスト(最も貴重なセクション)
「戻り値 S / アクティベーション 200 / コンパイル成功」にもかかわらず結果が正しくない場合は、まずここを確認してください。
3.1 データのずれと欠落
SELECT 明示的な列リスト INTO TABLE <事前定義されたテーブル全体型>は位置で値を割り当てる — 型がたまたま互換性がある場合、エラーも警告もなし:DD07TのDOMVALがDOMNAMEに格納される、COEPのWKGBTRがMEGBTRに格納される(金額は常に空)、ZTEPIC3Aはcomponent "ZJK" not compatible with "ZJYRQ"エラー(_act_dbgで位置を特定)。同じ落とし穴が5つの報告書で再発しています。 解決策は3つ:SELECT */INTO TABLE @DATA(...)インライン(構造はリストによって定義される)/ 列の順序が一致する狭い構造を自分で作成する。またはINTO CORRESPONDING FIELDS OF TABLEを使用する。LEFT JOINの右テーブルのフィールドをWHEREに記述すると、意味的にINNER JOINに退化する — 右テーブルに一致するものがない場合は NULL になり、条件は成立しません。フィルタ条件はONに移動します。DELETE ADJACENT DUPLICATES ... COMPARING <非キーフィールド>はデータを飲み込む — 条件テーブル A017 の主キーはDATBIを含み、KNUMHはキーではありません。COMPARING knumhで重複を削除すると、同じ KNUMH の後続の有効期間セグメントがすべて削除されます。最初にすべてのキーでSORT BYしてから、すべてのキーでCOMPARINGします。AT NEW/AT END OFは物理的な隣接性に基づいてグループを判定するため、ALV 書き戻し後は必ず無効になる —REUSE_ALV_*はt_outtabをその場で並べ替えます(ユーザーがメッセージ列で並べ替えるとトリガーされます)。同じグループは隣接しなくなり、AT END OFが複数回トリガーされます。グループ化の方法を変更する: 外側のループで重複排除キーを繰り返し、内側のループでキーごとに収集し、グループ境界はビジネスキーのみを認識します。APPENDはsy-tabixを書き換える —APPEND INITIAL LINE ... ASSIGNING <fs>の後にsy-tabixを取得すると、セグメント番号が取得され、元の行番号ではありません ⇒ 間隔が拡大され、128行が558行(同じレコードが43回繰り返される)として出力されます。ループの最初の行で、lv_tabix = sy-tabix.とします。READ TABLE ... WITH KEYがヒットしないとsy-tabixが0になる — その後APPEND sy-tabix TO lt_delで削除リストに追加され、DELETE ... INDEX 0で直接TABLE_INVALID_INDEXダンプが発生します。ループの最初の行で行番号を保存し、削除前にIF idx >= 1 AND idx <= lines( )を確認します。- インライン
DATA(...)宣言がIF分岐内にある場合、条件が成立しないと内部テーブルは作成されない ⇒ 後続のLOOPで短絡ダンプが発生します。宣言をメソッドの先頭に移動するか、データ取得前に空のチェックを追加します。
3.2 BAPI / 標準関数「戻り値 S またはエラーなしだが書き込まれない」
- BAPI の
x構造は元の構造とペアで渡す必要がある —depreciationareasのみを渡してdepreciationareasxを省略すると、AFASL が入力された場合にNo change parameter given for depreciation area 01エラーが発生します。x構造がなくても文法エラーにはならず、データ条件が満たされた場合にのみトリガーされます。 - SD 拡張フィールドを識別する構造のデータ要素は
CHAR1でなければならない —SD_SALESDOCUMENT_CHANGEはGET_CHANGEABLE_FIELDSを使用し、WHERE rollname = 'CHAR1' OR 'CHAR01'のフィールドのみをロールします。BAPIUPDATEを使用するものはすべて除外されます ⇒ 戻り値 S、値は変わらず、エラーゼロ。一方、作成パスはこのリストを使用しないため、典型的な「作成は書き込めるが、変更は書き込めない」という状態になります。BAPIUPDATEに戻すことはできません。 BAPIPAREX-VALUEPART1は240文字のみ — 標準関数はオフセットに基づいてVALUEPART1~4を連続して読み取ります(合計960)。最初のセグメントのみを入力すると、末尾が静かに切り詰められます(BAPE_VBAPで実際に271文字、後続の3フィールドはすべて空、エラーなし)。240文字ごとに4つのセグメントに分割して入力します。- 借方/貸方/計画なし行の注文タイプ: 数量は
TARGET_QTYのみを認識する —BAPISDITMにはKWMENGを参照するフィールドはありません。そのような明細カテゴリはTVEPZに設定がありません ⇒ 計画行は生成されず、送信されたBAPISCHDLは静かに破棄され、戻り値 S だがKWMENG=0、NETWR=0、VBEPは0行。判定基準はTVEPZにエントリがあるかどうか。TVAP-LFRELは使用不可(Z001とZ014のLFRELは両方とも空)。VBTYPも使用できません。読み取り側も同じ落とし穴:VBAP-KWMENGは常に0、数量はVBAP-ZMENG⇒ 戻り値のロジックは「KWMENGが空でなければ優先、空の場合はZMENGにフォールバック」。 BAPE_*の最初のフィールドはキー位置であり、変更モードで省略すると伝票番号がクリアされる — 標準関数はEXTENSIONINの値をmove-correspondingでE_VBAKKOMに入れます。VBELNが空のままの場合、伝票番号は空で上書きされます ⇒V1 045の後にV4 219が続く(根本原因はV4 219の前にある E メッセージを確認すること)。- SD 参照伝票は3点セット —
REF_DOC+REF_DOC_IT+REF_DOC_CA(参照伝票カテゴリ)。最初の2つだけを渡すと、V2 068 伝票カテゴリを含まないが C を含む(&2は空)。行番号に0を指定するか、伝票が存在しない場合も同じメッセージですが、&3は空です。カテゴリは参照される伝票自体のVBAK-VBTYPから取得します。ヘッダのVGTYPからではありません。 SD_SALESDOCUMENT_CHANGEのエントリは、2つのテーブルをそれぞれ並べ替える — 削除行はITEM_INとITEM_INXの両方にITM_NUMBERを入力する必要があります。ITEM_INXのみを入力すると、位置がずれ、全く関係のないT184 利用可能な明細カテゴリがありませんエラーが発生します。BAPI_USER_EXISTENCE_CHECKはこのシステムでは、どのアカウントに対しても0を返す(ZZZZZZ999でも成功する)⇒ チェックは形骸化しており、伝票が無効なアカウントに転送されると、両方の画面で表示されなくなります。USR02を直接確認し、長さをチェックします(0 < len <= 12)。DELETE_TEXTは存在しないテキストに対してRAISING NOT_FOUNDを発生させる — 標準関数はこの E メッセージをBAPIRET2に追加し、frm_check_returnで失敗と判定します ⇒ 「クリア」が逆にインターフェースを E にします。クリアする前にSTXHを確認します(TDOBJECT+TDNAME+TDID+TDSPRAS)。存在しない場合はスキップします(これにより操作が冪等になります)。また、SD_SALES_DOCU_MAINTAINでは、クリア機能コードは003です(DELETE_TEXT)。&&/005を渡すと、テキストが空行で上書きされるだけです。- 新しい価格条件を追加する際に、ステップ番号を指定してはいけない —
COND_ST_NO='001'をハードコーディングし、ZP01がSTUNR=010にある場合 ⇒ 「価格条件タイプがステップ番号 001 にありません」エラー。空のままにして、価格設定手順が自動的に配置できるようにします。「条件が既に存在するかどうか」でUPDATEFLAGのI/Uを駆動します。 - インターフェースで伝票が分割される場合、
OBJ_KEYは1つの伝票番号のみを返す ⇒ 伝票数が少なく報告され、行に基づいて書き戻すと誤った伝票を指します。OBJ_KEYのレイアウトは伝票番号(10)+会社コード(4)+会計年度(4)であり、年度は+14(4)にあります(よく誤って+10(4)と記述され、会社コードを取得します)。 BSEG-BUZEIはNUMC(3)であり、1つの伝票の物理的な上限は999行ですが、BAPI は実際には990行のみを取り込む — 分割の上限は989 明細 + 1 差額です。境界は 999/1000/1997 をカバーする必要があります(1997 は>999の判定で1000行に切り出され、オーバーフローします)。BAPIACHE09にはEXCH_RATEフィールドがない — 二重通貨(00+10行)で、00行のみにレートが設定されている場合、システムは OB08 に従ってヘッダレートを再決定します(7を入力すると8.28になります)。CURR_TYPE='10'行にもexch_rateを含める必要があります。明細行と差額行の両方で必要です。- 二重通貨伝票の各
ITEMNO_ACCには、2つのCURRENCYAMOUNTエントリが必要であり、各通貨の小計は0でなければならない —10行がないとInconsistent currency informationエラー。分割によって生成された差額行にも同様に追加する必要があります。 UMSKZは3つの BAPI 構造のいずれにもフィールドがない — 手形関連(W/S)を標準 BAPI チャネル経由で処理すると、静かに破棄され、勘定科目が誤って転記されます。EXTENSION2拡張を介してデータベースに格納するか、全体をPOSTING_INTERFACE_*(FB01 バッチインプット)経由で処理する必要があります。READ_TEXTのNAME仮引数はTDOBNAME(70) であるが、実引数TDNAME(16) は実行時型の競合を引き起こす — コンパイルは成功し、空の入力パラメータでは正常に動作しますが、データがヒットするとすぐに 500Type conflict during a function module callエラー。仮引数はTYPE tdobnameと記述するか、STXLクラスタテーブルを直接読み取ります。
3.3 テキスト / 説明 / 列見出しが空になる
- フィールドに匿名の組み込み型を使用すると、
DD03Tテキストが空になり、ALV 列見出しが空白になる — フィールドはデータ要素にアタッチする必要があります(アタッチ後にDD03Tが0行になるのは正しい状態であり、テキストは上位に移動します)。構造フィールドのテキストは、DDL でフィールドごとに@EndUserText.labelを指定することによってのみ設定できます。入れ子になったSTRU/TTYP型のフィールドには、常にDD03Tテキストがありません(SAP 標準構造も同様)。CreateStructureのfields[].descriptionはDD03Tに入りません。 - テキスト要素がアクティベートされていない / inactive バージョンのみの場合、実行時に
TEXT-xxxは常に空になる — ALV 列見出しが DDIC の短いテキストに戻り、ステータス列が空になります。 - ドメインが型のみを定義し、値セットを維持していない場合、選択画面の F4 ヘルプが表示されない —
DD07Tの確認にはAS4LOCAL='A'(そうしないと非アクティブ行を取得)、ORDER BY VALPOS(そうしないと1,10,11,2,3…の辞書順になる)、言語はドメインのマスター言語にフォールバックDD01L-DOMMASTER(フィールド名はMASTERLANGではない)が必要です。 MESSAGE ... WITHはメッセージ全体で約70文字の上限がある ⇒ 長い BAPI エラーメッセージはField Reas...のように静かに切り詰められ、トラブルシューティングの方向性を誤らせます。短いプレフィックスと、元のテキストを連結した文字列テンプレート(≤220文字、BAPI_MSGに合わせる)を使用します。BAPIACGL09-ITEM_TEXT(50) /BAPIACHE09-HEADER_TXT(25) は静かに右側が切り詰められる — 「プレフィックス+サフィックス」を連結する場合は、最初にプレフィックスを長さ-サフィックス長に切り詰めます。- DDIC コンポーネント名は30文字で静かに切り詰められる — JSON キー名のマッピング時に、キーが予期せず失われる可能性があります。
3.4 権限とフィルタリング
AUTHORITY-CHECKに空の値を渡すと、通常は権限なしとなり、関連するレコードが永久に表示されなくなる — ロールにはEKGRP = spaceが許可されることはありません。組織レベルのフィールドが空の場合は、チェックを行わずにそのまま通過させる:IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF.。画面全体を拒否するのではなく、行レベルのフィルタリングを使用します。そうしないと、権限のないユーザーはトランザクションコードに入ることすらできません。MODIF IDは表示/非表示のみを制御し、非表示フィールドに入力された値はフィルタリングに参加する — 非表示のシナリオでは、データ取得側で RANGE を使用して明示的にブロックします。
4. トランザクションと LUW
EXPORT ... TO DATABASE(クラスタログ)はトランザクション制御の対象である — 「データベーステーブルへの書き込み → 伝票の作成 → 失敗によるロールバック」により、ログとテーブルが同じ LUW 内に存在することになります。ROLLBACKはindx=01の入力パラメータスナップショットも一緒にロールバックします(indx=02は残ります)。また、基盤となる転記 FM は内部でBAPI_TRANSACTION_ROLLBACKも実行するため、「自分の ROLLBACK を削除する」や「順序を変更する」は無効です。 ⇒ 解決策: 唯一のデータベース書き込みアクションの前、かつ BAPI 呼び出しの前に、独立したCOMMIT WORKを挿入します。これにより、ROLLBACKは元のまま保持されます。付随的な利点: BAPI を呼び出す前に、自身の LUW を決済することは、そもそも正しい手順です。- アトミック性が必要な場合は、それぞれコミットしてはいけません。独立したコミットが必要な場合は、読み戻しを保持する必要があります —
SD_SALESDOCUMENT_CHANGEのCALL_FROM_BAPI='X'は、更新を更新キューに保留します。コミットしないと読み取れません ⇒COMMIT WORK AND WAITを削除した後に残したSELECTによる読み戻しチェックは、必然的に「フラグが更新されていない」と誤判定します。転記失敗の分岐では、ROLLBACK WORKを追加する必要があります。そうしないと、保留された SD 更新が宙に浮き、次のコミットで誤ってデータベースに書き込まれる可能性があります。 - プラットフォームスナップショットは FM パラメータのみをバインドし、関数内のローカル変数は一切バインドしない — メッセージが
lv_req/lv_respにある場合、プラットフォームからは見えません。モニターのserializeはname_mappingsを持たず、契約メッセージではなく DDIC の大文字名を出力します。ビジネス側で独自のメッセージログテーブルと、save( )の1行をメッセージが唯一生成される場所(frm_sendの末尾)に記述します。独立したコミットは、プラットフォームがロールバックによってログを消費するという欠点を補います。
5. ポップアップ / F4 / ALV / 選択画面
POPUP_GET_VALUESのRETURNCODEのセマンティクスは直感に反する —'A'= キャンセル。スペース = 確認、かつFIELDSが入力済み(LSPO4U01:IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.)。IF lv_rc <> 'A'と記述すると、「確認」を「キャンセル」と見なします ⇒ 現象はポップアップの確認ボタンをクリックしても何も起こらない(メッセージなし、アクションなし、ダンプなし)。POPUP_GET_VALUESは、フィールドが DDIC 検索ヘルプにアタッチされている場合にのみ F4 を表示する — 判定基準はF4IF_DETERMINE_SEARCHHELP(0=あり / 2=フィールドはあるがヘルプなし / 1=フィールドが存在しない)。SKA1-SAKNRに変更してヘルプを借用してはいけません。ALPHA ゼロ埋めが導入され、'8000'が'000008000'になり、チェックが誤って失敗し、データベースの格納形式が変わります。 ⇒ 代わりにPOPUP_GET_VALUES_USER_HELP+F4_FORMNAME+F4_PROGRAMNAME = sy-repidを使用します。フィールドの属性は変わりません。出力側の FORM は FM によって 5つのUSINGで呼び出されます(tabname/fieldname/display_flag/returncode/value)。選択した値を5番目の仮引数に書き戻し、returncodeを'F'に設定します。これでポップアップがリフレッシュされます(sy-steplは取得できないため、dynprofield 経由の書き戻しは行わないでください)。POPUP_GET_VALUESは条件に応じてフィールドを非表示にすることをサポートしていない —SVALはFIELD_ATTR(必須入力/読み取り専用)のみを制御できます。タイプに応じて対応するフィールドセットのみをロードし、ユーザーがタイプを変更した場合は、新しいタイプでポップアップを再表示します(タイプが変更されていない場合は終了し、ループしません)。F4IF_INT_TABLE_VALUE_REQUESTにFIELD_TABを渡す場合、各行はINTTYPEを導出できなければならない — 導出チェーンTABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME
…
[truncated for translation]