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):
- 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.
- 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. - If it fails, always check ST22 first, don't guess — The
Error analysisin a short dump directly gives you theprocedure "XXX" "(FORM)"and theCX_SY_*exception class, which is an order of magnitude faster than trying individual FORMs.
0. Ten Iron Rules (Violation Means Rework)
- 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. - Text elements are part of the baseline;
get_sourcedoes NOT include TEXTPOOL. Before pushing text in an overwriting manner, you must useread_textelementsto 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. - Never directly UPDATE / MODIFY standard tables. To change a posted FI document, use
FI_DOCUMENT_CHANGE(groupFACG, inT_ACCHGonly fillFDNAME+NEWVAL, do not passI_BUZEIin the header, only in line items). - 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. - Don't use
Update*classes for exploratory calls to interfaces. They actually write — onceUpdateFunctionModulewas 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. - Four steps for delivery: Generate → Push source → Push text elements (
PROG/PXrequires separate activation) → Generate GUI status (CUA). If text elements are not activated,TEXT-xxxwill be constantly empty at runtime. - 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. - Delete test/probe objects immediately after testing. First
get_sourceto archive inbackup/→ delete → recheckTADIRto 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. - 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.
- 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=activeand inactive versions.TRDIR-RSTATis 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 ischkl: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_textelementswith onlyGET→LOCK→PUT→UNLOCKand no ACTIVATE means PUT only writes the inactive version. When called onPROG/P, SAP finds nothing activatable, returnsactivationExecuted="false"and no messages at all ⇒ silent inaction. The activation interface does not acceptcorrNr(returns 400 if passed). push_textelementsPUT 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_FILEmust 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; ②UpdateDataElementreportslocked by another userwhich is actually a 400 'already exists' — the handler'sisAlreadyExistsError()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;UpdateDomainhas no such precondition so it is unaffected); ③403 … not specified for this clientmeans 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_EESDICTare all completely ineffective. After confirmation viaENQUEUE_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
Acceptheader for ADT LOCK must include the datatype — Sending onlyapplication/xmlmeans SAP has already acquired the lock successfully, but responds with a 406, so you don't get theLOCK_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()usingprocess.exit()skipsfinally⇒ enqueue lock remains — After an exception in a push script,SEOCLSENQlocks 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 useprocess.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/programsalways 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 runINSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(withSTATEbeforePROGRAM 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*_proxyand setNO_PROXY=*; for all write requests, setproxy: 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).D020Sis the dynpro screen header, not the GUI status table;RS_CUA_GET_STATUScannot be used for determination, onlyRS_CUA_INTERNAL_FETCHis reliable (provide all 10 mandatory TABLES, useORDER BY ppositionto prevent the 25-row truncation).
2. Syntax Hard Constraints (Compile on First Pass)
- Four taboos for FORM parameters: ① Cannot write
TYPE c LENGTH n(reportsNumeric literals cannot be used as field names) ⇒ useTYPE 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 500Type conflict when calling a FORM⇒ for cross-length use, use genericTYPE c; ③ Cannot inlineTYPE STANDARD TABLE OF(the parameter is counted as 8 separate ones, reportingDifferent number of parameters) ⇒ define aTYPESfirst; ④FIELD-SYMBOLdeclared inside a FORM is local; to use it across FORMs, it must be global. FORM ... TABLESmust be written beforeUSING/CHANGING— WritingUSING ... CHANGING ... TABLESreportsField "TABLES" is unknown.(treating the keyword as a parameter name). ThePERFORMcall side has the same requirement.CALL FUNCTIONparameter order is mandatory —EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. PuttingTABLESbeforeIMPORTINGreportsUnable to interpret "EV_OBJNR", completely obscuring the real issue. The called function'sEXPORTINGparameters must be in the calling program'sIMPORTINGsection; putting them in the wrong place reportsCALL_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 — Reportselements in the "SELECT LIST" list must be separated using commas. The same applies to multi-assignment inUPDATE ... SET; constants (includingCONSTANTS) inWHEREclauses also need@. - The position of
INTOandUP TO n ROWSis determined by the activation error — Statements with JOINs/subqueries/SQL expressions are in strict mode, forcingINTOto be last (SELECT 563);UP TO n ROWSmust be beforeWHERE. 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; removingLENGTHcauses a DUMPInvalid subfield accesson index overflow. The correct approach is to usecl_abap_container_utilities=>fill_container_c( )(inverse of the standard functionREAD_CONTAINER_C).- In
DESCRIBE FIELD,TYPEmust be written beforeCOMPONENTS— Even if you only want the number of components, you must write:DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count. CONSTANTSmust includeVALUE—CONSTANTS: c_x TYPE flag.reports"VALUE ..." is not specified.MESSAGE TEXT-xxx TYPE 'S' WITH nis not valid — Text symbols cannot be used as the message text for aWITHclause. 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/char220are all illegal, reportingType "X" is unknown; always useTYPE c LENGTH n. Guessing the type name wrong = one wasted activation cycle. If unsure, checkDD04Lor useget_source. class=>methodcan only be used for static methods — For instance methods, you must useCREATE OBJECT lo. lo->method( ), otherwise it reportscan only be specified with static methods.- String templates cannot be used as
PERFORM ... USINGarguments — ReportsField "|" is unknown; assign to a variable first and then pass it. CALL TRANSACTION ... WITH f1 v1is not valid syntax —WITHis only forAUTHORITY-CHECK. Parameters can only be passed viaUSING bdcdata; the SM30 initial screen only has the table name as one field (VIMDYNFLDShas no 'update mode' field). To enter change mode, useVIEW_MAINTENANCE_CALLwithaction = '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.CONCATENATEdoes not accept numeric types —lv_cnt TYPE ireports 'must be character-like'; built-in functions (strlen/lines) cannot be used directly in&&(reportsThe 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
$TMPprobe, FORMs cannot be defined; comments outside method bodies are not saved — The probe activation reportsStatement is not accessible(whileCheckProgramdoes not report it); comments betweenENDMETHODand the nextMETHODin a class reportunknown comments which can't be stored. SELECT-OPTIONSname 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 elementselectionsentry 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:DOMVALfromDD07Tlands inDOMNAME,WKGBTRfromCOEPlands inMEGBTR(amount is always empty),ZTEPIC3Areportscomponent "ZJK" not compatible with "ZJYRQ"(detected using_act_dbgcoordinates). 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; orINTO CORRESPONDING FIELDS OF TABLE.- Placing a
LEFT JOINright-table field in theWHEREclause ⇒ semantics degrade toINNER JOIN— When the right table has no match, the value is NULL, and the condition is false. Move the filter condition into theONclause. DELETE ADJACENT DUPLICATES ... COMPARING <non-key field>can swallow data — The conditional table A017 hasDATBIandKNUMHin its primary key;KNUMHis not a key field. UsingCOMPARING knumhto deduplicate deletes all subsequent validity periods for the sameKNUMH. FirstSORT BYall keys, thenCOMPARINGall keys.AT NEW/AT END OFworks on physically adjacent rows; it will definitely break after an ALV write-back —REUSE_ALV_*reorderst_outtabin place (triggered when the user sorts by a message column), so the same group is no longer adjacent, causingAT END OFto 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.APPENDoverwritessy-tabix— AfterAPPEND INITIAL LINE ... ASSIGNING <fs>,sy-tabixgives 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 KEYmisses, it setssy-tabixto 0 — SubsequentlyAPPEND sy-tabix TO lt_deladds it to the deletion list, andDELETE ... INDEX 0causes a direct DUMPTABLE_INVALID_INDEX. Save the index at the start of the loop and checkIF idx >= 1 AND idx <= lines( )before deleting. - Inline declaration
DATA(...)inside anIFbranch means the internal table is never created if the condition is false ⇒ subsequentLOOPcauses 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
xstructures must be passed as a pair with their original structures — Passingdepreciationareaswithoutdepreciationareasx⇒ if AFASL is filled, you getNo change parameter given for depreciation area 01. Missing thexstructure 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_CHANGEusesGET_CHANGEABLE_FIELDS, which only scrolls through fields whereWHERE rollname = 'CHAR1' OR 'CHAR01'; fields usingBAPIUPDATEare 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 toBAPIUPDATE. BAPIPAREX-VALUEPART1is only 240 characters — Standard functions readVALUEPART1~4contiguously by offset (total 960). If you only fill the first part, the tail is silently truncated (BAPE_VBAPtested 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 inBAPISDITMreferencesKWMENG; this item category has no configuration inTVEPZ⇒ no schedule line is generated, the passedBAPISCHDLis silently discarded, returns S butKWMENG=0,NETWR=0,VBEPhas 0 rows. The criterion is whetherTVEPZhas an entry, notTVAP-LFREL(bothZ001andZ014have emptyLFREL), norVBTYP. The same pitfall exists on the reading side:VBAP-KWMENGis always 0, the quantity is inVBAP-ZMENG⇒ the fill logic should be 'prioritizeKWMENGif not empty, fall back toZMENGif empty'. - The first field in
BAPE_*structures is the key; if omitted in modification mode, the document number is cleared — Standard functionsmove-correspondingtheEXTENSIONINvalue intoE_VBAKKOM. IfVBELNis left empty, it overwrites the document number with an empty value ⇒V1 045followed byV4 219(look at the E message beforeV4 219for 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 reportsV2 068 ...with&2empty. Filling the item number as 0 or a non-existent document produces the same message but with&3empty. The category comes from the referenced document itself, i.e.,VBAK-VBTYP, not the header'sVGTYP. SD_SALESDOCUMENT_CHANGErequires sorting for two of its tables — For deletion, bothITEM_INandITEM_INXmust be filled withITM_NUMBER; filling onlyITEM_INXcauses misalignment and reports a completely unrelated error:T184 No item category available.BAPI_USER_EXISTENCE_CHECKreturns 0 for any account in this system (evenZZZZZZ999passes) ⇒ the check is useless; documents transferred to invalid accounts are invisible from both UIs. Directly queryUSR02+ add length validation (0 < len <= 12).DELETE_TEXTRAISING NOT_FOUNDfor non-existent text — The standard function appends this E message toBAPIRET2, causingfrm_check_returnto flag it as a failure ⇒ the 'clear' operation actually causes the interface to return E. Before clearing, checkSTXHfirst (TDOBJECT+TDNAME+TDID+TDSPRAS); if it doesn't exist, skip the operation (this also makes the action idempotent). Also: inSD_SALES_DOCU_MAINTAIN, the function code for clearing is003(DELETE_TEXT); passing&&/005just rewrites the text as a single empty line.- When adding a pricing condition, do not specify the step number — Hardcoding
COND_ST_NO='001'whileZP01is atSTUNR=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 driveUPDATEFLAGasI/U. - When splitting an interface document a second time,
OBJ_KEYonly returns one document number ⇒ the count is understated, and writing back by line points to the wrong document. The layout ofOBJ_KEYisdocument 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-BUZEIisNUMC(3), with a physical maximum of 999 lines per document; BAPI actually only accepts 990 lines — The chunk limit is989 detail + 1 balancing line; boundaries must cover 999/1000/1997 (1997 checked as>999would yield 1000 lines and overflow).BAPIACHE09does not have anEXCH_RATEfield — 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). TheCURR_TYPE='10'lines must also carryexch_rate, both in the detail lines and the balancing line.- For dual-currency documents, each
ITEMNO_ACCmust have twoCURRENCYAMOUNTentries, and the subtotal for each currency type must be 0 — Missing the10line reportsInconsistent currency information; the balancing line generated by splitting must also be supplemented. UMSKZis 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 viaEXTENSION2enhancements, or the entire document can be processed viaPOSTING_INTERFACE_*(FB01 batch input).- The
NAMEparameter ofREAD_TEXTis typed asTDOBNAME(70), but the actual parameterTDNAMEis (16), causing a runtime type conflict — Compilation passes, empty input works fine, but as soon as data is hit, a 500Type conflict during a function module calloccurs. Type the parameter asTYPE tdobname, or directly read the cluster tableSTXL.
3.3 Text / Descriptions / Column Headers Becoming Empty
- Using an anonymous built-in type for a field ⇒
DD03Ttext is empty ⇒ ALV column headers are completely blank — The field must be linked to a data element (after linking,DD03Thaving 0 rows is the correct state, text is moved up). Structure field text can only be provided via DDL with@EndUserText.labelper field; nestedSTRU/TTYPtype fields never haveDD03Ttext (SAP standard structures are the same).CreateStructure'sfields[].descriptiondoes not enterDD03T. - Text elements are not activated / only exist as inactive version ⇒ runtime
TEXT-xxxis 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 includeAS4LOCAL='A'(otherwise you get inactive rows),ORDER BY VALPOS(otherwise you get lexicographic order like1,10,11,2,3…), and the language fallback to the domain's master languageDD01L-DOMMASTER(the field name is notMASTERLANG). MESSAGE ... WITHhas an upper limit of about 70 characters for the entire message text ⇒ Long BAPI errors are silently truncated to something likeField Reas..., misleading the investigation. Use a short prefix + string template with the original text (≤ 220 characters, aligned toBAPI_MSG).BAPIACGL09-ITEM_TEXT(50) /BAPIACHE09-HEADER_TXT(25) silently truncate on the right — When concatenating 'prefix+suffix', first truncate the prefix tolength - 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-CHECKusually results in no authorization ⇒ related records are permanently invisible — Roles typically do not grantEKGRP = 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 IDcontrols 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.ROLLBACKwill eat the input snapshot inindx=01along with it (thoughindx=02remains). Additionally, the underlying posting FM internally performsBAPI_TRANSACTION_ROLLBACK, so simply 'removing your own ROLLBACK' or 'changing the order' is ineffective. ⇒ Solution: Insert an independentCOMMIT WORKstatement just before the single database write action, and before calling the BAPI. This preserves the input data in the cluster even if a subsequentROLLBACKoccurs. 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_CHANGEwithCALL_FROM_BAPI='X'places the update into the update queue. Until committed, aSELECTread-back will always incorrectly conclude that 'the flag was not updated'. In a failure branch, aROLLBACK WORKmust 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_respare not visible on the platform; the monitor'sserializedoes not usename_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 singlesave( )call at the only point where the message is generated (end offrm_send). An independent commit here exactly compensates for the platform's weakness of losing logs during rollbacks.
5. Popups / F4 / ALV / Selection Screens
- The
RETURNCODEsemantics ofPOPUP_GET_VALUESare counter-intuitive —'A'= Cancel; Space = Confirm andFIELDShave been filled back (LSPO4U01:IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). WritingIF 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_VALUESonly triggers F4 if the field is connected to a DDIC search help — The criterion isF4IF_DETERMINE_SEARCHHELP(0=has help / 2=field exists but no help / 1=field does not exist). Do not change the type toSKA1-SAKNRto borrow its help — this introduces ALPHA zero-padding:'8000'becomes'000008000', causing validation errors and incorrect persistence format. ⇒ Instead, usePOPUP_GET_VALUES_USER_HELPwithF4_FORMNAME+F4_PROGRAMNAME = sy-repid, keeping the field attributes unchanged. The exit FORM is called by the FM with 5USINGparameters (tabname/fieldname/display_flag/returncode/value). Write the selected value back into the 5th parameter and setreturncodeto'F'to refresh the popup (sy-steplis not available here, so do not try to fill via dynprofield).POPUP_GET_VALUESdoes not support hiding fields based on conditions —SVALcan only controlFIELD_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_TABtoF4IF_INT_TABLE_VALUE_REQUEST, each row must be able to deriveINTTYPE— The derivation chain isTABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME...