*&---------------------------------------------------------------------* *& Report Z_ERPFORGE_00_QUICKSCAN *&---------------------------------------------------------------------* *& ERPForgeAI - Step 0: One-Click Quick Scan (First Contact) *& *& -- GOVERNANCE (read in under a minute) ------------------------------ *& READS : Release, kernel, SP-level + quick triage of repository *& size and ABAP feature support (CVERS, dictionary counts). *& DOES NOT : No UPDATE/INSERT/MODIFY/DELETE on DB tables (read-only). *& No RFC, no DESTINATION, no network/internet. No server-side *& file write. No business data / table rows / PII is read. *& Output is SAP METADATA only (structure, not contents) - *& nothing leaves your system unless YOU export it. *& FOOTPRINT: Transient analysis report - delete after extraction. *& SOURCE : MIT-licensed; public source + SHA-256 checksum at *& https://erpforgeai.de/sap-tools.html *& --------------------------------------------------------------------- *& *& VERSION: v0.2 *& v0.1 -> v0.2 (after stress-testing script 04 on system ): *& - Section 3 (DD_FIELDS): replaced LOOP+SELECT-SINGLE x 2 anti-pattern *& with three bounded queries (DD03L + FOR ALL ENTRIES on DD04T/DD01T). *& v0.1 would TIME_OUT on stressed DBs with N+1 query syndrome. *& - Section 3: added s_tab empty guard + 50000-row cap. v0.1 had no *& protection against unbounded SELECT into DD03L. *& - Section 4 (TADIR_OBJ): aggregation moved to DB via GROUP BY *& object,author. v0.1 pulled all rows into ABAP and aggregated *& client-side -> TSV_TNEW_PAGE_ALLOC_FAILED on multi-million-row *& TADIR. Section now defaults OFF; opt-in via P_TADIR checkbox *& because even with DB aggregation it can be slow on huge TADIRs. *& - Section 5 (NACE_KAPPL): GROUP BY + COUNT(DISTINCT) on DB. *& *& PURPOSE *& Single-shot system profile for the FIRST customer interaction. *& Customer effort: SE38 -> F8 -> Save -> email the TXT. *& No parameters to fill in. No multiple uploads. No second run needed. *& *& This script duplicates a curated subset of Z_ERPFORGE_01..04 logic *& on purpose - so the customer can run ONE thing, send ONE file, and *& get a meaningful scoping conversation started. *& *& WHAT IT EXPORTS (one tab-delimited TXT, sectioned by "## SECTION X"): *& 1. SYSTEM - Release, kernel, DB, OS, components (CVERS top hits) *& 2. ABAP_FEAT - Which 7.40+ features compile here (dynamic check) *& 3. DD_FIELDS - Field-level metadata for 10 high-value tables *& (STXH, STXL, STXFADM, TADIR, TNAPR, NAST, *& T685, T685A, DD30L, DD30T) *& 4. TADIR_OBJ - Object-type histogram (OPT-IN; can be slow) *& 5. NACE_KAPPL - Configured output-determination applications *& *& WHAT IT DOES NOT DO (use the dedicated scripts for these): *& - Full repository inventory -> Z_ERPFORGE_02_EXPORT *& - Forms inventory -> Z_ERPFORGE_03_FORMS_INVENTORY *& - Wide DD-Steckbrief -> Z_ERPFORGE_04_DD_DUMP *& *& Compatible: SAP_BASIS 7.40 SP02+ (verified on 7.50 SP34, system ) *&---------------------------------------------------------------------* REPORT z_erpforge_00_quickscan. CONSTANTS: gc_tab TYPE c VALUE cl_abap_char_utilities=>horizontal_tab. DATA: gt_lines TYPE TABLE OF string. DATA: gv_line TYPE string. SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE gt_b01. SELECTION-SCREEN COMMENT /1(70) c01. SELECTION-SCREEN COMMENT /1(70) c02. SELECTION-SCREEN END OF BLOCK b01. SELECTION-SCREEN BEGIN OF BLOCK b02 WITH FRAME TITLE gt_b02. SELECTION-SCREEN BEGIN OF LINE. PARAMETERS p_tadir AS CHECKBOX DEFAULT ' '. SELECTION-SCREEN COMMENT 3(60) c_tadir. SELECTION-SCREEN END OF LINE. SELECTION-SCREEN END OF BLOCK b02. INITIALIZATION. gt_b01 = 'ERPForgeAI Quick Scan'. gt_b02 = 'Optional'. c01 = 'Klicken Sie auf Ausfuehren (F8). Der Scan dauert <1 Minute.'. c02 = 'Senden Sie die erzeugte TXT-Datei an Ihren ERPForgeAI-Berater.'. c_tadir = 'TADIR-Histogramm einschliessen (langsam auf grossen Systemen)'. *======================================================================* START-OF-SELECTION. PERFORM write_header. PERFORM section_1_system. PERFORM section_2_abap_features. PERFORM section_3_dd_fields. IF p_tadir = 'X'. PERFORM section_4_tadir_objects. ENDIF. PERFORM section_5_nace_kappl. PERFORM save_file. *&---------------------------------------------------------------------* FORM write_header. APPEND |================================================================| TO gt_lines. APPEND | ERPForgeAI Quick Scan| TO gt_lines. APPEND | System: { sy-sysid } / Mandant: { sy-mandt } / Host: { sy-host }| TO gt_lines. APPEND | Erstellt: { sy-datum } { sy-uzeit } / User: { sy-uname }| TO gt_lines. APPEND |================================================================| TO gt_lines. APPEND |' '| TO gt_lines. ENDFORM. *&---------------------------------------------------------------------* *& Section 1: System info from CVERS + sy-* fields *&---------------------------------------------------------------------* FORM section_1_system. APPEND '## SECTION 1 - SYSTEM' TO gt_lines. " sy-* gives us release/host/etc. without a SELECT APPEND |SAP_RELEASE{ gc_tab }{ sy-saprl }| TO gt_lines. APPEND |HOST{ gc_tab }{ sy-host }| TO gt_lines. APPEND |MANDT{ gc_tab }{ sy-mandt }| TO gt_lines. APPEND |LANGU{ gc_tab }{ sy-langu }| TO gt_lines. APPEND |OPSYS{ gc_tab }{ sy-opsys }| TO gt_lines. APPEND |DBSYS{ gc_tab }{ sy-dbsys }| TO gt_lines. " Top components (limit to most-relevant for fast scoping) DATA: BEGIN OF ls_cvers, component TYPE cvers-component, release TYPE cvers-release, extrelease TYPE cvers-extrelease, END OF ls_cvers, lt_cvers LIKE TABLE OF ls_cvers. SELECT component release extrelease FROM cvers INTO TABLE lt_cvers WHERE component IN ('SAP_BASIS','SAP_ABA','SAP_APPL','SAP_HR','SAP_UI', 'IS-UT','IS-OIL','IS-H','IS-M','IS-PRA', 'FI-CA','FI-CAX','IDEXDE','IDEXGM', 'EA-FINSERV','INSURANCE','ERECRUIT', 'MDG_FND','MDG_APPL'). LOOP AT lt_cvers INTO ls_cvers. APPEND |COMPONENT{ gc_tab }{ ls_cvers-component }{ gc_tab }{ ls_cvers-release }{ gc_tab }{ ls_cvers-extrelease }| TO gt_lines. ENDLOOP. APPEND |' '| TO gt_lines. ENDFORM. *&---------------------------------------------------------------------* *& Section 2: ABAP language features via dynamic SYNTAX-CHECK *&---------------------------------------------------------------------* FORM section_2_abap_features. APPEND '## SECTION 2 - ABAP_FEAT' TO gt_lines. PERFORM probe_feature USING 'INLINE_DATA' 'DATA(lv) = 1.'. PERFORM probe_feature USING 'STRING_TEMPLATE' '|test|.'. PERFORM probe_feature USING 'COND_EXPR' 'DATA(x) = COND i( WHEN 1 = 1 THEN 1 ELSE 0 ).'. PERFORM probe_feature USING 'VALUE_OP' 'DATA(t) = VALUE string_table( ( |a| ) ).'. PERFORM probe_feature USING 'FOR_EXPR' 'DATA(s) = REDUCE i( INIT n = 0 FOR i = 1 UNTIL i > 3 NEXT n = n + i ).'. APPEND |' '| TO gt_lines. ENDFORM. FORM probe_feature USING uv_name TYPE string uv_snippet TYPE string. DATA: lt_src TYPE TABLE OF string. DATA: lv_msg TYPE string. DATA: lv_word TYPE string. DATA: lv_line TYPE i. DATA: lv_word2 TYPE string. " Receiver for the PROGRAM addition. Newer SAP_BASIS releases REQUIRE " either PROGRAM or DIRECTORY ENTRY on SYNTAX-CHECK FOR itab — otherwise " activation raises "Mindestens einer der Zusaetze PROGRAM oder " DIRECTORY ENTRY muss angegeben werden". The variable receives the name " of the temporarily generated probe program; we don't use the value. DATA: lv_prog TYPE sy-repid. APPEND |REPORT zerpforge_probe.| TO lt_src. APPEND |START-OF-SELECTION.| TO lt_src. APPEND uv_snippet TO lt_src. SYNTAX-CHECK FOR lt_src MESSAGE lv_msg LINE lv_line WORD lv_word PROGRAM lv_prog. IF lv_msg IS INITIAL. APPEND |FEATURE{ gc_tab }{ uv_name }{ gc_tab }JA| TO gt_lines. ELSE. APPEND |FEATURE{ gc_tab }{ uv_name }{ gc_tab }NEIN| TO gt_lines. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Section 3: DD field metadata for 10 high-value tables *& v0.2: bounded-query pattern instead of LOOP+SELECT-SINGLE x 2. *& Hard-coded table list (no user input) + 50000-row safety cap. *&---------------------------------------------------------------------* FORM section_3_dd_fields. CONSTANTS: lc_max_fields TYPE i VALUE 50000. APPEND '## SECTION 3 - DD_FIELDS' TO gt_lines. APPEND |TABNAME{ gc_tab }FIELDNAME{ gc_tab }POSITION{ gc_tab }DATATYPE{ gc_tab }LENG{ gc_tab }DECIMALS{ gc_tab }ROLLNAME{ gc_tab }KEYFLAG{ gc_tab }DDTEXT| TO gt_lines. TYPES: BEGIN OF ty_f, tabname TYPE dd03l-tabname, fieldname TYPE dd03l-fieldname, position TYPE dd03l-position, datatype TYPE dd03l-datatype, leng TYPE dd03l-leng, decimals TYPE dd03l-decimals, rollname TYPE dd03l-rollname, domname TYPE dd03l-domname, keyflag TYPE dd03l-keyflag, ddtext TYPE dd04t-ddtext, END OF ty_f. DATA: lt_f TYPE TABLE OF ty_f. DATA: ls_f TYPE ty_f. " Step 1: pull the DD03L rows for the 10 fixed tables. SELECT tabname fieldname position datatype leng decimals rollname domname keyflag FROM dd03l INTO CORRESPONDING FIELDS OF TABLE lt_f UP TO lc_max_fields ROWS WHERE tabname IN ('STXH','STXL','STXFADM','TADIR','TNAPR', 'NAST','T685','T685A','DD30L','DD30T') AND as4local = 'A' AND fieldname NOT LIKE '.%' AND fieldname NOT LIKE '/%' ORDER BY tabname position. IF lt_f IS INITIAL. APPEND '(no DD03L rows returned for the 10 default tables)' TO gt_lines. APPEND |' '| TO gt_lines. RETURN. ENDIF. IF lines( lt_f ) >= lc_max_fields. APPEND |(WARNING: hit { lc_max_fields }-row cap. Output truncated.)| TO gt_lines. ENDIF. " Step 2: collect distinct rollnames and domnames. TYPES: BEGIN OF ty_roll_key, rollname TYPE dd03l-rollname, END OF ty_roll_key. TYPES: BEGIN OF ty_dom_key, domname TYPE dd03l-domname, END OF ty_dom_key. DATA: lt_rolls TYPE TABLE OF ty_roll_key, lt_doms TYPE TABLE OF ty_dom_key. DATA: ls_roll TYPE ty_roll_key, ls_dom TYPE ty_dom_key. LOOP AT lt_f ASSIGNING FIELD-SYMBOL(). IF -rollname IS NOT INITIAL. ls_roll-rollname = -rollname. APPEND ls_roll TO lt_rolls. ENDIF. IF -domname IS NOT INITIAL. ls_dom-domname = -domname. APPEND ls_dom TO lt_doms. ENDIF. ENDLOOP. SORT lt_rolls. DELETE ADJACENT DUPLICATES FROM lt_rolls. SORT lt_doms. DELETE ADJACENT DUPLICATES FROM lt_doms. " Step 3: batched lookup for rollname descriptions (dd04t). TYPES: BEGIN OF ty_dd04t, rollname TYPE dd04t-rollname, ddtext TYPE dd04t-ddtext, END OF ty_dd04t. DATA: lt_dd04t TYPE TABLE OF ty_dd04t. IF lt_rolls IS NOT INITIAL. SELECT rollname ddtext FROM dd04t INTO TABLE lt_dd04t FOR ALL ENTRIES IN lt_rolls WHERE rollname = lt_rolls-rollname AND ddlanguage = sy-langu AND as4local = 'A'. SORT lt_dd04t BY rollname. ENDIF. " Step 4: batched lookup for domain descriptions (dd01t). TYPES: BEGIN OF ty_dd01t, domname TYPE dd01t-domname, ddtext TYPE dd01t-ddtext, END OF ty_dd01t. DATA: lt_dd01t TYPE TABLE OF ty_dd01t. IF lt_doms IS NOT INITIAL. SELECT domname ddtext FROM dd01t INTO TABLE lt_dd01t FOR ALL ENTRIES IN lt_doms WHERE domname = lt_doms-domname AND ddlanguage = sy-langu AND as4local = 'A'. SORT lt_dd01t BY domname. ENDIF. " Step 5: merge descriptions back, applying rollname-first precedence, " then write the output rows. DATA: ls_dd04t TYPE ty_dd04t, ls_dd01t TYPE ty_dd01t. LOOP AT lt_f INTO ls_f. IF ls_f-rollname IS NOT INITIAL. READ TABLE lt_dd04t INTO ls_dd04t WITH KEY rollname = ls_f-rollname BINARY SEARCH. IF sy-subrc = 0. ls_f-ddtext = ls_dd04t-ddtext. ENDIF. ENDIF. IF ls_f-ddtext IS INITIAL AND ls_f-domname IS NOT INITIAL. READ TABLE lt_dd01t INTO ls_dd01t WITH KEY domname = ls_f-domname BINARY SEARCH. IF sy-subrc = 0. ls_f-ddtext = ls_dd01t-ddtext. ENDIF. ENDIF. APPEND |{ ls_f-tabname }{ gc_tab }{ ls_f-fieldname }{ gc_tab }{ ls_f-position }{ gc_tab }{ ls_f-datatype }{ gc_tab }{ ls_f-leng }{ gc_tab }{ ls_f-decimals }{ gc_tab }{ ls_f-rollname }{ gc_tab }{ ls_f-keyflag }{ gc_tab }{ ls_f-ddtext }| TO gt_lines. ENDLOOP. APPEND |' '| TO gt_lines. ENDFORM. *&---------------------------------------------------------------------* *& Section 4: TADIR object-type histogram *& v0.2: aggregation pushed to DB via GROUP BY object, author. *& Classification (SAP vs customer) happens in ABAP on the small *& grouped result set (~few thousand rows) instead of pulling *& millions of TADIR rows into memory. *& OPT-IN via P_TADIR — even with DB aggregation, can be slow. *&---------------------------------------------------------------------* FORM section_4_tadir_objects. APPEND '## SECTION 4 - TADIR_OBJ' TO gt_lines. APPEND |OBJECT{ gc_tab }TOTAL{ gc_tab }SAP{ gc_tab }CUSTOMER| TO gt_lines. TYPES: BEGIN OF ty_grp, object TYPE tadir-object, author TYPE tadir-author, cnt TYPE i, END OF ty_grp. DATA: lt_grp TYPE TABLE OF ty_grp. DATA: ls_grp TYPE ty_grp. SELECT object author COUNT(*) AS cnt FROM tadir INTO TABLE lt_grp WHERE pgmid = 'R3TR' GROUP BY object author. IF lt_grp IS INITIAL. APPEND '(no TADIR rows)' TO gt_lines. APPEND |' '| TO gt_lines. RETURN. ENDIF. TYPES: BEGIN OF ty_acc, object TYPE tadir-object, total TYPE i, sap_cnt TYPE i, cust_cnt TYPE i, END OF ty_acc. DATA: lt_acc TYPE TABLE OF ty_acc. DATA: ls_acc TYPE ty_acc. SORT lt_grp BY object. DATA: lv_prev TYPE tadir-object. CLEAR: ls_acc, lv_prev. LOOP AT lt_grp INTO ls_grp. IF ls_grp-object <> lv_prev AND lv_prev IS NOT INITIAL. ls_acc-total = ls_acc-sap_cnt + ls_acc-cust_cnt. APPEND ls_acc TO lt_acc. CLEAR ls_acc. ENDIF. ls_acc-object = ls_grp-object. IF ls_grp-author = 'SAP' OR ls_grp-author = 'DDIC' OR ls_grp-author CP 'SAP*'. ls_acc-sap_cnt = ls_acc-sap_cnt + ls_grp-cnt. ELSE. ls_acc-cust_cnt = ls_acc-cust_cnt + ls_grp-cnt. ENDIF. lv_prev = ls_grp-object. ENDLOOP. IF lv_prev IS NOT INITIAL. ls_acc-total = ls_acc-sap_cnt + ls_acc-cust_cnt. APPEND ls_acc TO lt_acc. ENDIF. SORT lt_acc BY total DESCENDING. LOOP AT lt_acc INTO ls_acc. APPEND |{ ls_acc-object }{ gc_tab }{ ls_acc-total }{ gc_tab }{ ls_acc-sap_cnt }{ gc_tab }{ ls_acc-cust_cnt }| TO gt_lines. ENDLOOP. APPEND |' '| TO gt_lines. ENDFORM. *&---------------------------------------------------------------------* *& Section 5: NACE KAPPL list *& v0.2: GROUP BY + COUNT(DISTINCT kschl) on the database side. *&---------------------------------------------------------------------* FORM section_5_nace_kappl. APPEND '## SECTION 5 - NACE_KAPPL' TO gt_lines. APPEND |KAPPL{ gc_tab }OUTPUT_TYPES| TO gt_lines. TYPES: BEGIN OF ty_k, kappl TYPE t685a-kappl, output_cnt TYPE i, END OF ty_k. DATA: lt_k TYPE TABLE OF ty_k. DATA: ls_k TYPE ty_k. SELECT kappl COUNT( DISTINCT kschl ) AS output_cnt FROM t685a INTO TABLE lt_k GROUP BY kappl. SORT lt_k BY output_cnt DESCENDING. LOOP AT lt_k INTO ls_k. APPEND |{ ls_k-kappl }{ gc_tab }{ ls_k-output_cnt }| TO gt_lines. ENDLOOP. APPEND |' '| TO gt_lines. ENDFORM. *&---------------------------------------------------------------------* *& Save file - one save dialog, deterministic filename *&---------------------------------------------------------------------* FORM save_file. DATA: lv_fp TYPE string, lv_fn TYPE string, lv_p TYPE string, lv_a TYPE i. cl_gui_frontend_services=>file_save_dialog( EXPORTING window_title = 'ERPForgeAI QuickScan speichern' default_extension = 'txt' default_file_name = |ERPForgeAI_QuickScan_{ sy-sysid }_{ sy-datum }| file_filter = 'Text (*.txt)|*.txt|All|*.*' CHANGING filename = lv_fn path = lv_p fullpath = lv_fp user_action = lv_a EXCEPTIONS OTHERS = 4 ). IF sy-subrc <> 0 OR lv_a <> 0. MESSAGE 'Speichern abgebrochen.' TYPE 'I'. RETURN. ENDIF. cl_gui_frontend_services=>gui_download( EXPORTING filename = lv_fp filetype = 'ASC' write_field_separator = ' ' trunc_trailing_blanks = 'X' codepage = '4110' CHANGING data_tab = gt_lines EXCEPTIONS OTHERS = 22 ). IF sy-subrc = 0. MESSAGE |QuickScan gespeichert: { lv_fp }| TYPE 'S'. ELSE. MESSAGE |Download-Fehler { sy-subrc }| TYPE 'E'. ENDIF. ENDFORM.