Community编程与开发github.com

yanghangmvp/sap-abap-pitfalls

ABAP 开发踩坑与解法库(跨项目通用)。沉淀自示例客户 DEV ERP 项目 100+ 份交付报告与逐日纠错记录,共约 120 条「现象 → 根因 → 解法」,另附新项目开工 checklist(摸底/动手前/编码自检/交付/收尾五阶段)。覆盖 ADT 激活假成功/假锁判定、ABAP 语法硬约束、「返回成功但数据没变」的静默失效清单、事务与 LUW、F4 与弹窗与 ALV、接口 JSON 与网关、DDIC 建对象、ST22 与探针排查手法、打补丁脚本陷阱。开新 ABAP 项目做开场阅读与开工检查,或遇到「编译通过但激活失败 / 激活成功但运行不对 / 返回 S 但值没变 / 报错文案与真实原因无关 / 按钮点了没反应 / 数据莫名错位」时加载本技能。

sap-abap-pitfalls 是什么?

sap-abap-pitfalls is a Claude Code agent skill that aBAP 开发踩坑与解法库(跨项目通用)。沉淀自示例客户 DEV ERP 项目 100+ 份交付报告与逐日纠错记录,共约 120 条「现象 → 根因 → 解法」,另附新项目开工 checklist(摸底/动手前/编码自检/交付/收尾五阶段)。覆盖 ADT 激活假成功/假锁判定、ABAP 语法硬约束、「返回成功但数据没变」的静默失效清单、事务与 LUW、F4 与弹窗与 ALV、接口 JSON 与网关、DDIC 建对象、ST22 与探针排查手法、打补丁脚本陷阱。开新 ABAP 项目做开场阅读与开工检查,或遇到「编译通过但激活失败 / 激活成功但运行不对 / 返回 S 但值没变 / 报错文案与真实原因无关 / 按钮点了没反应 / 数据莫名错位」时加载本技能。.

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/yanghangmvp/MCP_ABAP/tree/HEAD/skills/sap-abap-pitfalls

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

ABAP Development Pitfalls & Solutions Library

Purpose: Read through once before starting a new project; consult by symptom when troubleshooting. This skill only contains experience that is reusable across projects. Environment addresses, request numbers, and object lists are documented in the project-level master document (e.g., sap-abap-project-ops). The full 120 entries (including code snippets and sources) are in references/casebook.md.

How to use: New project startup → directly flip to §11 Startup checklist (execute in chronological order); Already started and encountered a problem → §10 Symptom quick lookup to locate the relevant section; Want to delve deeper into a specific entry's complete code and source → references/casebook.md.

Three meta-rules (more important than any single pitfall):

  1. Error messages are often unrelated to the real cause — The ABAP compiler's error point is often "where it parsed to", not "where you made the error". First suspect sequence/order of keywords/type mismatch, not the syntax itself.
  2. The most dangerous thing in SAP is not an error, but silent failure — Data can be silently corrupted under the triple illusion of 'Return S', 'Activation 200', 'Compilation successful'. For anything involving BAPI, SELECT INTO TABLE, EXTENSIONIN, text elements, enhancement fields, always do a database reconciliation, do not trust the return value.
  3. If it fails, always check ST22 first, don't guess — The Error analysis in a short dump directly gives you the procedure "XXX" "(FORM)" and the CX_SY_* exception class, which is an order of magnitude faster than trying individual FORMs.

0. Ten Iron Rules (Violation Means Rework)

  1. Before modifying any existing object, the first step is to read back the current system state. Users often modify production objects directly in SE80 without writing back to the archive. The local archive is for comparison only, not as a baseline. get_source → diff with backup to confirm 0 difference → apply patch → read back and diff again → reverse overwrite archive.
  2. Text elements are part of the baseline; get_source does NOT include TEXTPOOL. Before pushing text in an overwriting manner, you must use read_textelements to get the full text of the active version from the archive, diff it with the local JSON, manually merge, and then push. Otherwise, text changed by the user in the SE38 frontend will be silently overwritten, and the source code diff will show no changes at all.
  3. Never directly UPDATE / MODIFY standard tables. To change a posted FI document, use FI_DOCUMENT_CHANGE (group FACG, in T_ACCHG only fill FDNAME+NEWVAL, do not pass I_BUZEI in the header, only in line items).
  4. Don't create request numbers yourself. If a transportable object lacks CORR_NR ⇒ stop and report, wait for the user to provide the number; before creating a new TADIR object, first report 'type + suggested name + TADIR duplicate check result + reason for number continuation', get approval, then create. For subsequent changes to existing objects already attached to a request, reuse the original number, don't ask again.
  5. Don't use Update* classes for exploratory calls to interfaces. They actually write — once UpdateFunctionModule was used with just the function body to 'try', and it overwrote the inactive source code with a 620-character truncated version. Probes should only use read-only interfaces.
  6. Four steps for delivery: Generate → Push source → Push text elements (PROG/PX requires separate activation) → Generate GUI status (CUA). If text elements are not activated, TEXT-xxx will be constantly empty at runtime.
  7. Production source comments: Keep the skeleton, remove the narrative. Keep the program header (unit/author/date/summary/change log) + *&--- frame lines + FORM banner + -->/<-- parameter documentation + short judgment-type comments; remove design rationale, algorithm explanations, joint testing records, TODO reasons, scripts/work/ADT/ZTEST_* references. After stripping, a diff must prove zero change to the code body.
  8. Delete test/probe objects immediately after testing. First get_source to archive in backup/ → delete → recheck TADIR to confirm it's cleared. Only delete objects created by yourself in this round; do not touch objects created by others, objects already attached to packages/requests, or DEMOs in use.
  9. Check locks first, backup first, then write to SAP. Do not pass credentials to external files, do not display them, do not write them into deliverables.
  10. When the user says 'the data is fine', be even more certain that the program is the problem. Use the 'error amount + document number' to reverse-engineer the input characteristics, write a probe to meticulously reproduce the error; once the reproduction is successful, the root cause is locked.

1. Activation and Write Channels (ADT / MCP)

  • HTTP 200 ≠ Activation Success — Success/failure is written in the body: Must explicitly check for activationExecuted="false" and raise an error; the only reliable way to verify activation is to read back the include or compare the ?version=active and inactive versions. TRDIR-RSTAT is not written in S/4.
  • False success is the hardest to detect — Old scripts used regex to find messages by <chklst:shortText> / <msg>text</msg>, but the real structure is chkl: prefix + <shortText><txt> + msg with attributes ⇒ not a single message could be extracted, and reporting 'Activation successful' three times was actually a 131-character empty shell. If you can't capture a message, it must be treated as a failure.
  • Text elements are independent activatable objects PROG/PX — Activating the parent program does not activate them; push_textelements with only GET→LOCK→PUT→UNLOCK and no ACTIVATE means PUT only writes the inactive version. When called on PROG/P, SAP finds nothing activatable, returns activationExecuted="false" and no messages at all ⇒ silent inaction. The activation interface does not accept corrNr (returns 400 if passed).
  • push_textelements PUT is a full overwrite; the push order cannot be reversed — New symbols: push text elements first, then source; Deleting symbols: push source first, then text elements. TE_FILE must include all existing selections as well.
  • When text elements only have an inactive version, the program must be regenerated to take effect — Just pushing text elements and then PUTting the same source code again will not refresh them. Change one line of source code (even a comment), push again, or recreate the program.
  • Three types of 'false lock': ① The message 'Locked in user xxx's request yyy' is actually missing transport_request/corrNr; ② UpdateDataElement reports locked by another user which is actually a 400 'already exists' — the handler's isAlreadyExistsError() only checks English, cannot detect Chinese messages, and the fallback to 403 is translated as 'locked' (⇒ In ZH environments, MCP cannot modify existing data elements; UpdateDomain has no such precondition so it is unaffected); ③ 403 … not specified for this client means the client is wrong.
  • Real locks can only be deleted via SM12 — Three types of locks (ESDICT/ESRDIRE/SEOCLSENQ) are held on ADT sessions. DEQUEUE_ALL, _scope='3', DEQUEUE_EESDICT are all completely ineffective. After confirmation via ENQUEUE_READ, ask the user to manually delete in SM12 and re-run immediately; do not wait for session timeout.
  • Two different 403 messages have completely different meanings — User XXX is currently editing ZFIDxxx = a real person has SE80 open, wait; Object ... locked in task DEVK90011x not specified for this client = wrong client, re-push with the request's correct client. Confusing the two will send you on a wild goose chase.
  • The Accept header for ADT LOCK must include the datatype — Sending only application/xml means SAP has already acquired the lock successfully, but responds with a 406, so you don't get the LOCK_HANDLE ⇒ lock leak, and all subsequent requests will report 'User X is currently editing', fooling you into thinking someone else is using it. Correct value: application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result.
  • In scripts, fail() using process.exit() skips finally ⇒ enqueue lock remains — After an exception in a push script, SEOCLSENQ locks can persist for 40 minutes, causing all subsequent LOCK requests to give 403 as if 'a user has SE80 open'. Always write scripts to UNLOCK first before exiting, or use process.exitCode = 1.
  • Do not force-delete edit locks on behalf of the user — Deleting locks via ADT will discard the other user's unsaved changes.
  • Includes are independent ADT resources — POSTing to /programs/programs always creates an executable program (TRDIR-SUBC=1), which cannot be changed later; includes are posted to /sap/bc/adt/programs/includes (root element <include:abapInclude>). The main program and its includes must be in the same activation request, or they will report each other as undefined.
  • INSERT REPORT ... STATE 'A' creates an include with the source as the active version — Later modifications to the inactive version will still use the old active version for compilation when the main program is activated, causing errors that point to the old code. After the change, you must also run INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'. (with STATE before PROGRAM TYPE) to refresh the active version.
  • Local proxies can disrupt ADT requests — The symptoms are identical to 'cannot connect to SAP' (socket hang up / CSRF failure). Before spawning an MCP child process, delete *_proxy and set NO_PROXY=*; for all write requests, set proxy: false.
  • ADT has no dynpro / GUI Status / Menu resources — However, CUA can be handled via code (RS_CUA_INTERNAL_FETCH + _WRITE, state='A', takes effect immediately upon write). D020S is the dynpro screen header, not the GUI status table; RS_CUA_GET_STATUS cannot be used for determination, only RS_CUA_INTERNAL_FETCH is reliable (provide all 10 mandatory TABLES, use ORDER BY pposition to prevent the 25-row truncation).

2. Syntax Hard Constraints (Compile on First Pass)

  • Four taboos for FORM parameters: ① Cannot write TYPE c LENGTH n (reports Numeric literals cannot be used as field names) ⇒ use TYPE char<n> or a DDIC type; ② A fixed-length parameter receiving a different length causes a compile-time 'incompatible' error, but text symbols are an exception, though they still cause a runtime 500 Type conflict when calling a FORM ⇒ for cross-length use, use generic TYPE c; ③ Cannot inline TYPE STANDARD TABLE OF (the parameter is counted as 8 separate ones, reporting Different number of parameters) ⇒ define a TYPES first; ④ FIELD-SYMBOL declared inside a FORM is local; to use it across FORMs, it must be global.
  • FORM ... TABLES must be written before USING/CHANGING — Writing USING ... CHANGING ... TABLES reports Field "TABLES" is unknown. (treating the keyword as a parameter name). The PERFORM call side has the same requirement.
  • CALL FUNCTION parameter order is mandatory — EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. Putting TABLES before IMPORTING reports Unable to interpret "EV_OBJNR", completely obscuring the real issue. The called function's EXPORTING parameters must be in the calling program's IMPORTING section; putting them in the wrong place reports CALL_FUNCTION_PARM_UNKNOWN — very easily misdiagnosed as 'the FM interface was not generated'.
  • Multi-field SELECT must be comma-separated, @ host variables must be escaped — Reports elements in the "SELECT LIST" list must be separated using commas. The same applies to multi-assignment in UPDATE ... SET; constants (including CONSTANTS) in WHERE clauses also need @.
  • The position of INTO and UP TO n ROWS is determined by the activation error — Statements with JOINs/subqueries/SQL expressions are in strict mode, forcing INTO to be last (SELECT 563); UP TO n ROWS must be before WHERE. The two constructs behave differently depending on the statement, so memorizing the pattern can backfire.
  • ASSIGN ... CASTING TYPE c LENGTH <variable> is not supported — The length must be a literal; removing LENGTH causes a DUMP Invalid subfield access on index overflow. The correct approach is to use cl_abap_container_utilities=>fill_container_c( ) (inverse of the standard function READ_CONTAINER_C).
  • In DESCRIBE FIELD, TYPE must be written before COMPONENTS — Even if you only want the number of components, you must write: DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS must include VALUE — CONSTANTS: c_x TYPE flag. reports "VALUE ..." is not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n is not valid — Text symbols cannot be used as the message text for a WITH clause. Use an auxiliary FORM: lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • Fixed-length types don't have a name like char<n> — TYPE char1000/char55/char220 are all illegal, reporting Type "X" is unknown; always use TYPE c LENGTH n. Guessing the type name wrong = one wasted activation cycle. If unsure, check DD04L or use get_source.
  • class=>method can only be used for static methods — For instance methods, you must use CREATE OBJECT lo. lo->method( ), otherwise it reports can only be specified with static methods.
  • String templates cannot be used as PERFORM ... USING arguments — Reports Field "|" is unknown; assign to a variable first and then pass it.
  • CALL TRANSACTION ... WITH f1 v1 is not valid syntax — WITH is only for AUTHORITY-CHECK. Parameters can only be passed via USING bdcdata; the SM30 initial screen only has the table name as one field (VIMDYNFLDS has no 'update mode' field). To enter change mode, use VIEW_MAINTENANCE_CALL with action = 'U'.
  • REPLACE ALL OCCURRENCES OF ... WITH '' can cause an endless loop in this type system — For deleting spaces/characters, use a character-by-character approach: DO strlen TIMES + lv_txt+lv_i(1) filter.
  • CONCATENATE does not accept numeric types — lv_cnt TYPE i reports 'must be character-like'; built-in functions (strlen/lines) cannot be used directly in && (reports The syntax for a method specification is objref->method, which confusingly suggests an arrow syntax error). Always use string templates for numbers: |...{ lv_cnt }...|.
  • In a $TMP probe, FORMs cannot be defined; comments outside method bodies are not saved — The probe activation reports Statement is not accessible (while CheckProgram does not report it); comments between ENDMETHOD and the next METHOD in a class report unknown comments which can't be stored.
  • SELECT-OPTIONS name must be ≤ 8 characters — A 9-character name is rejected on the first activation. If you shorten the name, you must simultaneously update the text element selections entry name (old variants with the old name will be silently ignored, only a hint is given, no error).

3. Silent Failure List (Most Costly Section)

If you get 'Return S' / 'Activation 200' / 'Compilation successful' but results are wrong, check here first.

3.1 Data Misalignment and Loss

  • SELECT with explicit column list INTO TABLE <pre-declared fully typed table> assigns by position — If the types happen to be compatible, no error or warning is given: DOMVAL from DD07T lands in DOMNAME, WKGBTR from COEP lands in MEGBTR (amount is always empty), ZTEPIC3A reports component "ZJK" not compatible with "ZJYRQ" (detected using _act_dbg coordinates). This single pitfall recurred in 5 different reports. Three solutions: SELECT * / INTO TABLE @DATA(...) inline (structure is determined by the list) / create your own narrow structure with matching column order; or INTO CORRESPONDING FIELDS OF TABLE.
  • Placing a LEFT JOIN right-table field in the WHERE clause ⇒ semantics degrade to INNER JOIN — When the right table has no match, the value is NULL, and the condition is false. Move the filter condition into the ON clause.
  • DELETE ADJACENT DUPLICATES ... COMPARING <non-key field> can swallow data — The conditional table A017 has DATBI and KNUMH in its primary key; KNUMH is not a key field. Using COMPARING knumh to deduplicate deletes all subsequent validity periods for the same KNUMH. First SORT BY all keys, then COMPARING all keys.
  • AT NEW / AT END OF works on physically adjacent rows; it will definitely break after an ALV write-back — REUSE_ALV_* reorders t_outtab in place (triggered when the user sorts by a message column), so the same group is no longer adjacent, causing AT END OF to trigger multiple times. For grouping, change the method to 'outer loop over grouping keys × inner loop to collect by key', and only recognize business keys for group boundaries.
  • APPEND overwrites sy-tabix — After APPEND INITIAL LINE ... ASSIGNING <fs>, sy-tabix gives you the segment index, not the original row number ⇒ ranges are amplified, 128 lines output as 558 lines (same record repeated 43 times). At the beginning of each loop, save: lv_tabix = sy-tabix.
  • When READ TABLE ... WITH KEY misses, it sets sy-tabix to 0 — Subsequently APPEND sy-tabix TO lt_del adds it to the deletion list, and DELETE ... INDEX 0 causes a direct DUMP TABLE_INVALID_INDEX. Save the index at the start of the loop and check IF idx >= 1 AND idx <= lines( ) before deleting.
  • Inline declaration DATA(...) inside an IF branch means the internal table is never created if the condition is false ⇒ subsequent LOOP causes a short dump. Move the declaration to the method header, or add a pre-empt check for emptiness after selecting data.

3.2 BAPI / Standard Functions 'Return S or No Error But Did Not Write'

  • BAPI x structures must be passed as a pair with their original structures — Passing depreciationareas without depreciationareasx ⇒ if AFASL is filled, you get No change parameter given for depreciation area 01. Missing the x structure does not cause a syntax error; it is only triggered when the data condition is met.
  • The data element for SD enhancement field identifier structures must be CHAR1 — SD_SALESDOCUMENT_CHANGE uses GET_CHANGEABLE_FIELDS, which only scrolls through fields where WHERE rollname = 'CHAR1' OR 'CHAR01'; fields using BAPIUPDATE are all excluded ⇒ returns S, value unchanged, zero errors, while the creation path does not use this list ⇒ classic 'creation works, modification cannot write'. Cannot be changed back to BAPIUPDATE.
  • BAPIPAREX-VALUEPART1 is only 240 characters — Standard functions read VALUEPART1~4 contiguously by offset (total 960). If you only fill the first part, the tail is silently truncated (BAPE_VBAP tested with 271 characters, last 3 fields all empty, no error). Split into four 240-character parts.
  • For debit/credit note / non-planned order types: quantity only recognizes TARGET_QTY — No field in BAPISDITM references KWMENG; this item category has no configuration in TVEPZ ⇒ no schedule line is generated, the passed BAPISCHDL is silently discarded, returns S but KWMENG=0, NETWR=0, VBEP has 0 rows. The criterion is whether TVEPZ has an entry, not TVAP-LFREL (both Z001 and Z014 have empty LFREL), nor VBTYP. The same pitfall exists on the reading side: VBAP-KWMENG is always 0, the quantity is in VBAP-ZMENG ⇒ the fill logic should be 'prioritize KWMENG if not empty, fall back to ZMENG if empty'.
  • The first field in BAPE_* structures is the key; if omitted in modification mode, the document number is cleared — Standard functions move-corresponding the EXTENSIONIN value into E_VBAKKOM. If VBELN is left empty, it overwrites the document number with an empty value ⇒ V1 045 followed by V4 219 (look at the E message before V4 219 for the root cause).
  • SD reference documents require a triplet — REF_DOC + REF_DOC_IT + REF_DOC_CA (reference document category); passing only the first two reports V2 068 ... with &2 empty. Filling the item number as 0 or a non-existent document produces the same message but with &3 empty. The category comes from the referenced document itself, i.e., VBAK-VBTYP, not the header's VGTYP.
  • SD_SALESDOCUMENT_CHANGE requires sorting for two of its tables — For deletion, both ITEM_IN and ITEM_INX must be filled with ITM_NUMBER; filling only ITEM_INX causes misalignment and reports a completely unrelated error: T184 No item category available.
  • BAPI_USER_EXISTENCE_CHECK returns 0 for any account in this system (even ZZZZZZ999 passes) ⇒ the check is useless; documents transferred to invalid accounts are invisible from both UIs. Directly query USR02 + add length validation (0 < len <= 12).
  • DELETE_TEXT RAISING NOT_FOUND for non-existent text — The standard function appends this E message to BAPIRET2, causing frm_check_return to flag it as a failure ⇒ the 'clear' operation actually causes the interface to return E. Before clearing, check STXH first (TDOBJECT+TDNAME+TDID+TDSPRAS); if it doesn't exist, skip the operation (this also makes the action idempotent). Also: in SD_SALES_DOCU_MAINTAIN, the function code for clearing is 003 (DELETE_TEXT); passing &&/005 just rewrites the text as a single empty line.
  • When adding a pricing condition, do not specify the step number — Hardcoding COND_ST_NO='001' while ZP01 is at STUNR=010 ⇒ returns 'Pricing type not in step number 001'. Leave it empty for the pricing procedure to locate automatically; use 'whether the condition already exists' to drive UPDATEFLAG as I/U.
  • When splitting an interface document a second time, OBJ_KEY only returns one document number ⇒ the count is understated, and writing back by line points to the wrong document. The layout of OBJ_KEY is document number(10)+company code(4)+fiscal year(4), with the year at +14(4) (often mistakenly written at +10(4), which captures the company code).
  • BSEG-BUZEI is NUMC(3), with a physical maximum of 999 lines per document; BAPI actually only accepts 990 lines — The chunk limit is 989 detail + 1 balancing line; boundaries must cover 999/1000/1997 (1997 checked as >999 would yield 1000 lines and overflow).
  • BAPIACHE09 does not have an EXCH_RATE field — When dual-currency (00+10 lines) is used, if only the 00 line carries the exchange rate, the system recalculates the header rate based on OB08 (input 7, result 8.28). The CURR_TYPE='10' lines must also carry exch_rate, both in the detail lines and the balancing line.
  • For dual-currency documents, each ITEMNO_ACC must have two CURRENCYAMOUNT entries, and the subtotal for each currency type must be 0 — Missing the 10 line reports Inconsistent currency information; the balancing line generated by splitting must also be supplemented.
  • UMSKZ is not present in any of the three BAPI structures — For bill of exchange types (W/S), using the standard BAPI path will silently discard it, posting to the wrong account. It can only be stored via EXTENSION2 enhancements, or the entire document can be processed via POSTING_INTERFACE_* (FB01 batch input).
  • The NAME parameter of READ_TEXT is typed as TDOBNAME(70), but the actual parameter TDNAME is (16), causing a runtime type conflict — Compilation passes, empty input works fine, but as soon as data is hit, a 500 Type conflict during a function module call occurs. Type the parameter as TYPE tdobname, or directly read the cluster table STXL.

3.3 Text / Descriptions / Column Headers Becoming Empty

  • Using an anonymous built-in type for a field ⇒ DD03T text is empty ⇒ ALV column headers are completely blank — The field must be linked to a data element (after linking, DD03T having 0 rows is the correct state, text is moved up). Structure field text can only be provided via DDL with @EndUserText.label per field; nested STRU/TTYP type fields never have DD03T text (SAP standard structures are the same). CreateStructure's fields[].description does not enter DD03T.
  • Text elements are not activated / only exist as inactive version ⇒ runtime TEXT-xxx is always empty — This manifests as ALV column headers falling back to DDIC short text, or status columns being empty.
  • A domain is defined with only a type and no value set ⇒ the selection screen F4 help does not show candidates — When querying DD07T, you must include AS4LOCAL='A' (otherwise you get inactive rows), ORDER BY VALPOS (otherwise you get lexicographic order like 1,10,11,2,3…), and the language fallback to the domain's master language DD01L-DOMMASTER (the field name is not MASTERLANG).
  • MESSAGE ... WITH has an upper limit of about 70 characters for the entire message text ⇒ Long BAPI errors are silently truncated to something like Field Reas..., misleading the investigation. Use a short prefix + string template with the original text (≤ 220 characters, aligned to BAPI_MSG).
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) silently truncate on the right — When concatenating 'prefix+suffix', first truncate the prefix to length - suffix_length.
  • DDIC component names are silently truncated at 30 characters — JSON key name mapping can mysteriously drop keys.

3.4 Authorization and Filtering

  • Passing an empty value to AUTHORITY-CHECK usually results in no authorization ⇒ related records are permanently invisible — Roles typically do not grant EKGRP = space. For organizational-level fields that are empty, skip the check and allow access directly: IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF. Use row-level filtering instead of rejecting the whole screen, otherwise users without authorization cannot even enter the transaction code.
  • MODIF ID controls visibility only; values entered into hidden fields still participate in filtering — For hidden scenarios, explicitly block the values using RANGES on the data retrieval side.

4. Transactions and LUW

  • EXPORT ... TO DATABASE (cluster log) is subject to transaction control — The sequence 'write to underlying table → create document → rollback on failure' means the log and the table are within the same LUW. ROLLBACK will eat the input snapshot in indx=01 along with it (though indx=02 remains). Additionally, the underlying posting FM internally performs BAPI_TRANSACTION_ROLLBACK, so simply 'removing your own ROLLBACK' or 'changing the order' is ineffective. ⇒ Solution: Insert an independent COMMIT WORK statement just before the single database write action, and before calling the BAPI. This preserves the input data in the cluster even if a subsequent ROLLBACK occurs. An added benefit: settling your own LUW before calling a BAPI is best practice anyway.
  • If atomicity is required, don't commit separately; if independent commits are needed, preserve the read-back — SD_SALESDOCUMENT_CHANGE with CALL_FROM_BAPI='X' places the update into the update queue. Until committed, a SELECT read-back will always incorrectly conclude that 'the flag was not updated'. In a failure branch, a ROLLBACK WORK must be added, otherwise the pending SD update will hang in the air and may be written incorrectly by a later commit.
  • Platform snapshots only capture FM parameters; local variables within the function are never captured — Messages in lv_req/lv_resp are not visible on the platform; the monitor's serialize does not use name_mappings, so it outputs the DDIC uppercase name instead of the contract message. The business side should build its own message log table and place a single save( ) call at the only point where the message is generated (end of frm_send). An independent commit here exactly compensates for the platform's weakness of losing logs during rollbacks.

5. Popups / F4 / ALV / Selection Screens

  • The RETURNCODE semantics of POPUP_GET_VALUES are counter-intuitive — 'A' = Cancel; Space = Confirm and FIELDS have been filled back (LSPO4U01: IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). Writing IF lv_rc <> 'A' treats 'Confirm' as 'Cancel' ⇒ the symptom is: clicking confirm on the popup has absolutely no effect (no message, no action, no short dump).
  • POPUP_GET_VALUES only triggers F4 if the field is connected to a DDIC search help — The criterion is F4IF_DETERMINE_SEARCHHELP (0=has help / 2=field exists but no help / 1=field does not exist). Do not change the type to SKA1-SAKNR to borrow its help — this introduces ALPHA zero-padding: '8000' becomes '000008000', causing validation errors and incorrect persistence format. ⇒ Instead, use POPUP_GET_VALUES_USER_HELP with F4_FORMNAME + F4_PROGRAMNAME = sy-repid, keeping the field attributes unchanged. The exit FORM is called by the FM with 5 USING parameters (tabname/fieldname/display_flag/returncode/value). Write the selected value back into the 5th parameter and set returncode to 'F' to refresh the popup (sy-stepl is not available here, so do not try to fill via dynprofield).
  • POPUP_GET_VALUES does not support hiding fields based on conditions — SVAL can only control FIELD_ATTR (required/read-only). Load only the relevant field set based on the type. If the user changes the type, re-display the popup with the set for the new type (if the type hasn't changed, the popup exits without a loop).
  • When passing FIELD_TAB to F4IF_INT_TABLE_VALUE_REQUEST, each row must be able to derive INTTYPE — The derivation chain is TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME...

相关技能