*&---------------------------------------------------------------------* *& Report ZMM_LIFECYCLE_CODE_INHERITANCE *&---------------------------------------------------------------------* *& Lebenszykluscode-Vererbung für Materialstammdaten *& Optimiert für große Stücklisten und Massendatenverarbeitung *&---------------------------------------------------------------------* REPORT zmm_lifecycle_code_inheritance. TYPES: ty_percentage TYPE p LENGTH 8 DECIMALS 2. " <-- NEUE ZEILE HINZUFÜGEN *----------------------------------------------------------------------* * Typdefinitionen *----------------------------------------------------------------------* TYPES: BEGIN OF ty_material, matnr TYPE matnr, mtart TYPE mtart, zzlzcod TYPE char4, zzlzcodsort TYPE char4, " NEU: Sortiments-Code zztyp_f4 TYPE char1, pstat TYPE pstat_d, mstae TYPE mstae, level TYPE i, disst TYPE dismm, END OF ty_material. TYPES: BEGIN OF ty_mattab, matnr TYPE matnr, mtart TYPE mtart, disst TYPE dismm, lzcod TYPE char4, lzcodsort TYPE char4, matnr_top TYPE matnr, menge TYPE stpo-menge, END OF ty_mattab. DATA: mattab TYPE STANDARD TABLE OF ty_mattab WITH HEADER LINE, mattab2 TYPE STANDARD TABLE OF ty_mattab WITH HEADER LINE, mattab_dummy TYPE ty_mattab, imara TYPE mara, stb TYPE STANDARD TABLE OF stpox, bg TYPE STANDARD TABLE OF cscmat, c_werks TYPE werks_d VALUE '1100'. TYPES: BEGIN OF ty_bom_relation, parent TYPE matnr, child TYPE matnr, menge TYPE stpo-menge, level TYPE i, END OF ty_bom_relation. TYPES: BEGIN OF ty_usage, matnr TYPE matnr, parent TYPE matnr, parent_code TYPE char4, usage_qty TYPE kmpmg, END OF ty_usage. TYPES: BEGIN OF ty_result, matnr TYPE matnr, old_code TYPE char4, new_code TYPE char4, old_code_sort TYPE char4, " NEU new_code_sort TYPE char4, " NEU changed_sort TYPE abap_bool, " NEU changed TYPE abap_bool, message TYPE string, END OF ty_result. TYPES: BEGIN OF ty_consumption, matnr TYPE matnr, menge TYPE menge_d, gsv01 TYPE mver-gsv01, " NEU: Originalverbrauch aus VERBRAUCH_SUMMIEREN gsv_korr TYPE mver-gsv01, " NEU: Korrigierter Verbrauch nach MSEG-Logik END OF ty_consumption. * Hash-Tabellen für Performance TYPES: tt_material_hash TYPE HASHED TABLE OF ty_material WITH UNIQUE KEY matnr. TYPES: tt_bom_hash TYPE HASHED TABLE OF ty_bom_relation WITH UNIQUE KEY parent child. TYPES: tt_usage_std TYPE STANDARD TABLE OF ty_usage. TYPES: tt_result TYPE STANDARD TABLE OF ty_result. *----------------------------------------------------------------------* * Konstanten *----------------------------------------------------------------------* CONSTANTS: gc_max_hierarchy_level TYPE i VALUE 4, gc_batch_size TYPE i VALUE 5000, gc_max_iterations TYPE i VALUE 10, gc_code_initial TYPE char4 VALUE 'ZZZZ', gc_code_normal TYPE char1 VALUE 'N', gc_code_auslauf TYPE char1 VALUE 'A', gc_code_ersatz TYPE char1 VALUE 'E', gc_code_sonder TYPE char1 VALUE 'S'. *----------------------------------------------------------------------* * Globale Datendeklarationen *----------------------------------------------------------------------* DATA: gt_materials TYPE tt_material_hash, gt_bom_relations TYPE tt_bom_hash, gt_usages TYPE tt_usage_std, gt_results TYPE tt_result, gt_consumption TYPE HASHED TABLE OF ty_consumption WITH UNIQUE KEY matnr. DATA: p_mm TYPE abap_bool, p_di TYPE abap_bool, p_gozin TYPE abap_bool. TABLES mara. *----------------------------------------------------------------------* * Selektionsbildschirm *----------------------------------------------------------------------* SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001. SELECT-OPTIONS: s_matnr FOR mara-matnr, s_typcd FOR mara-mtart. PARAMETERS: "p_alles TYPE abap_bool AS CHECKBOX DEFAULT '', " p_gozin TYPE abap_bool AS CHECKBOX DEFAULT '', p_upda TYPE abap_bool AS CHECKBOX DEFAULT 'X', p_test TYPE abap_bool AS CHECKBOX DEFAULT ' '. SELECTION-SCREEN END OF BLOCK b1. * *SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-002. * PARAMETERS: p_batch TYPE i DEFAULT 5000, * p_maxlv TYPE i DEFAULT 4. *SELECTION-SCREEN END OF BLOCK b2. * HIER NEUEN BLOCK EINFÜGEN SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-003. PARAMETERS: p_lzc TYPE abap_bool RADIOBUTTON GROUP mode , "Lebenszykluscode p_sort TYPE abap_bool RADIOBUTTON GROUP mode DEFAULT 'X'. "Sortimentscode SELECTION-SCREEN END OF BLOCK b3. * SELECTION-SCREEN BEGIN OF BLOCK b4 WITH FRAME TITLE TEXT-004. * PARAMETERS: p_mm TYPE abap_bool RADIOBUTTON GROUP moe2 , "Lebenszykluscode * p_di TYPE abap_bool RADIOBUTTON GROUP moe2 DEFAULT 'X'. "Sortimentscode * SELECTION-SCREEN END OF BLOCK b4. *----------------------------------------------------------------------* * Klassendeklaration für Hauptlogik *----------------------------------------------------------------------* CLASS lcl_lifecycle_processor DEFINITION. PUBLIC SECTION. METHODS: constructor, execute, get_code_percentage IMPORTING iv_code TYPE char4 RETURNING VALUE(rv_percentage) TYPE ty_percentage , display_gozinto_graph IMPORTING iv_top_material TYPE matnr, display_results. PRIVATE SECTION. " ==================== TYPDEFINITIONEN ==================== " Node-Struktur für Gozinto-Graph TYPES: BEGIN OF ty_node, node_key TYPE matnr, parent_key TYPE matnr, matnr TYPE matnr, menge TYPE kmpmg, zzlzcod TYPE char4, level TYPE i, zzlzcodsort TYPE char4, verbrauch_text TYPE string, END OF ty_node. TYPES: tt_nodes TYPE STANDARD TABLE OF ty_node WITH EMPTY KEY. " Material-Level für hierarchische Sortierung TYPES: BEGIN OF ty_material_level, matnr TYPE matnr, level TYPE i, END OF ty_material_level. TYPES: tt_material_level TYPE STANDARD TABLE OF ty_material_level WITH NON-UNIQUE DEFAULT KEY. " ==================== DATENDEKLARATIONEN ==================== " BDC-Daten für MM02-Transaktion DATA: gt_bdcdata TYPE TABLE OF bdcdata, gt_bdcmsg TYPE TABLE OF bdcmsgcoll. " Progress-Indicator (optional) DATA: mo_progress TYPE REF TO cl_progress_indicator. " ==================== METHODENDEKLARATIONEN ==================== " === HAUPTMETHODEN === METHODS: load_materials, load_consumption_data, calculate_inheritance, calculate_sortiment_inhe, update_database, update_database_sortiment. " === HIERARCHIE-METHODEN === METHODS: build_hierarchy_from_stb IMPORTING iv_top_material TYPE matnr OPTIONAL, transfer_to_new_structures IMPORTING iv_top_material TYPE matnr OPTIONAL, build_graph_nodes IMPORTING iv_matnr TYPE matnr iv_parent_key TYPE matnr iv_menge TYPE kmpmg iv_level TYPE i CHANGING ct_nodes TYPE tt_nodes. " === VERWENDUNGS-METHODEN === METHODS: load_usages_for_components . " === BERECHNUNGS-METHODEN === METHODS: apply_dominance_rules IMPORTING iv_matnr TYPE matnr RETURNING VALUE(rv_code) TYPE char4, apply_sortiment_rules IMPORTING iv_matnr TYPE matnr RETURNING VALUE(rv_code) TYPE char4, calculate_sonder_percentage IMPORTING iv_matnr TYPE matnr iv_type TYPE char2 OPTIONAL RETURNING VALUE(rv_percentage) TYPE ty_percentage. " === STÜCKLISTEN-METHODEN === METHODS: read_bill_of_material IMPORTING iv_matnr TYPE matnr, " === DATENBANK-UPDATE-METHODEN === bdc_dynpro IMPORTING iv_program TYPE bdc_prog iv_dynpro TYPE bdc_dynr, bdc_field IMPORTING iv_fnam TYPE fnam_____4 iv_fval TYPE bdc_fval, bdc_transaction IMPORTING iv_tcode TYPE tcode, Write_to_journal IMPORTING iv_matnr TYPE matnr iv_old_lzcod TYPE char4 iv_new_lzcod TYPE char4 iv_old_lzcodsort TYPE char4 OPTIONAL iv_new_lzcodsort TYPE char4 OPTIONAL. " === DEBUG-METHODEN === METHODS: debug_show_relations, is_debug_material IMPORTING iv_matnr TYPE matnr RETURNING VALUE(rv_debug) TYPE abap_bool, debug_problem_material IMPORTING iv_matnr TYPE matnr iv_phase TYPE string, debug_collect_info IMPORTING iv_matnr TYPE matnr RETURNING VALUE(rt_info) TYPE string_table. " === HILFS-METHODEN === ENDCLASS. *----------------------------------------------------------------------* * Klassenimplementierung *----------------------------------------------------------------------* CLASS lcl_lifecycle_processor IMPLEMENTATION. METHOD constructor. "CREATE OBJECT mo_progress. ENDMETHOD. METHOD execute. DATA: lv_start_time TYPE timestampl, lv_end_time TYPE timestampl. GET TIME STAMP FIELD lv_start_time. " *** NEU: Job deaktivieren (nur produktiv) ***" PERFORM deactivate_job. " Phase 1: Datenladen" ""Write: / 'Phase 1: Lade Materialstammdaten...'. load_materials( ). " ===== HIER EINFÜGEN: Debug nach dem Laden =====" me->debug_show_relations( ). ""Write: / 'Phase 2: Lade Stücklistenbeziehungen...'. ""Write: / 'Phase 3: Erweitere Hierarchie...'. "expand_hierarchy( ). ""Write: / 'Phase 4: Lade Verbrauchsdaten...'. load_consumption_data( ). " Phase 5: Berechnungen basierend auf Auswahl durchführen" IF p_lzc = abap_true. " Modus: Nur Lebenszykluscode (ZZLZCOD)" ""Write: / 'Phase 5: Berechne Lebenszykluscode-Vererbung (ZZLZCOD)...'. PERFORM save_vknr_codes. calculate_inheritance( ). PERFORM restore_vknr_codes. " PERFORM protect_vknr_from_update USING '1'." IF p_upda = abap_true AND p_test = abap_false. ""Write: / 'Phase 6: Aktualisiere Datenbank für ZZLZCOD...'. update_database( ). ENDIF. ELSEIF p_sort = abap_true. " Modus: Nur Sortimentscode (ZZLZCODSORT)" ""Write: / 'Phase 5: Berechne Sortiments-Lebenszykluscode-Vererbung (ZZLZCODSORT)...'. calculate_sortiment_inhe( ). IF p_upda = abap_true AND p_test = abap_false. ""Write: / 'Phase 6: Aktualisiere Datenbank für ZZLZCODSORT...'. update_database_sortiment( ). ENDIF. ENDIF. GET TIME STAMP FIELD lv_end_time. DATA(lv_runtime) = lv_end_time - lv_start_time. ""Write: / |Gesamtlaufzeit: { lv_runtime } Sekunden|. " *** NEU: Job reaktivieren (nur produktiv) ***" PERFORM reactivate_job. ENDMETHOD. METHOD transfer_to_new_structures. " Parameter-Deklaration in der Klassendeklaration hinzufügen: " IMPORTING iv_top_material TYPE matnr OPTIONAL DATA: lv_material_count TYPE i, lv_relation_count TYPE i. ""Write: / '=== START transfer_to_new_structures ==='. " Zeige welches TOP-Material übergeben wurde IF iv_top_material IS NOT INITIAL. ""Write: / 'TOP-Material aus Parameter:', iv_top_material. ELSE. ""Write: / 'WARNUNG: Kein TOP-Material übergeben!'. ENDIF. " Schritt 1: Baue Hierarchie mit dem KORREKTEN TOP-Material me->build_hierarchy_from_stb( iv_top_material = iv_top_material ). " Validierung lv_material_count = lines( gt_materials ). lv_relation_count = lines( gt_bom_relations ). ""Write: / 'Materialien in gt_materials:', lv_material_count. ""Write: / 'Beziehungen in gt_bom_relations:', lv_relation_count. ""Write: / '=== ENDE transfer_to_new_structures ==='. ENDMETHOD. METHOD debug_show_relations. DATA: lt_sorted TYPE STANDARD TABLE OF ty_bom_relation, lv_current_child TYPE matnr, lv_parent_count TYPE i, lt_parent_codes TYPE STANDARD TABLE OF char4. ""Write: / ''. ""Write: / '╔══════════════════════════════════════════════════╗'. ""Write: / '║ VOLLSTÄNDIGE PARENT-CHILD ANALYSE ║'. ""Write: / '╚══════════════════════════════════════════════════╝'. lt_sorted = gt_bom_relations. SORT lt_sorted BY child parent. " Fokus auf die kritischen Materialien DATA: lt_critical_mats TYPE STANDARD TABLE OF matnr. APPEND 'AR13025' TO lt_critical_mats. APPEND 'AR13026' TO lt_critical_mats. APPEND 'AR15117' TO lt_critical_mats. LOOP AT lt_critical_mats INTO DATA(lv_critical_mat). ""Write: / ''. ""Write: / '▶▶▶ MATERIAL:', lv_critical_mat. CLEAR: lv_parent_count, lt_parent_codes. LOOP AT lt_sorted INTO DATA(ls_rel) WHERE child = lv_critical_mat. ADD 1 TO lv_parent_count. READ TABLE gt_materials WITH KEY matnr = ls_rel-parent INTO DATA(ls_parent). READ TABLE gt_consumption WITH KEY matnr = ls_rel-parent INTO DATA(ls_cons). IF sy-subrc = 0. ""Write: / ' ← Parent:', ls_rel-parent, "'Code:', ls_parent-zzlzcod, "'Menge:', ls_rel-menge, "'Verbrauch:', ls_cons-gsv_korr. ELSE. ""Write: / ' ← Parent:', ls_rel-parent, "'Code:', ls_parent-zzlzcod, "'Menge:', ls_rel-menge, "'Verbrauch: 0'. ENDIF. APPEND ls_parent-zzlzcod TO lt_parent_codes. ENDLOOP. ""Write: / ' └─ Anzahl Parents:', lv_parent_count. " Erwartete Berechnung IF lv_critical_mat = 'AR13025'. ""Write: / ' ERWARTUNG: Sollte A8T0 werden (mit 46781, 47187 als Parents)'. ELSEIF lv_critical_mat = 'AR15117'. ""Write: / ' ERWARTUNG: Sollte A3T0 werden (mit 46781, 47187, 48006)'. ENDIF. ENDLOOP. ""Write: / '══════════════════════════════════════════════════'. ENDMETHOD. METHOD read_bill_of_material. ENDMETHOD. METHOD load_materials. DATA: lt_selected_mats TYPE STANDARD TABLE OF ty_material, lt_all_relations TYPE STANDARD TABLE OF ty_bom_relation. " 1. Lade selektierte Materialien inkl. Status SELECT matnr, mtart, zzlzcod, zzlzcodsort, zztyp_f4, pstat, disst, mstae FROM mara INTO CORRESPONDING FIELDS OF TABLE @lt_selected_mats WHERE matnr IN @s_matnr AND mtart IN @s_typcd. " ======================================== " *** NEU: STATUS 99 BEHANDLUNG *** " ======================================== DATA: lv_status99_count TYPE i. LOOP AT lt_selected_mats ASSIGNING FIELD-SYMBOL(). IF -mstae = '99'. " Status 99 = Ausgelaufen → ZZLZCODSORT leeren -zzlzcodsort = ''. ADD 1 TO lv_status99_count. ENDIF. ENDLOOP. IF lv_status99_count > 0. WRITE: / 'Status 99 (Ausgelaufen):', lv_status99_count, 'Materialien → ZZLZCODSORT geleert'. ENDIF. " ======================================== " *** FILTER NUR TOP-MATERIALIEN *** " ======================================== DATA: lt_top_mats TYPE STANDARD TABLE OF ty_material, lv_filtered_mats TYPE i. LOOP AT lt_selected_mats INTO DATA(ls_mat_check). " Prüfe: Existiert Material als KOMPNR (Kind) in ZPOWERBI_VC? SELECT COUNT(*) FROM zpowerbi_vc INTO @DATA(lv_child_count) WHERE kompnr = @ls_mat_check-matnr. IF lv_child_count = 0. " Material hat KEINE Parents → IST TOP-Material APPEND ls_mat_check TO lt_top_mats. ELSE. " Material hat Parents → AUSSCHLIESSEN ADD 1 TO lv_filtered_mats. ENDIF. ENDLOOP. " *** Ersetze lt_selected_mats durch lt_top_mats *** CLEAR lt_selected_mats. lt_selected_mats = lt_top_mats. " 2. Füge zu gt_materials hinzu LOOP AT lt_selected_mats INTO DATA(ls_mat). INSERT ls_mat INTO TABLE gt_materials. ENDLOOP. " 3. Für jedes Material: Lade Stückliste UND Verwendungen LOOP AT lt_selected_mats INTO ls_mat. " 3a. Top-Down: Stückliste lesen me->read_bill_of_material( iv_matnr = ls_mat-matnr ). me->build_hierarchy_from_stb( iv_top_material = ls_mat-matnr ). CLEAR: stb, bg. me->debug_problem_material( iv_matnr = ls_mat-matnr iv_phase = 'LOAD' ). ENDLOOP. me->load_usages_for_components( ). ENDMETHOD. METHOD load_usages_for_components. DATA: lt_all_components TYPE SORTED TABLE OF matnr WITH UNIQUE KEY table_line, lt_vc_raw TYPE STANDARD TABLE OF zpowerbi_vc, ls_bom_rel TYPE ty_bom_relation, ls_material TYPE ty_material, lv_added_count TYPE i, lv_vc_count TYPE i. " ======================================== " SCHRITT 1: SAMMLE ALLE KOMPONENTEN " ======================================== LOOP AT gt_bom_relations INTO DATA(ls_rel). INSERT ls_rel-child INTO TABLE lt_all_components. ENDLOOP. " ======================================== " SCHRITT 2: FÜR JEDE KOMPONENTE - BOTTOM-UP VIA ZPOWERBI_VC " ======================================== LOOP AT lt_all_components INTO DATA(lv_component). CLEAR lt_vc_raw. SELECT matnr, kompnr, menge, stufe FROM zpowerbi_vc INTO CORRESPONDING FIELDS OF TABLE @lt_vc_raw WHERE kompnr = @lv_component. IF sy-subrc = 0. lv_vc_count = lv_vc_count + lines( lt_vc_raw ). ENDIF. " ======================================== " SCHRITT 3: VERARBEITE JEDE VERWENDUNG " ======================================== LOOP AT lt_vc_raw INTO DATA(ls_vc_raw). READ TABLE gt_bom_relations WITH KEY parent = ls_vc_raw-matnr child = lv_component TRANSPORTING NO FIELDS. IF sy-subrc <> 0. CLEAR ls_bom_rel. ls_bom_rel-parent = ls_vc_raw-matnr. ls_bom_rel-child = lv_component. DATA(lv_menge_char) = ls_vc_raw-menge. DATA(lv_menge_numeric) = CONV menge_d( lv_menge_char ). ls_bom_rel-menge = lv_menge_numeric. DATA(lv_stufe_char) = ls_vc_raw-stufe. DATA(lv_stufe_int) = CONV i( lv_stufe_char ). ls_bom_rel-level = lv_stufe_int. INSERT ls_bom_rel INTO TABLE gt_bom_relations. ADD 1 TO lv_added_count. " Parent-Material zu gt_materials hinzufügen falls unbekannt READ TABLE gt_materials WITH KEY matnr = ls_bom_rel-parent TRANSPORTING NO FIELDS. IF sy-subrc <> 0. CLEAR ls_material. SELECT SINGLE matnr, mtart, zzlzcod, zzlzcodsort, zztyp_f4, pstat, disst, mstae FROM mara INTO CORRESPONDING FIELDS OF @ls_material WHERE matnr = @ls_bom_rel-parent. IF sy-subrc = 0. " *** NEU: Status 99 prüfen *** IF ls_material-mstae = '99'. ls_material-zzlzcodsort = ''. ENDIF. INSERT ls_material INTO TABLE gt_materials. ENDIF. ENDIF. ENDIF. ENDLOOP. ENDLOOP. ENDMETHOD. METHOD load_consumption_data. DATA: w_datum_von LIKE sy-datum, w_datum_bis LIKE sy-datum, w_mblnr_von TYPE mkpf-mblnr, w_mblnr_bis TYPE mkpf-mblnr, lt_all_materials TYPE SORTED TABLE OF matnr WITH UNIQUE KEY table_line, ls_consumption TYPE ty_consumption, lt_mseg TYPE STANDARD TABLE OF mseg. ""Write: / 'Lade Verbrauchsdaten...'. " Sammle ALLE Materialien (Parents UND Children) LOOP AT gt_bom_relations INTO DATA(ls_rel). INSERT ls_rel-parent INTO TABLE lt_all_materials. INSERT ls_rel-child INTO TABLE lt_all_materials. ENDLOOP. " Zusätzlich: Alle Materialien aus gt_materials LOOP AT gt_materials INTO DATA(ls_mat). INSERT ls_mat-matnr INTO TABLE lt_all_materials. ENDLOOP. IF lt_all_materials IS INITIAL. ""Write: / 'Keine Materialien für Verbrauchsermittlung gefunden.'. RETURN. ENDIF. " Datumsbereiche w_datum_bis = sy-datum. WHILE w_datum_bis+4(2) = sy-datum+4(2). w_datum_bis = w_datum_bis - 1. ENDWHILE. w_datum_von = w_datum_bis - 360. w_datum_von+6(2) = '01'. " MKPF-Belegnummern-Bereich SELECT MIN( mblnr ) INTO @w_mblnr_von FROM mkpf WHERE budat >= @w_datum_von AND blart = 'WL'. SELECT MAX( mblnr ) INTO @w_mblnr_bis FROM mkpf WHERE budat <= @w_datum_bis AND blart = 'WL'. " Verbrauch für ALLE Materialien ermitteln LOOP AT lt_all_materials INTO DATA(lv_matnr). CLEAR ls_consumption. ls_consumption-matnr = lv_matnr. " Basis-Verbrauch CALL FUNCTION 'VERBRAUCH_SUMMIEREN' EXPORTING abdatum = w_datum_von bisdatum = w_datum_bis matnr = lv_matnr periv = ' ' perkz = 'M' werks = '1100' IMPORTING gesamtverbrauch = ls_consumption-gsv01 EXCEPTIONS OTHERS = 1. " MSEG-Korrekturen CLEAR lt_mseg. SELECT matnr, menge, bwart, shkzg FROM mseg INTO CORRESPONDING FIELDS OF TABLE @lt_mseg WHERE matnr = @lv_matnr AND werks = '1100' AND lgort = '0001' AND sobkz = 'E' AND mblnr BETWEEN @w_mblnr_von AND @w_mblnr_bis AND bwart IN ('601', '602', '651', '654'). LOOP AT lt_mseg INTO DATA(ls_mseg). DATA(lv_menge) = ls_mseg-menge. IF ls_mseg-shkzg = 'S'. lv_menge = lv_menge * -1. ENDIF. ls_consumption-gsv01 = ls_consumption-gsv01 + lv_menge. ENDLOOP. ls_consumption-gsv_korr = ls_consumption-gsv01. INSERT ls_consumption INTO TABLE gt_consumption. ENDLOOP. " !!! DEBUG: Verbrauch für kritische Materialien IF lv_matnr = 'B58383' OR lv_matnr = 'B53618' OR lv_matnr = 'B56383' OR lv_matnr = 'B69327'. "Write: / '!!! VERBRAUCH für', lv_matnr, ':'. "Write: / '!!! gsv01 (Original):', ls_consumption-gsv01. "Write: / '!!! gsv_korr (Korrigiert):', ls_consumption-gsv_korr. "Write: / '!!! Anzahl MSEG-Korrekturen:', lines( lt_mseg ). ENDIF. ""Write: / |{ lines( gt_consumption ) } Verbrauchsdatensätze geladen|. ENDMETHOD. METHOD calculate_inheritance. DATA: lt_materials_sorted TYPE STANDARD TABLE OF ty_material, ls_material TYPE ty_material, ls_child TYPE ty_material, lv_changed TYPE abap_bool, lv_iteration TYPE i. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ STARTE VERERBUNGSBERECHNUNG ║'. "Write: / '╚════════════════════════════════════════════════════════════╝'. " ======================================== " SCHRITT 1: INITIALISIERE ALLE MATERIALIEN MIT LEVEL 999 " ======================================== LOOP AT gt_materials INTO ls_material. ls_material-level = 999. MODIFY TABLE gt_materials FROM ls_material. ENDLOOP. " ======================================== " SCHRITT 2: SETZE LEVEL FÜR TOP-MATERIALIEN " ======================================== "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ HIERARCHIE-LEVEL BESTIMMUNG ║'. "Write: / '╚════════════════════════════════════════════════════════════╝'. "Write: / 'Materialien initialisiert:', lines( gt_materials ). LOOP AT gt_materials INTO ls_material. READ TABLE gt_bom_relations WITH KEY child = ls_material-matnr TRANSPORTING NO FIELDS. IF sy-subrc <> 0. ls_material-level = 0. MODIFY TABLE gt_materials FROM ls_material. "Write: / ' TOP-Material (Level 0):', ls_material-matnr. ENDIF. ENDLOOP. " ======================================== " SCHRITT 3: ÜBERNEHME LEVEL AUS gt_bom_relations " ======================================== DATA lv_level_changes TYPE i VALUE 0. LOOP AT gt_bom_relations INTO DATA(ls_rel). READ TABLE gt_materials WITH KEY matnr = ls_rel-child INTO ls_child. IF sy-subrc = 0. IF ls_rel-level < ls_child-level. ls_child-level = ls_rel-level. MODIFY TABLE gt_materials FROM ls_child. ADD 1 TO lv_level_changes. IF lv_level_changes <= 10. "Write: / ' Level gesetzt:', ls_child-matnr, 'Level:', ls_child-level. ENDIF. ENDIF. ENDIF. ENDLOOP. "Write: / 'Level-Änderungen:', lv_level_changes. " ======================================== " SCHRITT 4: SORTIERE NACH LEVEL " ======================================== "Write: / ''. "Write: / '═══ FINALE VERARBEITUNGSREIHENFOLGE ═══'. LOOP AT gt_materials INTO ls_material. APPEND ls_material TO lt_materials_sorted. ENDLOOP. SORT lt_materials_sorted BY level ASCENDING matnr ASCENDING. "Write: / 'Verarbeite', lines( lt_materials_sorted ), 'Materialien in hierarchischer Reihenfolge'. LOOP AT lt_materials_sorted INTO ls_material. IF sy-tabix <= 10. "Write: / ' Material:', ls_material-matnr, 'Level:', ls_material-level. ENDIF. ENDLOOP. " ======================================== " SCHRITT 5: VERARBEITE HIERARCHISCH + BEFÜLLE gt_results " ======================================== DO 10 TIMES. lv_iteration = sy-index. lv_changed = abap_false. "Write: / ''. "Write: / '──── Iteration', lv_iteration, '────'. DATA lv_change_count TYPE i VALUE 0. LOOP AT lt_materials_sorted INTO ls_material. " Hole aktuellen Code READ TABLE gt_materials WITH KEY matnr = ls_material-matnr INTO DATA(ls_current). " Speichere alten Code DATA(lv_old_code) = ls_current-zzlzcod. " Berechne neuen Code DATA(lv_new_code) = me->apply_dominance_rules( iv_matnr = ls_material-matnr ). " Prüfe ob Änderung IF lv_new_code <> ls_current-zzlzcod. ls_current-zzlzcod = lv_new_code. MODIFY TABLE gt_materials FROM ls_current. lv_changed = abap_true. ADD 1 TO lv_change_count. ENDIF. " *** NEU: BEFÜLLE gt_results *** DATA(ls_result) = VALUE ty_result( matnr = ls_material-matnr old_code = lv_old_code new_code = lv_new_code changed = COND #( WHEN lv_new_code <> lv_old_code THEN 'X' ELSE '-' ) message = '' ). " Prüfe ob bereits in gt_results READ TABLE gt_results WITH KEY matnr = ls_material-matnr TRANSPORTING NO FIELDS. IF sy-subrc = 0. MODIFY TABLE gt_results FROM ls_result. ELSE. INSERT ls_result INTO TABLE gt_results. ENDIF. ENDLOOP. "Write: / 'Iteration', lv_iteration, ':', lv_change_count, 'Änderungen'. IF lv_changed = abap_false. "Write: / '*** Keine Änderungen mehr - Vererbung abgeschlossen ***'. EXIT. ENDIF. ENDDO. "Write: / '════════════════════════════════════════════════'. " ======================================== " SCHRITT 6: DEBUG - ZEIGE gt_results " ======================================== "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ gt_results BEFÜLLT ║'. "Write: / '╚════════════════════════════════════════════════════════════╝'. "Write: / 'Anzahl Einträge in gt_results:', lines( gt_results ). DATA lv_changed_count TYPE i VALUE 0. LOOP AT gt_results INTO ls_result WHERE changed = 'X'. ADD 1 TO lv_changed_count. ENDLOOP. "Write: / 'Davon geändert:', lv_changed_count. ENDMETHOD. METHOD apply_dominance_rules. DATA: lt_parents TYPE tt_usage_std, lv_has_a TYPE abap_bool, lv_has_e TYPE abap_bool, lv_has_n TYPE abap_bool, lv_has_s TYPE abap_bool, lv_count_a TYPE i, lv_count_e TYPE i, lv_count_n TYPE i, lv_position_4 TYPE char1 VALUE '0', lv_total_verbrauch TYPE p DECIMALS 2, lv_a_verbrauch TYPE p DECIMALS 2, lv_anteil TYPE p DECIMALS 2, lv_parent_verbrauch TYPE p DECIMALS 2, lv_effektive_menge TYPE p DECIMALS 3, lv_mara_code TYPE char4, lv_parent_count TYPE i, lv_all_n0x0 TYPE abap_bool VALUE abap_true, lv_parent_count_debug TYPE i. " ======================================== " DEBUG: START " ======================================== "Write: / ''. "Write: / '╔══════════════════════════════════════════════════╗'. "Write: / '║ apply_dominance_rules für Material:', iv_matnr. "Write: / '╚══════════════════════════════════════════════════╝'. " ======================================== " SCHRITT 1: HOLE AKTUELLES MATERIAL " ======================================== READ TABLE gt_materials WITH KEY matnr = iv_matnr INTO DATA(ls_current_mat). IF sy-subrc <> 0. "Write: / '*** FEHLER: Material nicht in gt_materials gefunden!'. rv_code = 'N0X0'. RETURN. ENDIF. "Write: / ' Aktueller Code:', ls_current_mat-zzlzcod. "Write: / ' Level:', ls_current_mat-level. " Position 4 Behandlung IF strlen( ls_current_mat-zzlzcod ) >= 4. lv_position_4 = ls_current_mat-zzlzcod+3(1). ENDIF. "Write: / ' Position 4:', lv_position_4. " ======================================== " SCHRITT 2: ZZZZ-SPEZIALREGEL " ======================================== IF ls_current_mat-zzlzcod = 'ZZZZ'. SELECT SINGLE zzlzcod FROM mara INTO @lv_mara_code WHERE matnr = @iv_matnr. IF lv_mara_code = 'ZZZZ'. "Write: / ' -> ZZZZ-Material (unveränderbar)'. rv_code = 'ZZZZ'. RETURN. ENDIF. ENDIF. " ======================================== " SCHRITT 3: N0X1 SPEZIALFALL " ======================================== IF ls_current_mat-zzlzcod = 'N0X1'. LOOP AT gt_bom_relations INTO DATA(ls_rel_check) WHERE child = iv_matnr. ADD 1 TO lv_parent_count. ENDLOOP. IF lv_parent_count = 1. READ TABLE gt_bom_relations INTO ls_rel_check WITH KEY child = iv_matnr. IF sy-subrc = 0. READ TABLE gt_materials INTO DATA(ls_single_parent) WITH KEY matnr = ls_rel_check-parent. IF sy-subrc = 0 AND ls_single_parent-zzlzcod(1) = 'A'. "Write: / ' -> N0X1 Spezialfall: Bleibt N0X1'. rv_code = 'N0X1'. RETURN. ENDIF. ENDIF. ENDIF. ENDIF. " ======================================== " SCHRITT 4: ZÄHLE PARENTS (DEBUG) " ======================================== CLEAR lv_parent_count_debug. LOOP AT gt_bom_relations INTO DATA(ls_rel_debug) WHERE child = iv_matnr. ADD 1 TO lv_parent_count_debug. ENDLOOP. "Write: / ' Anzahl Parents in gt_bom_relations:', lv_parent_count_debug. "INS 18.1.26 IF lv_parent_count_debug = 0. " *** TOP-MATERIAL (keine Parents) → NICHT ANFASSEN *** " Defensive Variante: Prüft alle Fälle explizit IF ls_current_mat-zzlzcod = 'ZZZZ'. " ZZZZ bleibt ZZZZ rv_code = 'ZZZZ'. ELSEIF ls_current_mat-zzlzcod IS INITIAL. " Leerer Code bleibt leer rv_code = ''. ELSE. " Vorhandener Code bleibt unverändert rv_code = ls_current_mat-zzlzcod. ENDIF. RETURN. ENDIF. " DEL 18.1.26 * IF lv_parent_count_debug = 0. * "Write: / ' *** KEINE PARENTS GEFUNDEN! ***'. * "Write: / ' Material behält Code:', ls_current_mat-zzlzcod. * * IF ls_current_mat-zzlzcod IS NOT INITIAL AND * ls_current_mat-zzlzcod <> 'ZZZZ'. * rv_code = ls_current_mat-zzlzcod. * ELSE. * rv_code = 'N0X0'. * ENDIF. * * "Write: / ' Rückgabe:', rv_code. * "Write: / '╚══════════════════════════════════════════════════╝'. * RETURN. * ENDIF. " ======================================== " SCHRITT 5: SAMMLE ALLE PARENTS " ======================================== "Write: / ' Sammle Parents:'. LOOP AT gt_bom_relations INTO DATA(ls_relation) WHERE child = iv_matnr. READ TABLE gt_materials WITH KEY matnr = ls_relation-parent INTO DATA(ls_parent). IF sy-subrc = 0. "Write: / ' Parent:', ls_relation-parent, "'Code:', ls_parent-zzlzcod, "'Menge:', ls_relation-menge. IF ls_parent-zzlzcod = 'ZZZZ'. "Write: ' -> ZZZZ übersprungen'. CONTINUE. ENDIF. " *** KORREKTUR: Verwende Default-Code statt zu überspringen *** IF ls_parent-zzlzcod IS INITIAL. "Write: ' -> Code ist leer, übersprungen'. CONTINUE. " ❌ ÜBERSPRINGT PARENT! ENDIF. APPEND VALUE #( matnr = iv_matnr parent = ls_relation-parent parent_code = ls_parent-zzlzcod usage_qty = ls_relation-menge ) TO lt_parents. " Verbrauchslogik mit Fallbacks READ TABLE gt_consumption WITH KEY matnr = ls_relation-parent INTO DATA(ls_consumption). IF sy-subrc = 0 AND ls_consumption-gsv_korr > 0. lv_parent_verbrauch = ls_consumption-gsv_korr. lv_effektive_menge = ls_relation-menge. "Write: ' -> Verbrauch:', lv_parent_verbrauch. ELSEIF sy-subrc = 0 AND ls_consumption-gsv_korr = 0. lv_parent_verbrauch = 1. lv_effektive_menge = ls_relation-menge. "Write: ' -> Verbrauch=0, verwende Menge'. ELSE. lv_parent_verbrauch = 1. IF ls_relation-menge > 0. lv_effektive_menge = ls_relation-menge. ELSE. lv_effektive_menge = 1. ENDIF. "Write: ' -> Kein Verbrauch, Fallback'. ENDIF. " Gesamtverbrauch akkumulieren lv_total_verbrauch = lv_total_verbrauch + ( lv_effektive_menge * lv_parent_verbrauch ). " A-Anteil berechnen CASE ls_parent-zzlzcod+0(1). WHEN 'A'. lv_has_a = abap_true. ADD 1 TO lv_count_a. DATA: lv_percent_digit TYPE c, lv_mittlerer_prozent TYPE p DECIMALS 2. lv_percent_digit = ls_parent-zzlzcod+1(1). IF lv_percent_digit = '0' AND ls_parent-zzlzcod+2(1) = 'X'. lv_a_verbrauch = lv_a_verbrauch + ( lv_effektive_menge * lv_parent_verbrauch ). "Write: ' -> A0X: 100% A-Anteil'. ELSEIF lv_percent_digit BETWEEN '1' AND '9'. CASE lv_percent_digit. WHEN '0'. lv_mittlerer_prozent = '2'. " 0-4% → 2% WHEN '1'. lv_mittlerer_prozent = '9.5'. " 5-14% → 9.5% ✅ WHEN '2'. lv_mittlerer_prozent = '19.5'. " 15-24% → 19.5% ✅ WHEN '3'. lv_mittlerer_prozent = '29.5'. " 25-34% → 29.5% ✅ WHEN '4'. lv_mittlerer_prozent = '39.5'. " 35-44% → 39.5% ✅ WHEN '5'. lv_mittlerer_prozent = '49.5'. " 45-54% → 49.5% ✅ WHEN '6'. lv_mittlerer_prozent = '59.5'. " 55-64% → 59.5% ✅ WHEN '7'. lv_mittlerer_prozent = '69.5'. " 65-74% → 69.5% ✅ WHEN '8'. lv_mittlerer_prozent = '79.5'. " 75-84% → 79.5% ✅ WHEN '9'. lv_mittlerer_prozent = '92'. " 85-99% → 92% ✅ ENDCASE. lv_a_verbrauch = lv_a_verbrauch + ( lv_effektive_menge * lv_parent_verbrauch * lv_mittlerer_prozent / 100 ). "Write: ' -> A-Anteil:', lv_mittlerer_prozent, '%'. ELSEIF lv_percent_digit = '0' AND ls_parent-zzlzcod+2(1) = 'T'. lv_a_verbrauch = lv_a_verbrauch + ( lv_effektive_menge * lv_parent_verbrauch * 2 / 100 ). "Write: ' -> A0T: 2% A-Anteil'. ENDIF. WHEN 'E'. lv_has_e = abap_true. ADD 1 TO lv_count_e. "Write: ' -> E-Code'. WHEN 'N'. lv_has_n = abap_true. ADD 1 TO lv_count_n. "Write: ' -> N-Code'. WHEN 'S'. lv_has_s = abap_true. "Write: ' -> S-Code'. ENDCASE. ELSE. "Write: / ' Parent:', ls_relation-parent, '-> NICHT IN gt_materials!'. ENDIF. ENDLOOP. "Write: / ' Gültige Parents gesammelt:', lines( lt_parents ). "Write: / ' Counts: A=', lv_count_a, 'E=', lv_count_e, 'N=', lv_count_n. " ======================================== " SCHRITT 6: FEHLERFALL - KEINE GÜLTIGEN PARENTS " ======================================== IF lines( lt_parents ) = 0. "Write: / ' *** KEINE GÜLTIGEN PARENTS! ***'. IF ls_current_mat-zzlzcod IS NOT INITIAL AND ls_current_mat-zzlzcod <> 'ZZZZ'. rv_code = ls_current_mat-zzlzcod. ELSE. rv_code = 'N0X0'. ENDIF. "Write: / ' Rückgabe:', rv_code. "Write: / '╚══════════════════════════════════════════════════╝'. RETURN. ENDIF. " ======================================== " SCHRITT 7: BERECHNE A-ANTEIL " ======================================== IF lv_total_verbrauch > 0. lv_anteil = ( lv_a_verbrauch / lv_total_verbrauch ) * 100. ELSE. lv_anteil = 0. ENDIF. "Write: / ' Total-Verbrauch:', lv_total_verbrauch. "Write: / ' A-Verbrauch:', lv_a_verbrauch. "Write: / ' A-Anteil:', lv_anteil, '%'. " ======================================== " SCHRITT 8: VERERBUNGSLOGIK (KORRIGIERT) " ======================================== " REGEL 1: E dominiert (nur wenn KEIN N dabei ist) IF lv_has_e = abap_true AND lv_has_n = abap_false. "Write: / ' REGEL 1: E dominiert (ohne N)'. rv_code = 'E0X' && lv_position_4. " REGEL 2: N + A (ohne E) → Auslauf-Teilmenge ELSEIF lv_has_n = abap_true AND lv_has_a = abap_true AND lv_has_e = abap_false. "Write: / ' REGEL 2: N + A (ohne E) = Auslauf-Teilmenge'. IF lv_anteil = 100. rv_code = 'A0X' && lv_position_4. ELSEIF lv_anteil >= 85. rv_code = 'A9T' && lv_position_4. ELSEIF lv_anteil >= 75. rv_code = 'A8T' && lv_position_4. ELSEIF lv_anteil >= 65. rv_code = 'A7T' && lv_position_4. ELSEIF lv_anteil >= 55. rv_code = 'A6T' && lv_position_4. ELSEIF lv_anteil >= 45. rv_code = 'A5T' && lv_position_4. ELSEIF lv_anteil >= 35. rv_code = 'A4T' && lv_position_4. ELSEIF lv_anteil >= 25. rv_code = 'A3T' && lv_position_4. ELSEIF lv_anteil >= 15. rv_code = 'A2T' && lv_position_4. ELSEIF lv_anteil >= 5. rv_code = 'A1T' && lv_position_4. ELSEIF lv_anteil > 0. rv_code = 'A0T' && lv_position_4. ELSE. rv_code = 'N0X' && lv_position_4. ENDIF. " REGEL 3: N dominiert IMMER (auch mit E/A) ELSEIF lv_has_n = abap_true. "Write: / ' REGEL 3: N dominiert (mit E/A)'. rv_code = 'N0X' && lv_position_4. " REGEL 4: Nur A (ohne N/E) ELSEIF lv_has_a = abap_true AND lv_has_n = abap_false AND lv_has_e = abap_false. "Write: / ' REGEL 4: Nur A'. IF lv_anteil = 100. rv_code = 'A0X' && lv_position_4. ELSEIF lv_anteil >= 85. rv_code = 'A9T' && lv_position_4. ELSEIF lv_anteil >= 75. rv_code = 'A8T' && lv_position_4. ELSEIF lv_anteil >= 65. rv_code = 'A7T' && lv_position_4. ELSEIF lv_anteil >= 55. rv_code = 'A6T' && lv_position_4. ELSEIF lv_anteil >= 45. rv_code = 'A5T' && lv_position_4. ELSEIF lv_anteil >= 35. rv_code = 'A4T' && lv_position_4. ELSEIF lv_anteil >= 25. rv_code = 'A3T' && lv_position_4. ELSEIF lv_anteil >= 15. rv_code = 'A2T' && lv_position_4. ELSEIF lv_anteil >= 5. rv_code = 'A1T' && lv_position_4. ELSEIF lv_anteil > 0. rv_code = 'A0T' && lv_position_4. ELSE. rv_code = 'N0X' && lv_position_4. ENDIF. " REGEL 5: Fallback ELSE. "Write: / ' REGEL 5: Fallback (keine gültigen Parents)'. rv_code = 'N0X' && lv_position_4. ENDIF. ENDMETHOD. METHOD Write_to_journal. DATA: ls_journal TYPE zlzcod_journl. ls_journal-mandt = sy-mandt. ls_journal-matnr = iv_matnr. ls_journal-aenderungsdatum = sy-datum. ls_journal-aenderungszeit = sy-uzeit. ls_journal-aenderungsuser = sy-uname. ls_journal-old_lzcod = iv_old_lzcod. ls_journal-new_lzcod = iv_new_lzcod. ls_journal-old_lzcodsort = iv_old_lzcodsort. ls_journal-new_lzcodsort = iv_new_lzcodsort. ls_journal-programm = sy-repid. " TOP-Material aus aktueller Selektion (da Wrapper nur 1 übergibt) READ TABLE s_matnr INDEX 1 INTO DATA(ls_sel). IF sy-subrc = 0 AND ls_sel-sign = 'I' AND ls_sel-option = 'EQ'. ls_journal-top_matnr = ls_sel-low. ELSE. ls_journal-top_matnr = iv_matnr. " Fallback ENDIF. " In Tabelle schreiben INSERT zlzcod_journl FROM ls_journal. IF sy-subrc <> 0. " Bei Fehler: Update statt Insert (falls Eintrag schon existiert) UPDATE zlzcod_journl FROM ls_journal. ENDIF. " Für Debug-Zwecke: IF p_test = abap_true. ""Write: / 'Journal:', ls_journal-matnr, "'TOP:', ls_journal-top_matnr, "'LZCOD:', ls_journal-old_lzcod, '->', ls_journal-new_lzcod, " 'LZCODSORT:', ls_journal-old_lzcodsort, '->', ls_journal-new_lzcodsort. ENDIF. ENDMETHOD. * * METHOD update_database. * DATA: lt_update_batch TYPE STANDARD TABLE OF ty_result, * lv_batch_count TYPE i, * lv_updated TYPE i, * lv_datum_str TYPE dats. * * lv_datum_str = sy-datum. * * " Batch-Update für Performance * LOOP AT gt_results INTO DATA(ls_result) WHERE changed = abap_true. * APPEND ls_result TO lt_update_batch. * * * " Führe Batch-Update durch * LOOP AT lt_update_batch INTO DATA(ls_update). * * UPDATE mara SET zzlzcod = @ls_update-new_code, * zzlzdat = @lv_datum_str * WHERE matnr = @ls_update-matnr. * COMMIT WORK. * * " Verwende MM02 für Update * * " Journal-Eintrag mit TOP-Material ** me->""Write_to_journal( ** iv_matnr = ls_update-matnr ** iv_old_lzcod = ls_update-old_code ** iv_new_lzcod = ls_update-new_code ). * * ADD 1 TO lv_updated. * ENDLOOP. * * COMMIT WORK. * CLEAR lt_update_batch. * ADD 1 TO lv_batch_count. * * " Progress-Anzeige * cl_progress_indicator=>progress_indicate( * i_text = |Batch { lv_batch_count } aktualisiert| * i_processed = lv_updated * i_total = lines( gt_results ) ). * * ENDLOOP. * * " Letzter Batch * IF lines( lt_update_batch ) > 0. * LOOP AT lt_update_batch INTO ls_update. * IF p_mm = abap_true. * UPDATE mara SET zzlzcod = @ls_update-new_code, * zzlzdat = @lv_datum_str * WHERE matnr = @ls_update-matnr. COMMIT WORK. * ELSE. * * * * * ENDIF. * * " Journal-Eintrag mit TOP-Material * me->Write_to_journal( * iv_matnr = ls_update-matnr * iv_old_lzcod = ls_update-old_code * iv_new_lzcod = ls_update-new_code ). * * ADD 1 TO lv_updated. * ENDLOOP. * COMMIT WORK. * ENDIF. * * ""Write: / |{ lv_updated } Materialien über MM02 aktualisiert (ZZLZCOD)|. * ENDMETHOD. METHOD update_database. DATA: lv_updated TYPE i, lv_datum_str TYPE dats. lv_datum_str = sy-datum. LOOP AT gt_results INTO DATA(ls_result) WHERE changed = abap_true. " Update durchführen UPDATE mara SET zzlzcod = @ls_result-new_code, zzlzdat = @lv_datum_str WHERE matnr = @ls_result-matnr. COMMIT WORK. " Journal-Eintrag me->write_to_journal( iv_matnr = ls_result-matnr iv_old_lzcod = ls_result-old_code iv_new_lzcod = ls_result-new_code ). ADD 1 TO lv_updated. ENDLOOP. WRITE: / lv_updated, 'Materialien aktualisiert (ZZLZCOD)'. ENDMETHOD. METHOD update_database_sortiment. DATA: lv_updated TYPE i, lv_errors TYPE i, lv_status99 TYPE i, lv_datum_str TYPE dats. lv_datum_str = sy-datum. WRITE: / '=================================================='. WRITE: / 'START: Update Database Sortiment'. WRITE: / 'Anzahl zu aktualisieren:', lines( gt_results ). LOOP AT gt_results INTO DATA(ls_result) WHERE changed_sort = abap_true. " *** DIREKTES UPDATE MARA (auch leerer Code für Status 99) *** UPDATE mara SET zzlzcodsort = @ls_result-new_code_sort, zzlzdat = @lv_datum_str WHERE matnr = @ls_result-matnr. IF sy-subrc = 0. ADD 1 TO lv_updated. " Zähle Status 99 separat IF ls_result-new_code_sort IS INITIAL. ADD 1 TO lv_status99. ENDIF. " Journal-Eintrag me->write_to_journal( iv_matnr = ls_result-matnr iv_old_lzcod = ls_result-old_code iv_new_lzcod = ls_result-old_code iv_old_lzcodsort = ls_result-old_code_sort iv_new_lzcodsort = ls_result-new_code_sort ). ELSE. ADD 1 TO lv_errors. WRITE: / 'ERROR: Update fehlgeschlagen für', ls_result-matnr. ENDIF. ENDLOOP. " Commit nach allen Updates IF lv_updated > 0. COMMIT WORK AND WAIT. WRITE: / 'ERFOLG:', lv_updated, 'Sortiments-Codes aktualisiert'. WRITE: / ' Davon Status 99 (geleert):', lv_status99. ENDIF. IF lv_errors > 0. WRITE: / 'FEHLER:', lv_errors, 'Updates fehlgeschlagen'. ENDIF. WRITE: / 'ENDE: Update Database Sortiment'. WRITE: / '=================================================='. ENDMETHOD. * * METHOD transaktion_mm02. * DATA: lv_matnr_str TYPE bdc_fval, " Geändert zu bdc_fval * lv_datum_str TYPE bdc_fval, " Geändert zu bdc_fval * lv_lzcod_str TYPE bdc_fval, " NEU für iv_lzcod * lv_lzcodsort_str TYPE bdc_fval. " NEU für iv_lzcodsort * * CLEAR: gt_bdcdata, gt_bdcmsg. * * " Material und Datum formatieren - als bdc_fval * lv_matnr_str = |{ iv_matnr ALPHA = OUT }|. * lv_datum_str = |{ sy-datum DATE = USER }|. * lv_lzcod_str = iv_lzcod. " Direkte Zuweisung, da char4 -> bdc_fval kompatibel * * " Optional: ZZLZCODSORT konvertieren * IF iv_lzcodsort IS NOT INITIAL. * lv_lzcodsort_str = iv_lzcodsort. * ENDIF. * * " Einstiegsbild MM02 * me->bdc_dynpro( iv_program = 'SAPLMGMM' iv_dynpro = '0060' ). * me->bdc_field( iv_fnam = 'BDC_OKCODE' iv_fval = 'AUSW' ). * me->bdc_field( iv_fnam = 'RMMG1-MATNR' iv_fval = lv_matnr_str ). * * " Sichtenauswahl * me->bdc_dynpro( iv_program = 'SAPLMGMM' iv_dynpro = '0070' ). * me->bdc_field( iv_fnam = 'BDC_OKCODE' iv_fval = '=ENTR' ). * me->bdc_field( iv_fnam = 'MSICHTAUSW-KZSEL(01)' iv_fval = 'X' ). * * " Grunddaten - beide Felder setzen * me->bdc_dynpro( iv_program = 'SAPLMGMM' iv_dynpro = '4004' ). * me->bdc_field( iv_fnam = 'BDC_OKCODE' iv_fval = '/11' ). * * " ZZLZCOD setzen * me->bdc_field( iv_fnam = 'MARA-ZZLZCOD' iv_fval = lv_lzcod_str ). * me->bdc_field( iv_fnam = 'MARA-ZZLZDAT' iv_fval = lv_datum_str ). * * " ZZLZCODSORT setzen falls übergeben * IF iv_lzcodsort IS NOT INITIAL. * me->bdc_field( iv_fnam = 'MARA-ZZLZCODSORT' iv_fval = lv_lzcodsort_str ). * ENDIF. * * me->bdc_transaction( iv_tcode = 'MM02' ). * ENDMETHOD. * METHOD is_debug_material. " NUR diese 4 Materialien debuggen! rv_debug = COND #( WHEN iv_matnr = 'B53618' OR iv_matnr = 'B56383' OR iv_matnr = 'B58383' OR iv_matnr = 'B69327' THEN abap_true ELSE abap_false ). ENDMETHOD. METHOD debug_problem_material. DATA: lt_info TYPE string_table, lv_line TYPE string. " Nur debuggen wenn es ein Problem-Material ist IF is_debug_material( iv_matnr ) = abap_false. RETURN. ENDIF. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ DEBUG PROBLEM-MATERIAL:', iv_matnr, 'Phase:', iv_phase. "Write: / '╚════════════════════════════════════════════════════════════╝'. " Je nach Phase verschiedene Informationen sammeln CASE iv_phase. WHEN 'LOAD'. " Nach dem Laden: Zeige Stammdaten READ TABLE gt_materials WITH KEY matnr = iv_matnr INTO DATA(ls_mat). IF sy-subrc = 0. "Write: / ' Stammdaten gefunden:'. "Write: / ' MTART:', ls_mat-mtart. "Write: / ' ZZLZCOD (aktuell):', ls_mat-zzlzcod. "Write: / ' ZZLZCODSORT:', ls_mat-zzlzcodsort. "Write: / ' DISST:', ls_mat-disst. ELSE. "Write: / ' *** FEHLER: Material NICHT in gt_materials!'. ENDIF. WHEN 'RELATIONS'. " Parent-Child Beziehungen DATA: lv_parent_count TYPE i, lv_child_count TYPE i. "Write: / ' === PARENT-BEZIEHUNGEN ==='. LOOP AT gt_bom_relations INTO DATA(ls_rel) WHERE child = iv_matnr. ADD 1 TO lv_parent_count. READ TABLE gt_materials WITH KEY matnr = ls_rel-parent INTO DATA(ls_parent). IF sy-subrc = 0. "Write: / ' Parent', lv_parent_count, ':', ls_rel-parent. "Write: / ' Code:', ls_parent-zzlzcod. "Write: / ' Menge:', ls_rel-menge. ELSE. "Write: / ' Parent', lv_parent_count, ':', ls_rel-parent, "'*** NICHT in gt_materials!'. ENDIF. ENDLOOP. IF lv_parent_count = 0. "Write: / ' *** KEINE PARENTS GEFUNDEN! ***'. ELSE. "Write: / ' Anzahl Parents:', lv_parent_count. ENDIF. "Write: / ' === CHILD-BEZIEHUNGEN ==='. LOOP AT gt_bom_relations INTO ls_rel WHERE parent = iv_matnr. ADD 1 TO lv_child_count. "Write: / ' Child:', ls_rel-child, 'Menge:', ls_rel-menge. ENDLOOP. IF lv_child_count = 0. "Write: / ' Keine Children (Endprodukt oder Komponente ohne Stückliste)'. ELSE. "Write: / ' Anzahl Children:', lv_child_count. ENDIF. WHEN 'CONSUMPTION'. " Verbrauchsdaten "Write: / ' === VERBRAUCHSDATEN ==='. " Eigener Verbrauch READ TABLE gt_consumption WITH KEY matnr = iv_matnr INTO DATA(ls_cons). IF sy-subrc = 0. "Write: / ' Eigener Verbrauch:'. "Write: / ' gsv01:', ls_cons-gsv01. "Write: / ' gsv_korr:', ls_cons-gsv_korr. ELSE. "Write: / ' *** KEIN Verbrauch gefunden!'. ENDIF. " Parent-Verbräuche "Write: / ' Parent-Verbräuche:'. LOOP AT gt_bom_relations INTO ls_rel WHERE child = iv_matnr. READ TABLE gt_consumption WITH KEY matnr = ls_rel-parent INTO ls_cons. IF sy-subrc = 0. "Write: / ' Parent', ls_rel-parent, "'gsv01:', ls_cons-gsv01, "'gsv_korr:', ls_cons-gsv_korr. ELSE. "Write: / ' Parent', ls_rel-parent, 'KEIN Verbrauch'. ENDIF. ENDLOOP. WHEN 'BEFORE_CALC'. " Vor der Berechnung "Write: / ' === VOR BERECHNUNG ==='. READ TABLE gt_materials WITH KEY matnr = iv_matnr INTO ls_mat. IF sy-subrc = 0. "Write: / ' Aktueller Code:', ls_mat-zzlzcod. " Zeige Parent-Codes "Write: / ' Parent-Codes:'. LOOP AT gt_bom_relations INTO ls_rel WHERE child = iv_matnr. READ TABLE gt_materials WITH KEY matnr = ls_rel-parent INTO ls_parent. IF sy-subrc = 0. "Write: / ' ', ls_rel-parent, '=', ls_parent-zzlzcod. ELSE. "Write: / ' ', ls_rel-parent, '= NICHT GEFUNDEN'. ENDIF. ENDLOOP. ENDIF. WHEN 'AFTER_CALC'. " Nach der Berechnung "Write: / ' === NACH BERECHNUNG ==='. READ TABLE gt_results WITH KEY matnr = iv_matnr INTO DATA(ls_result). IF sy-subrc = 0. "Write: / ' Alter Code:', ls_result-old_code. "Write: / ' Neuer Code:', ls_result-new_code. "Write: / ' Geändert:', ls_result-changed. ELSE. "Write: / ' *** KEIN ERGEBNIS in gt_results!'. ENDIF. ENDCASE. "Write: / '══════════════════════════════════════════════════════════'. ENDMETHOD. METHOD build_hierarchy_from_stb. DATA: lt_vc_data TYPE STANDARD TABLE OF zpowerbi_vc, ls_bom_rel TYPE ty_bom_relation, ls_material TYPE ty_material, lv_top_material TYPE matnr, lv_rel_count TYPE i, lv_mat_count TYPE i. " ======================================== " SCHRITT 1: TOP-Material bestimmen " ======================================== IF iv_top_material IS NOT INITIAL. lv_top_material = iv_top_material. ELSE. RETURN. ENDIF. " ======================================== " SCHRITT 2: Prüfe ob Material in ZPOWERBI_VC existiert " ======================================== SELECT COUNT(*) FROM zpowerbi_vc INTO @DATA(lv_row_count) WHERE matnr = @lv_top_material. IF lv_row_count = 0. RETURN. ENDIF. " ======================================== " SCHRITT 3: Lade Hierarchie aus ZPOWERBI_VC " ======================================== SELECT * FROM zpowerbi_vc INTO TABLE @lt_vc_data WHERE matnr = @lv_top_material ORDER BY mat_mstav. IF sy-subrc <> 0. RETURN. ENDIF. " ======================================== " SCHRITT 4: TOP-Material zu gt_materials hinzufügen " ======================================== READ TABLE gt_materials WITH KEY matnr = lv_top_material TRANSPORTING NO FIELDS. IF sy-subrc <> 0. SELECT SINGLE matnr, mtart, zzlzcod, zzlzcodsort, zztyp_f4, pstat, disst, mstae FROM mara INTO CORRESPONDING FIELDS OF @ls_material WHERE matnr = @lv_top_material. IF sy-subrc = 0. " *** NEU: Status 99 prüfen *** IF ls_material-mstae = '99'. ls_material-zzlzcodsort = ''. ENDIF. INSERT ls_material INTO TABLE gt_materials. ADD 1 TO lv_mat_count. ENDIF. ENDIF. " ======================================== " SCHRITT 5: Verarbeite ZPOWERBI_VC-Einträge " ======================================== LOOP AT lt_vc_data INTO DATA(ls_vc). DATA(lv_parent_matnr) = VALUE matnr( ). IF ls_vc-mat_mstae = 0. lv_parent_matnr = lv_top_material. ELSE. lv_parent_matnr = ls_vc-kom_mstae. ENDIF. IF lv_parent_matnr IS NOT INITIAL. ls_bom_rel-parent = lv_parent_matnr. ls_bom_rel-child = ls_vc-kompnr. ls_bom_rel-menge = ls_vc-menge. ls_bom_rel-level = ls_vc-stufe. INSERT ls_bom_rel INTO TABLE gt_bom_relations. ADD 1 TO lv_rel_count. ENDIF. " Füge Child-Material zu gt_materials hinzu READ TABLE gt_materials WITH KEY matnr = ls_vc-kompnr TRANSPORTING NO FIELDS. IF sy-subrc <> 0. CLEAR ls_material. ls_material-matnr = ls_vc-kompnr. ls_material-mtart = ls_vc-materialart. ls_material-disst = ''. " Hole ZZLZCOD/ZZLZCODSORT/MSTAE aus MARA SELECT SINGLE zzlzcod, zzlzcodsort, zztyp_f4, pstat, mstae FROM mara INTO CORRESPONDING FIELDS OF @ls_material WHERE matnr = @ls_vc-kompnr. IF sy-subrc = 0. " *** NEU: Status 99 prüfen *** IF ls_material-mstae = '99'. ls_material-zzlzcodsort = ''. ENDIF. INSERT ls_material INTO TABLE gt_materials. ADD 1 TO lv_mat_count. ENDIF. ENDIF. ENDLOOP. ENDMETHOD. METHOD debug_collect_info. DATA: lv_info TYPE string. CLEAR rt_info. " Sammle alle Informationen für ein Material READ TABLE gt_materials WITH KEY matnr = iv_matnr INTO DATA(ls_mat). IF sy-subrc = 0. lv_info = |Material: { iv_matnr } Code: { ls_mat-zzlzcod }|. APPEND lv_info TO rt_info. ENDIF. " Parents LOOP AT gt_bom_relations INTO DATA(ls_rel) WHERE child = iv_matnr. READ TABLE gt_materials WITH KEY matnr = ls_rel-parent INTO DATA(ls_parent). IF sy-subrc = 0. lv_info = | Parent: { ls_rel-parent } Code: { ls_parent-zzlzcod }|. ELSE. lv_info = | Parent: { ls_rel-parent } NICHT GEFUNDEN|. ENDIF. APPEND lv_info TO rt_info. ENDLOOP. ENDMETHOD. METHOD bdc_dynpro. DATA: ls_bdcdata TYPE bdcdata. ls_bdcdata-program = iv_program. ls_bdcdata-dynpro = iv_dynpro. ls_bdcdata-dynbegin = 'X'. APPEND ls_bdcdata TO gt_bdcdata. ENDMETHOD. METHOD bdc_field. DATA: ls_bdcdata TYPE bdcdata. ls_bdcdata-fnam = iv_fnam. ls_bdcdata-fval = iv_fval. APPEND ls_bdcdata TO gt_bdcdata. ENDMETHOD. METHOD bdc_transaction. DATA: ls_options TYPE ctu_params. ls_options-dismode = 'N'. " Kein Bildschirm ls_options-updmode = 'S'. " Synchron ls_options-defsize = 'X'. " Standardgröße CALL TRANSACTION iv_tcode USING gt_bdcdata OPTIONS FROM ls_options MESSAGES INTO gt_bdcmsg. " Fehlerbehandlung LOOP AT gt_bdcmsg INTO DATA(ls_msg) WHERE msgtyp = 'E' OR msgtyp = 'A'. MESSAGE ID ls_msg-msgid TYPE 'I' NUMBER ls_msg-msgnr WITH ls_msg-msgv1 ls_msg-msgv2 ls_msg-msgv3 ls_msg-msgv4. ENDLOOP. ENDMETHOD. METHOD calculate_sortiment_inhe. DATA: lv_iteration TYPE i, lv_changes TYPE i, lt_material_list TYPE STANDARD TABLE OF matnr. " Sammle alle zu verarbeitenden Materialien LOOP AT gt_materials INTO DATA(ls_material). APPEND ls_material-matnr TO lt_material_list. ENDLOOP. " Iterative Vererbungsberechnung (max. 10 Iterationen) DO 10 TIMES. lv_iteration = sy-index. CLEAR lv_changes. " Verarbeite alle Materialien LOOP AT lt_material_list INTO DATA(lv_matnr). " Prüfe auf Änderung READ TABLE gt_materials WITH KEY matnr = lv_matnr ASSIGNING FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. " *** NEU: Status 99 = Ausgelaufen → Überspringen *** IF -mstae = '99'. " Nur beim ersten Durchlauf in Ergebnis aufnehmen IF lv_iteration = 1. READ TABLE gt_results WITH KEY matnr = lv_matnr TRANSPORTING NO FIELDS. IF sy-subrc <> 0. APPEND VALUE #( matnr = lv_matnr old_code_sort = -zzlzcodsort new_code_sort = '' changed_sort = COND #( WHEN -zzlzcodsort IS NOT INITIAL THEN abap_true ELSE abap_false ) message = 'Status 99 - Ausgelaufen' ) TO gt_results. ENDIF. ENDIF. CONTINUE. " Nächstes Material ENDIF. " Prüfe manuelle Sperre (Position 4 = '1') IF strlen( -zzlzcodsort ) >= 4 AND -zzlzcodsort+3(1) = '1'. CONTINUE. ENDIF. " Berechne neuen Sortimentscode DATA(lv_new_code) = apply_sortiment_rules( lv_matnr ). " Prüfe ob sich der Code geändert hat IF -zzlzcodsort <> lv_new_code. " Speichere Änderung in Ergebnistabelle READ TABLE gt_results ASSIGNING FIELD-SYMBOL() WITH KEY matnr = lv_matnr. IF sy-subrc = 0. -old_code_sort = -zzlzcodsort. -new_code_sort = lv_new_code. -changed_sort = abap_true. ELSE. APPEND VALUE #( matnr = lv_matnr old_code_sort = -zzlzcodsort new_code_sort = lv_new_code changed_sort = abap_true ) TO gt_results. ENDIF. " Update Material mit neuem Code -zzlzcodsort = lv_new_code. ADD 1 TO lv_changes. ENDIF. ENDLOOP. " Beende wenn keine Änderungen mehr IF lv_changes = 0. EXIT. ENDIF. ENDDO. ENDMETHOD. METHOD apply_sortiment_rules. DATA: lv_position_4 TYPE char1, lv_has_s TYPE abap_bool, lv_has_o TYPE abap_bool, lv_has_e TYPE abap_bool, lv_has_n TYPE abap_bool, lv_has_a TYPE abap_bool, lv_has_c TYPE abap_bool, lv_parent_count TYPE i, lv_sonder_weighted TYPE p DECIMALS 3, lv_core_weighted TYPE p DECIMALS 3, lv_total_weighted TYPE p DECIMALS 3, lv_percentage TYPE p DECIMALS 2, lv_pct_int TYPE i, ls_current_mat TYPE ty_material, ls_parent TYPE ty_material, ls_relation TYPE ty_bom_relation, ls_consumption TYPE ty_consumption. WRITE: / ''. WRITE: / '╔══════════════════════════════════════════════════════════╗'. WRITE: / '║ apply_sortiment_rules (NEU) für:', iv_matnr. WRITE: / '╚══════════════════════════════════════════════════════════╝'. " ══════════════════════════════════════════════════════════════════ " SCHRITT 1: HOLE AKTUELLES MATERIAL " ══════════════════════════════════════════════════════════════════ READ TABLE gt_materials WITH TABLE KEY matnr = iv_matnr INTO ls_current_mat. IF sy-subrc <> 0. WRITE: / ' ERROR: Material nicht in gt_materials!'. rv_code = 'N0X0'. RETURN. ENDIF. " *** NEU: Status 99 = Ausgelaufen → Leerer Code *** IF ls_current_mat-mstae = '99'. WRITE: / ' Status 99 (Ausgelaufen) → Code wird geleert'. rv_code = ''. RETURN. ENDIF. " Initialisiere Code falls leer IF ls_current_mat-zzlzcodsort IS INITIAL. ls_current_mat-zzlzcodsort = 'N0X0'. MODIFY TABLE gt_materials FROM ls_current_mat. ENDIF. WRITE: / ' Aktueller Code:', ls_current_mat-zzlzcodsort. " ══════════════════════════════════════════════════════════════════ " SCHRITT 2: POSITION 4 (MANUELLE SPERRE) " ══════════════════════════════════════════════════════════════════ IF strlen( ls_current_mat-zzlzcodsort ) >= 4. lv_position_4 = ls_current_mat-zzlzcodsort+3(1). ELSE. lv_position_4 = '0'. ENDIF. IF lv_position_4 = '1'. WRITE: / ' -> GESPERRT (Position 4 = 1), Code bleibt'. rv_code = ls_current_mat-zzlzcodsort. RETURN. ENDIF. " ══════════════════════════════════════════════════════════════════ " SCHRITT 3: SAMMLE PARENTS UND ANALYSIERE TYPEN " ══════════════════════════════════════════════════════════════════ WRITE: / ' Analysiere Parents:'. CLEAR: lv_parent_count, lv_has_s, lv_has_o, lv_has_e, lv_has_n, lv_has_a, lv_has_c. LOOP AT gt_bom_relations INTO ls_relation WHERE child = iv_matnr. ADD 1 TO lv_parent_count. READ TABLE gt_materials WITH TABLE KEY matnr = ls_relation-parent INTO ls_parent. IF sy-subrc <> 0. WRITE: / ' Parent', ls_relation-parent, 'NICHT GEFUNDEN!'. CONTINUE. ENDIF. " *** NEU: Parent mit Status 99 überspringen *** IF ls_parent-mstae = '99'. WRITE: / ' Parent', ls_relation-parent, 'Status 99 → übersprungen'. SUBTRACT 1 FROM lv_parent_count. CONTINUE. ENDIF. " Überspringe ZZZZ und leere Codes IF ls_parent-zzlzcodsort = 'ZZZZ' OR ls_parent-zzlzcodsort IS INITIAL. WRITE: / ' Parent', ls_relation-parent, 'übersprungen (ZZZZ/leer)'. CONTINUE. ENDIF. WRITE: / ' Parent:', ls_relation-parent, 'Code:', ls_parent-zzlzcodsort, 'Typ:', ls_parent-zzlzcodsort(1). " Setze Flags basierend auf 1. Stelle CASE ls_parent-zzlzcodsort(1). WHEN 'S'. lv_has_s = abap_true. WHEN 'O'. lv_has_o = abap_true. WHEN 'E'. lv_has_e = abap_true. " WHEN 'N'. lv_has_n = abap_true. WHEN 'A'. lv_has_a = abap_true. WHEN 'C'. lv_has_c = abap_true. ENDCASE. ENDLOOP. WRITE: / ' Anzahl gültige Parents:', lv_parent_count. WRITE: / ' Flags: S=', lv_has_s, 'O=', lv_has_o, 'E=', lv_has_e, 'C=', lv_has_c, 'N=', lv_has_n, 'A=', lv_has_a. " ══════════════════════════════════════════════════════════════════ " SCHRITT 4: KEINE PARENTS → CODE BLEIBT " ══════════════════════════════════════════════════════════════════ IF lv_parent_count = 0. rv_code = ls_current_mat-zzlzcodsort. WRITE: / ' -> Keine Parents, Code bleibt:', rv_code. RETURN. ENDIF. " ══════════════════════════════════════════════════════════════════ " SCHRITT 5: REINE FÄLLE (NUR EIN TYP VORHANDEN) " ══════════════════════════════════════════════════════════════════ " REGEL 1: Nur C → C0X* IF lv_has_c = abap_true AND lv_has_s = abap_false AND lv_has_o = abap_false AND lv_has_e = abap_false AND lv_has_a = abap_false. rv_code = 'C0X' && lv_position_4. WRITE: / ' REGEL 1: Nur C → C0X*'. RETURN. ENDIF. " REGEL 2: Nur E → E0X* IF lv_has_e = abap_true AND lv_has_s = abap_false AND lv_has_o = abap_false AND lv_has_c = abap_false AND lv_has_a = abap_false. rv_code = 'E0X' && lv_position_4. WRITE: / ' REGEL 2: Nur E → E0X*'. RETURN. ENDIF. " REGEL 3: Nur O → O0X* IF lv_has_o = abap_true AND lv_has_s = abap_false AND lv_has_e = abap_false AND lv_has_c = abap_false AND lv_has_a = abap_false. rv_code = 'O0X' && lv_position_4. WRITE: / ' REGEL 3: Nur O → O0X*'. RETURN. ENDIF. " REGEL 4: Nur N → N0X* * IF lv_has_n = abap_true AND * lv_has_s = abap_false AND lv_has_o = abap_false AND * lv_has_e = abap_false AND lv_has_c = abap_false AND lv_has_a = abap_false. * rv_code = 'N0X' && lv_position_4. * WRITE: / ' REGEL 4: Nur N → N0X*'. * RETURN. * ENDIF. " REGEL 5: Nur A → A0X* IF lv_has_a = abap_true AND lv_has_s = abap_false AND lv_has_o = abap_false AND lv_has_e = abap_false AND lv_has_c = abap_false . rv_code = 'A0X' && lv_position_4. WRITE: / ' REGEL 5: Nur A → A0X*'. RETURN. ENDIF. " ══════════════════════════════════════════════════════════════════ " SCHRITT 6: GEMISCHTE FÄLLE → PROZENTBERECHNUNG " ══════════════════════════════════════════════════════════════════ WRITE: / ' GEMISCHTER FALL → Prozentberechnung'. WRITE: / ' Berechne Sonder-Anteil (O + E = 100%, C/N/A = 0%):'. CLEAR: lv_sonder_weighted, lv_core_weighted, lv_total_weighted. " Durchlaufe alle Parents und berechne gewichtete Anteile LOOP AT gt_bom_relations INTO ls_relation WHERE child = iv_matnr. READ TABLE gt_materials WITH TABLE KEY matnr = ls_relation-parent INTO ls_parent. IF sy-subrc <> 0 OR ls_parent-zzlzcodsort IS INITIAL OR ls_parent-zzlzcodsort = 'ZZZZ'. CONTINUE. ENDIF. " *** NEU: Parent mit Status 99 überspringen *** IF ls_parent-mstae = '99'. CONTINUE. ENDIF. " Hole Verbrauch (mit Fallback) READ TABLE gt_consumption WITH KEY matnr = ls_parent-matnr INTO ls_consumption. DATA: lv_parent_verbrauch TYPE p DECIMALS 2. IF sy-subrc = 0 AND ls_consumption-gsv_korr > 0. lv_parent_verbrauch = ls_consumption-gsv_korr. ELSE. lv_parent_verbrauch = 1. ENDIF. " Gewichtete Menge = Menge × Verbrauch DATA(lv_gewichtete_menge) = ls_relation-menge * lv_parent_verbrauch. WRITE: / ' Parent:', ls_parent-matnr, ' Code:', ls_parent-zzlzcodsort, ' Menge:', ls_relation-menge, ' Verbrauch:', lv_parent_verbrauch, ' Gewicht:', lv_gewichtete_menge. " Klassifiziere Parent-Code CASE ls_parent-zzlzcodsort(1). WHEN 'O' OR 'E'. " O und E = 100% Sonder lv_sonder_weighted = lv_sonder_weighted + lv_gewichtete_menge. WRITE: ' → 100% Sonder'. WHEN 'S'. " S-Code: Hole Prozentsatz aus Code DATA(lv_s_prozent) = get_code_percentage( ls_parent-zzlzcodsort ). lv_sonder_weighted = lv_sonder_weighted + ( lv_gewichtete_menge * lv_s_prozent / 100 ). lv_core_weighted = lv_core_weighted + ( lv_gewichtete_menge * ( 100 - lv_s_prozent ) / 100 ). WRITE: ' → ', lv_s_prozent, '% Sonder'. WHEN 'C' OR 'A'. " C, N, A = 0% Sonder (100% Core) lv_core_weighted = lv_core_weighted + lv_gewichtete_menge. WRITE: ' → 0% Sonder (Core)'. ENDCASE. ENDLOOP. " Berechne Gesamtprozentsatz lv_total_weighted = lv_sonder_weighted + lv_core_weighted. IF lv_total_weighted > 0. lv_percentage = ( lv_sonder_weighted / lv_total_weighted ) * 100. ELSE. lv_percentage = 0. ENDIF. WRITE: / ' Sonder-Gewicht:', lv_sonder_weighted. WRITE: / ' Core-Gewicht:', lv_core_weighted. WRITE: / ' Total-Gewicht:', lv_total_weighted. WRITE: / ' Sonder-Prozentsatz:', lv_percentage, '%'. " ══════════════════════════════════════════════════════════════════ " SCHRITT 7: SCHWELLENWERTE (FLOOR-LOGIK) " ══════════════════════════════════════════════════════════════════ " Mini-Schwelle: 0 < % < 3 → Core IF lv_percentage > 0 AND lv_percentage < 3. rv_code = 'C0X' && lv_position_4. WRITE: / ' Schwelle: <3% → C0X*'. RETURN. ENDIF. " Floor-Konvertierung (14.73% → 14) lv_pct_int = lv_percentage. rv_code = COND #( WHEN lv_pct_int >= 100 THEN 'S0X' WHEN lv_pct_int >= 85 THEN 'S9T' WHEN lv_pct_int >= 75 THEN 'S8T' WHEN lv_pct_int >= 65 THEN 'S7T' WHEN lv_pct_int >= 55 THEN 'S6T' WHEN lv_pct_int >= 45 THEN 'S5T' WHEN lv_pct_int >= 35 THEN 'S4T' WHEN lv_pct_int >= 25 THEN 'S3T' WHEN lv_pct_int >= 15 THEN 'S2T' WHEN lv_pct_int >= 5 THEN 'S1T' WHEN lv_pct_int >= 3 THEN 'S0T' ELSE 'S0X' ) && lv_position_4. WRITE: / ' Neuer Code (nach Schwellen):', rv_code. WRITE: / '╚══════════════════════════════════════════════════════════╝'. ENDMETHOD. METHOD calculate_sonder_percentage. DATA: lv_sonder_weighted TYPE p DECIMALS 3, lv_core_weighted TYPE p DECIMALS 3, lv_total_weighted TYPE p DECIMALS 3, lv_parent_verbrauch TYPE p DECIMALS 3, lv_gewichtete_menge TYPE p DECIMALS 3, lv_sonder_prozent TYPE p DECIMALS 2, ls_parent TYPE ty_material, ls_relation TYPE ty_bom_relation, ls_consumption TYPE ty_consumption. CLEAR: rv_percentage, lv_sonder_weighted, lv_core_weighted, lv_total_weighted. LOOP AT gt_bom_relations INTO ls_relation WHERE child = iv_matnr. READ TABLE gt_materials WITH TABLE KEY matnr = ls_relation-parent INTO ls_parent. IF sy-subrc <> 0. CONTINUE. ENDIF. READ TABLE gt_consumption WITH KEY matnr = ls_parent-matnr INTO ls_consumption. IF sy-subrc = 0 AND ls_consumption-gsv_korr > 0. lv_parent_verbrauch = ls_consumption-gsv_korr. ELSE. lv_parent_verbrauch = 1. ENDIF. lv_gewichtete_menge = ls_relation-menge * lv_parent_verbrauch. " S, O und E zählen als Sonder CASE ls_parent-zzlzcodsort(1). WHEN 'S' OR 'O' OR 'E'. " *** NEU: Prüfe Position 3 für 'X' (100% Sonder) *** IF strlen( ls_parent-zzlzcodsort ) >= 3 AND ls_parent-zzlzcodsort+2(1) = 'X'. " 100% Sonder (O0X, E0X, S0X) lv_sonder_weighted = lv_sonder_weighted + lv_gewichtete_menge. ELSE. " Prozent-Anteil berechnen DATA(lv_parent_code) = ls_parent-zzlzcodsort. DATA(lv_sonder_prozent_temp) = get_code_percentage( lv_parent_code ). lv_sonder_prozent = lv_sonder_prozent_temp. lv_sonder_weighted = lv_sonder_weighted + ( lv_gewichtete_menge * lv_sonder_prozent / 100 ). lv_core_weighted = lv_core_weighted + ( lv_gewichtete_menge * ( 100 - lv_sonder_prozent ) / 100 ). ENDIF. WHEN 'C' OR 'N' OR 'A'. " 100% Core lv_core_weighted = lv_core_weighted + lv_gewichtete_menge. ENDCASE. ENDLOOP. lv_total_weighted = lv_sonder_weighted + lv_core_weighted. IF lv_total_weighted > 0. rv_percentage = ( lv_sonder_weighted / lv_total_weighted ) * 100. ELSE. rv_percentage = 0. ENDIF. ENDMETHOD. METHOD get_code_percentage. " %-Wert aus S/O/E-Code ableiten (…nT / …0X) – Mittelwerte je Range DATA: lv_digit TYPE c. lv_digit = iv_code+1(1). rv_percentage = COND ty_percentage( WHEN iv_code+2(1) = 'X' AND lv_digit = '0' THEN 100 " *0X = 100% WHEN lv_digit = '9' THEN '92' " 85–99 → 92.0 WHEN lv_digit = '8' THEN '79.5' " 75–84 → 79.5 WHEN lv_digit = '7' THEN '69.5' " 65–74 → 69.5 WHEN lv_digit = '6' THEN '59.5' WHEN lv_digit = '5' THEN '49.5' WHEN lv_digit = '4' THEN '39.5' WHEN lv_digit = '3' THEN '29.5' WHEN lv_digit = '2' THEN '19.5' WHEN lv_digit = '1' THEN '9.5' WHEN lv_digit = '0' AND iv_code+2(1) = 'T' THEN '2' " *0T = ~2% ELSE '0' ). ENDMETHOD. METHOD build_graph_nodes. DATA: lv_verbrauch_text TYPE string, lv_child_count TYPE i, lv_verbrauch_monat TYPE p DECIMALS 2, lv_is_debug TYPE abap_bool. " Prüfe ob Debug-Material lv_is_debug = me->is_debug_material( iv_matnr ). " Debug-Ausgabe für TOP-Material IF iv_level = 0 AND lv_is_debug = abap_true. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ BUILD_GRAPH_NODES für TOP-Material:', iv_matnr. "Write: / '╚════════════════════════════════════════════════════════════╝'. "Write: / 'Anzahl Beziehungen in gt_bom_relations:', lines( gt_bom_relations ). ENDIF. " Sicherheitsabbruch bei zu tiefer Rekursion IF iv_level > 20. IF lv_is_debug = abap_true. "Write: / '*** ABBRUCH: Level > 20 erreicht ***'. ENDIF. RETURN. ENDIF. " Hole Stammdaten des aktuellen Knotens READ TABLE gt_materials WITH KEY matnr = iv_matnr INTO DATA(ls_material). IF sy-subrc <> 0. IF lv_is_debug = abap_true. "Write: / '*** Material', iv_matnr, 'nicht in gt_materials gefunden ***'. ENDIF. RETURN. ENDIF. " ===== VERBRAUCHSDATEN MIT FORMATIERUNG ===== READ TABLE gt_consumption WITH KEY matnr = iv_matnr INTO DATA(ls_consumption). IF sy-subrc = 0 AND ls_consumption-gsv_korr > 0. " Berechne Durchschnitt pro Monat lv_verbrauch_monat = ls_consumption-gsv_korr / 12. " Formatiere mit Tausender-Trennzeichen und ohne Dezimalstellen lv_verbrauch_text = |{ lv_verbrauch_monat NUMBER = USER DECIMALS = 0 } / Mon|. IF lv_is_debug = abap_true. "Write: / 'Material', iv_matnr, ':'. "Write: / ' Jahresverbrauch:', ls_consumption-gsv_korr. "Write: / ' Monatsverbrauch:', lv_verbrauch_monat. "Write: / ' Formatiert:', lv_verbrauch_text. ENDIF. ELSE. " Kein Verbrauch vorhanden lv_verbrauch_text = '0'. IF lv_is_debug = abap_true. "Write: / 'Material', iv_matnr, ': KEIN Verbrauch in gt_consumption'. ENDIF. ENDIF. " ===== KNOTEN ZUR AUSGABE HINZUFÜGEN ===== APPEND VALUE #( node_key = iv_matnr parent_key = iv_parent_key matnr = ls_material-matnr menge = iv_menge zzlzcod = ls_material-zzlzcod zzlzcodsort = ls_material-zzlzcodsort level = iv_level verbrauch_text = lv_verbrauch_text ) TO ct_nodes. " ===== DEBUG: ZÄHLE KINDER ===== IF lv_is_debug = abap_true. CLEAR lv_child_count. LOOP AT gt_bom_relations INTO DATA(ls_check) WHERE parent = iv_matnr. ADD 1 TO lv_child_count. ENDLOOP. IF lv_child_count > 0. "Write: / ' Material', iv_matnr, 'hat', lv_child_count, 'Komponenten'. ELSE. "Write: / ' Material', iv_matnr, 'hat KEINE Komponenten (Endprodukt)'. ENDIF. ENDIF. " ===== REKURSION: VERARBEITE ALLE KINDER ===== LOOP AT gt_bom_relations INTO DATA(ls_bom_rel) WHERE parent = iv_matnr. IF lv_is_debug = abap_true. DATA: lv_next_level TYPE i. lv_next_level = iv_level + 1. "Write: / ' → Rekursion für Kind:', ls_bom_rel-child, "'Menge:', ls_bom_rel-menge, "'Level:', lv_next_level. ENDIF. " Rekursiver Aufruf für jedes Kind me->build_graph_nodes( EXPORTING iv_matnr = ls_bom_rel-child iv_parent_key = iv_matnr iv_menge = ls_bom_rel-menge iv_level = iv_level + 1 CHANGING ct_nodes = ct_nodes ). ENDLOOP. " ===== ABSCHLUSS FÜR TOP-MATERIAL ===== IF iv_level = 0 AND lv_is_debug = abap_true. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ BUILD_GRAPH_NODES ABGESCHLOSSEN'. "Write: / '╚════════════════════════════════════════════════════════════╝'. "Write: / 'Anzahl Knoten im Baum:', lines( ct_nodes ). ENDIF. ENDMETHOD. " SORTIMENTSCODE ENDE METHOD display_gozinto_graph. " Zeigt ALLE selektierten TOP-Materialien in EINEM ALV " mit Code-Änderungen und Berechnungsformel TYPES: BEGIN OF ty_tree_extended, top_material TYPE matnr, hierarchy_text TYPE string, matnr TYPE matnr, menge TYPE kmpmg, code_alt TYPE char4, code_neu TYPE char4, zzlzcodsort TYPE char4, verbrauch TYPE string, formel TYPE string, verwendungen TYPE string, level TYPE i, END OF ty_tree_extended. DATA: lt_all_display TYPE STANDARD TABLE OF ty_tree_extended, lt_nodes TYPE tt_nodes, lv_mat_count TYPE i. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ KOMBINIERTE GRAPH-ANZEIGE (ALLE TOP-MATERIALIEN) ║'. "Write: / '╚════════════════════════════════════════════════════════════╝'. " ======================================== " FÜR JEDES SELEKTIERTE MATERIAL " ======================================== LOOP AT s_matnr INTO DATA(ls_matnr_range). IF ls_matnr_range-sign = 'I' AND ls_matnr_range-option = 'EQ'. ADD 1 TO lv_mat_count. DATA(lv_top_mat) = ls_matnr_range-low. "Write: / 'Verarbeite TOP-Material', lv_mat_count, ':', lv_top_mat. " Baue Hierarchie CLEAR lt_nodes. me->build_graph_nodes( EXPORTING iv_matnr = lv_top_mat iv_parent_key = '' iv_menge = 1 iv_level = 0 CHANGING ct_nodes = lt_nodes ). IF lt_nodes IS INITIAL. "Write: / ' -> Keine Hierarchie gefunden'. CONTINUE. ENDIF. " ======================================== " TRENNLINIE EINFÜGEN (außer beim ersten Material) " ======================================== IF lv_mat_count > 1. APPEND VALUE #( top_material = lv_top_mat hierarchy_text = '────────────────────────────────────────────────' level = -1 ) TO lt_all_display. ENDIF. " ======================================== " VERARBEITE JEDEN KNOTEN " ======================================== LOOP AT lt_nodes INTO DATA(ls_node). " *** CODE VORHER/NACHHER *** DATA: lv_code_alt TYPE char4, lv_code_neu TYPE char4. READ TABLE gt_results INTO DATA(ls_result) WITH KEY matnr = ls_node-matnr. IF sy-subrc = 0. lv_code_alt = ls_result-old_code. lv_code_neu = ls_result-new_code. ELSE. lv_code_alt = ls_node-zzlzcod. lv_code_neu = ls_node-zzlzcod. ENDIF. " *** FORMEL BERECHNEN *** DATA: lv_formel TYPE string. CLEAR lv_formel. IF ls_node-matnr CA 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. " Nur für Komponenten (B-Materialien) DATA: lt_parent_info TYPE STANDARD TABLE OF string, lv_total_verb TYPE p DECIMALS 2, lv_a_verb TYPE p DECIMALS 2, lv_parent_count TYPE i, lv_has_a TYPE abap_bool, lv_has_n TYPE abap_bool, lv_has_e TYPE abap_bool. CLEAR: lt_parent_info, lv_total_verb, lv_a_verb, lv_parent_count, lv_has_a, lv_has_n, lv_has_e. " Sammle Parent-Informationen LOOP AT gt_bom_relations INTO DATA(ls_rel_form) WHERE child = ls_node-matnr. ADD 1 TO lv_parent_count. READ TABLE gt_materials WITH KEY matnr = ls_rel_form-parent INTO DATA(ls_parent_mat). IF sy-subrc <> 0. CONTINUE. ENDIF. " Hole Verbrauch READ TABLE gt_consumption WITH KEY matnr = ls_rel_form-parent INTO DATA(ls_cons_form). DATA: lv_parent_verb TYPE p DECIMALS 2. IF sy-subrc = 0 AND ls_cons_form-gsv_korr > 0. lv_parent_verb = ls_cons_form-gsv_korr. ELSE. lv_parent_verb = 1. ENDIF. DATA: lv_gewicht TYPE p DECIMALS 2. lv_gewicht = ls_rel_form-menge * lv_parent_verb. lv_total_verb = lv_total_verb + lv_gewicht. " Prüfe Code-Typ CASE ls_parent_mat-zzlzcod+0(1). WHEN 'A'. lv_has_a = abap_true. DATA: lv_prozent TYPE p DECIMALS 2. DATA(lv_digit) = ls_parent_mat-zzlzcod+1(1). IF lv_digit = '0' AND ls_parent_mat-zzlzcod+2(1) = 'X'. lv_prozent = 100. ELSEIF lv_digit BETWEEN '1' AND '9'. CASE lv_digit. WHEN '0'. lv_prozent = 2. WHEN '1'. lv_prozent = 9. WHEN '2'. lv_prozent = 19. WHEN '3'. lv_prozent = 29. WHEN '4'. lv_prozent = 39. WHEN '5'. lv_prozent = 49. WHEN '6'. lv_prozent = 59. WHEN '7'. lv_prozent = 69. WHEN '8'. lv_prozent = 79. WHEN '9'. lv_prozent = 92. ENDCASE. ELSEIF lv_digit = '0' AND ls_parent_mat-zzlzcod+2(1) = 'T'. lv_prozent = 2. ENDIF. lv_a_verb = lv_a_verb + ( lv_gewicht * lv_prozent / 100 ). DATA: lv_parent_text TYPE string. lv_parent_text = |{ ls_rel_form-parent }({ ls_parent_mat-zzlzcod }):{ lv_parent_verb NUMBER = USER DECIMALS = 0 }×{ lv_prozent }%|. APPEND lv_parent_text TO lt_parent_info. WHEN 'E'. lv_has_e = abap_true. lv_parent_text = |{ ls_rel_form-parent }(E0X):{ lv_parent_verb NUMBER = USER DECIMALS = 0 }|. APPEND lv_parent_text TO lt_parent_info. WHEN 'N'. lv_has_n = abap_true. lv_parent_text = |{ ls_rel_form-parent }(N0X):{ lv_parent_verb NUMBER = USER DECIMALS = 0 }×0%|. APPEND lv_parent_text TO lt_parent_info. ENDCASE. ENDLOOP. " Erstelle Formel-Text IF lv_parent_count > 0. DATA: lv_regel TYPE string. IF lv_has_e = abap_true AND lv_has_n = abap_true. lv_regel = 'E+N→N0X'. lv_formel = |Regel:{ lv_regel } (N dominiert)|. ELSEIF lv_has_e = abap_true AND lv_has_n = abap_false. lv_regel = 'E→E0X'. lv_formel = |Regel:{ lv_regel } (E dominiert)|. ELSEIF lv_has_n = abap_true AND lv_has_a = abap_true AND lv_has_e = abap_false. lv_regel = 'N+A→A0T'. lv_formel = |Regel:{ lv_regel } (fix A0T)|. ELSEIF lv_has_a = abap_true AND lv_has_n = abap_false AND lv_has_e = abap_false. DATA: lv_anteil TYPE p DECIMALS 2. IF lv_total_verb > 0. lv_anteil = ( lv_a_verb / lv_total_verb ) * 100. ELSE. lv_anteil = 0. ENDIF. lv_formel = |A-Anteil={ lv_anteil NUMBER = USER DECIMALS = 1 }% (|. DATA: lv_count TYPE i. LOOP AT lt_parent_info INTO DATA(lv_pinfo). lv_count = lv_count + 1. IF lv_count = 1. lv_formel = |{ lv_formel }{ lv_pinfo }|. ELSE. lv_formel = |{ lv_formel }+{ lv_pinfo }|. ENDIF. IF lv_count >= 3. IF lines( lt_parent_info ) > 3. DATA(lv_rest) = lines( lt_parent_info ) - 3. lv_formel = |{ lv_formel }+{ lv_rest }mehr|. ENDIF. EXIT. ENDIF. ENDLOOP. lv_formel = |{ lv_formel })→{ lv_code_neu }|. ELSEIF lv_has_n = abap_true AND lv_has_a = abap_false AND lv_has_e = abap_false. lv_regel = 'Nur N'. lv_formel = |Regel:{ lv_regel } → N0X|. ELSE. lv_formel = 'Keine Vererbung'. ENDIF. ELSE. lv_formel = 'TOP-Material'. ENDIF. ELSE. lv_formel = 'TOP-Material (keine Vererbung)'. ENDIF. " *** VERWENDUNGEN *** DATA: lv_usage_text TYPE string. CLEAR lv_usage_text. IF ls_node-matnr CA 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. DATA: lt_usage_info TYPE SORTED TABLE OF string WITH UNIQUE KEY table_line, lv_usage_count TYPE i, lv_total_usages TYPE i. CLEAR lt_usage_info. LOOP AT gt_bom_relations INTO DATA(ls_rel_use) WHERE child = ls_node-matnr. READ TABLE gt_consumption WITH KEY matnr = ls_rel_use-parent INTO DATA(ls_cons_use). IF sy-subrc = 0 AND ls_cons_use-gsv_korr > 0 AND ls_rel_use-parent NA 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. DATA: lv_verbrauch_monat TYPE p DECIMALS 0. lv_verbrauch_monat = ls_cons_use-gsv_korr / 12. DATA: lv_usage_entry TYPE string. lv_usage_entry = |{ ls_rel_use-parent }({ lv_verbrauch_monat NUMBER = USER DECIMALS = 0 })|. INSERT lv_usage_entry INTO TABLE lt_usage_info. lv_usage_count = lines( lt_usage_info ). IF lv_usage_count >= 10. EXIT. ENDIF. ENDIF. ENDLOOP. IF lt_usage_info IS NOT INITIAL. lv_usage_text = 'Verwendet in: '. DATA: lv_first TYPE abap_bool VALUE abap_true. LOOP AT lt_usage_info INTO DATA(lv_usage_entry_text). IF lv_first = abap_true. lv_usage_text = |{ lv_usage_text }{ lv_usage_entry_text }|. lv_first = abap_false. ELSE. lv_usage_text = |{ lv_usage_text }, { lv_usage_entry_text }|. ENDIF. ENDLOOP. CLEAR lv_total_usages. LOOP AT gt_bom_relations TRANSPORTING NO FIELDS WHERE child = ls_node-matnr. ADD 1 TO lv_total_usages. ENDLOOP. IF lv_total_usages > 10. DATA: lv_remaining TYPE i. lv_remaining = lv_total_usages - 10. lv_usage_text = |{ lv_usage_text } (+{ lv_remaining } weitere)|. ENDIF. ELSE. lv_usage_text = ''. ENDIF. ENDIF. " *** ERSTELLE ANZEIGE-EINTRAG *** DATA(lv_indent) = repeat( val = ' ' occ = ls_node-level ). DATA(lv_prefix) = COND string( WHEN ls_node-level = 0 THEN '►' WHEN ls_node-level = 1 THEN '├─' WHEN ls_node-level = 2 THEN '│ └─' ELSE '│ └─' ). APPEND VALUE #( top_material = lv_top_mat hierarchy_text = |{ lv_indent }{ lv_prefix } { ls_node-matnr }| matnr = ls_node-matnr menge = ls_node-menge code_alt = lv_code_alt code_neu = lv_code_neu zzlzcodsort = ls_node-zzlzcodsort verbrauch = ls_node-verbrauch_text formel = lv_formel verwendungen = lv_usage_text level = ls_node-level ) TO lt_all_display. ENDLOOP. ENDIF. ENDLOOP. " ======================================== " ANZEIGE ALS EIN ALV " ======================================== IF lt_all_display IS INITIAL. MESSAGE 'Keine Daten für Graph-Anzeige' TYPE 'I'. RETURN. ENDIF. TRY. cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_alv) CHANGING t_table = lt_all_display ). DATA(lo_columns) = lo_alv->get_columns( ). lo_columns->get_column( 'TOP_MATERIAL' )->set_long_text( 'TOP-Material' ). lo_columns->get_column( 'TOP_MATERIAL' )->set_output_length( 12 ). lo_columns->get_column( 'HIERARCHY_TEXT' )->set_long_text( 'Hierarchie' ). lo_columns->get_column( 'HIERARCHY_TEXT' )->set_output_length( 40 ). lo_columns->get_column( 'MATNR' )->set_visible( abap_false ). lo_columns->get_column( 'LEVEL' )->set_visible( abap_false ). lo_columns->get_column( 'MENGE' )->set_long_text( 'Menge' ). lo_columns->get_column( 'CODE_ALT' )->set_long_text( 'Code VORHER' ). lo_columns->get_column( 'CODE_NEU' )->set_long_text( 'Code NACHHER' ). lo_columns->get_column( 'CODE_ALT' )->set_output_length( 10 ). lo_columns->get_column( 'CODE_NEU' )->set_output_length( 10 ). lo_columns->get_column( 'ZZLZCODSORT' )->set_long_text( 'Sort-Code' ). lo_columns->get_column( 'VERBRAUCH' )->set_long_text( 'Verbrauch/Mon' ). lo_columns->get_column( 'FORMEL' )->set_long_text( 'Berechnungsformel' ). lo_columns->get_column( 'FORMEL' )->set_output_length( 60 ). lo_columns->get_column( 'VERWENDUNGEN' )->set_long_text( 'Verwendungen' ). lo_columns->get_column( 'VERWENDUNGEN' )->set_output_length( 80 ). lo_columns->set_optimize( ). lo_alv->get_display_settings( )->set_list_header( |Kombinierte Hierarchie: { lv_mat_count } TOP-Materialien| ). lo_alv->get_functions( )->set_all( ). lo_alv->display( ). CATCH cx_salv_msg. MESSAGE 'Fehler bei kombinierter Graph-Anzeige' TYPE 'E'. ENDTRY. "Write: / ''. "Write: / '╔════════════════════════════════════════════════════════════╗'. "Write: / '║ KOMBINIERTE ANZEIGE ABGESCHLOSSEN ║'. "Write: / '╚════════════════════════════════════════════════════════════╝'. "Write: / 'Anzahl TOP-Materialien:', lv_mat_count. "Write: / 'Anzahl Zeilen im ALV:', lines( lt_all_display ). ENDMETHOD. METHOD display_results. DATA: lo_alv TYPE REF TO cl_salv_table. TRY. cl_salv_table=>factory( IMPORTING r_salv_table = lo_alv CHANGING t_table = gt_results ). " Spaltenoptimierung lo_alv->get_columns( )->set_optimize( abap_true ). " Funktionen aktivieren lo_alv->get_functions( )->set_all( abap_true ). " Anzeige lo_alv->display( ). CATCH cx_salv_msg. MESSAGE 'Fehler bei ALV-Anzeige' TYPE 'E'. ENDTRY. ENDMETHOD. ENDCLASS. *----------------------------------------------------------------------* * Hauptprogramm *----------------------------------------------------------------------* START-OF-SELECTION. "Ersetzte Parameter 251217 DATA(p_batch) = CONV i( 5000 ). DATA(p_maxlv) = CONV i( 4 ). p_mm = abap_false. p_di = abap_true. "Default wie vorher p_gozin = abap_false. "END " Validierungen " IF p_alles = abap_true AND s_matnr[] IS NOT INITIAL. " MESSAGE 'Entweder "Alle Materialien" oder Selektion angeben' TYPE 'E'. " ENDIF. IF p_batch > 50000. MESSAGE 'Batch-Größe zu groß (max. 50000)' TYPE 'W'. p_batch = 50000. ENDIF. " Hauptverarbeitung DATA(lo_processor) = NEW lcl_lifecycle_processor( ). lo_processor->execute( ). END-OF-SELECTION. " Ergebnisanzeige IF p_gozin = 'X'. lo_processor->display_results( ). " Graph-Anzeige für ein ausgewähltes Material " NEU: Graph-Anzeige für ALLE selektierten Materialien (max. 4 Stufen) DATA: lv_material_count TYPE i. LOOP AT s_matnr INTO DATA(ls_matnr_range). " Verarbeite jeden Eintrag im Range IF ls_matnr_range-sign = 'I' AND ls_matnr_range-option = 'EQ'. " Einzelnes Material ADD 1 TO lv_material_count. ""Write: / ''. ""Write: / '================================================'. ""Write: / 'Gozinto-Graph für Material:', ls_matnr_range-low. ""Write: / '================================================'. " lo_processor->display_gozinto_graph( iv_top_material = ls_matnr_range-low ). ELSEIF ls_matnr_range-sign = 'I' AND ls_matnr_range-option = 'BT'. " Range von Materialien SELECT matnr FROM mara INTO @DATA(lv_matnr) WHERE matnr BETWEEN @ls_matnr_range-low AND @ls_matnr_range-high. ADD 1 TO lv_material_count. ""Write: / ''. ""Write: / '================================================'. ""Write: / 'Gozinto-Graph für Material:', lv_matnr. ""Write: / '================================================'. lo_processor->display_gozinto_graph( iv_top_material = lv_matnr ). " Optional: Begrenzung auf z.B. max 10 Materialien IF lv_material_count >= 10. ""Write: / 'WARNUNG: Maximal 10 Graphen angezeigt. Weitere Materialien übersprungen.'. EXIT. ENDIF. ENDSELECT. ENDIF. ENDLOOP. lo_processor->display_gozinto_graph( iv_top_material = ls_matnr_range-low ). IF lv_material_count = 0. ""Write: / 'Keine Materialien für Gozinto-Graph gefunden.'. ELSE. ""Write: / ''. ""Write: / '================================================'. ""Write: / 'Gesamt', lv_material_count, 'Gozinto-Graphen angezeigt.'. ENDIF. ENDIF. *&---------------------------------------------------------------------* *& Form DEACTIVATE_JOB *& Entfernt Startbedingung des Jobs VC_AUFLOESUNG_ZLO *& Nur im produktiven Modus (p_test = abap_false) *&---------------------------------------------------------------------* FORM deactivate_job. DATA: lt_joblist TYPE STANDARD TABLE OF tbtcjob, ls_job TYPE tbtcjob. " Nur im produktiven Modus IF p_test = abap_true. WRITE: / 'Testmodus: Job-Deaktivierung übersprungen'. RETURN. ENDIF. CLEAR: gv_job_was_active, gv_job_count. " Suche freigegebenen Job (Status 'S' = Scheduled/Released) CALL FUNCTION 'BP_JOB_SELECT' EXPORTING jobselect_dialog = abap_false jobname = gc_job_name username = gc_job_user TABLES jobselect_joblist = lt_joblist EXCEPTIONS OTHERS = 1. IF sy-subrc <> 0. WRITE: / 'WARNUNG: Job', gc_job_name, 'nicht gefunden'. RETURN. ENDIF. " Finde freigegebenen Job (Status S = Scheduled) LOOP AT lt_joblist INTO ls_job WHERE status = 'S'. EXIT. ENDLOOP. IF sy-subrc <> 0. WRITE: / 'INFO: Kein freigegebener Job', gc_job_name, 'gefunden'. RETURN. ENDIF. " Sichere Job-Count für spätere Reaktivierung gv_job_count = ls_job-jobcount. gv_job_was_active = abap_true. " Entferne Startbedingung (Job auf 'P' = Planned setzen) CALL FUNCTION 'BP_JOB_MODIFY' EXPORTING jobname = gc_job_name jobcount = gv_job_count new_status = 'P' EXCEPTIONS OTHERS = 1. IF sy-subrc = 0. WRITE: / 'Job', gc_job_name, 'deaktiviert (Startbedingung entfernt)'. ELSE. WRITE: / 'FEHLER: Job', gc_job_name, 'konnte nicht deaktiviert werden'. CLEAR gv_job_was_active. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Form REACTIVATE_JOB *& Setzt Startbedingung des Jobs wieder (01:00 nächster Tag, täglich) *& Nur wenn Job vorher aktiv war und produktiver Modus *&---------------------------------------------------------------------* FORM reactivate_job. DATA: lv_next_date TYPE sy-datum, lv_start_time TYPE sy-uzeit VALUE '010000'. " Nur im produktiven Modus und wenn Job vorher aktiv war IF p_test = abap_true. WRITE: / 'Testmodus: Job-Reaktivierung übersprungen'. RETURN. ENDIF. IF gv_job_was_active = abap_false. WRITE: / 'INFO: Job war nicht aktiv, keine Reaktivierung nötig'. RETURN. ENDIF. " Berechne nächsten Tag lv_next_date = sy-datum + 1. " Setze Startbedingung wieder (täglich um 01:00) CALL FUNCTION 'BP_JOB_MODIFY' EXPORTING jobname = gc_job_name jobcount = gv_job_count new_status = 'S' new_sdlstrtdt = lv_next_date new_sdlstrttm = lv_start_time new_prddays = 1 new_periodic = abap_true EXCEPTIONS OTHERS = 1. IF sy-subrc = 0. WRITE: / 'Job', gc_job_name, 'reaktiviert für', lv_next_date, lv_start_time. ELSE. WRITE: / 'FEHLER: Job', gc_job_name, 'konnte nicht reaktiviert werden!'. WRITE: / 'ACHTUNG: Job muss manuell in SM37 freigegeben werden!'. ENDIF. ENDFORM. *----------------------------------------------------------------------* * Unit-Tests (wenn p_test = 'X') *----------------------------------------------------------------------* AT SELECTION-SCREEN. IF p_test = abap_true AND sy-ucomm = 'ONLI'. " Test-Framework aktivieren PERFORM run_unit_tests. ENDIF. *&---------------------------------------------------------------------* *& Form run_unit_tests *&---------------------------------------------------------------------* FORM run_unit_tests. DATA: lv_test_result TYPE string. ""Write: / '=== UNIT TESTS ==='. " Test 1: Dominanzmatrix lv_test_result = 'Test Dominanzmatrix: '. " Implementierung der Testlogik ""Write: / lv_test_result, 'PASSED'. " Test 2: Zirkularitätsprüfung lv_test_result = 'Test Zirkularität: '. " Implementierung der Testlogik ""Write: / lv_test_result, 'PASSED'. "'KMAT', 'ZHAL', 'ZFER', 'HALB', 'FERT') " AND zzlzcod NE 'ZZZZ'. " Test 3: Batch-Verarbeitung lv_test_result = 'Test Batch-Verarbeitung: '. " Implementierung der Testlogik ""Write: / lv_test_result, 'PASSED'. ""Write: / '=================='. ENDFORM. *&---------------------------------------------------------------------* *& Form PROTECT_VKNR_FROM_UPDATE *& Schützt Materialien vor Update im LZCode-Modus *& *& Parameter iv_mode: *& 'T' = Nur TOP-Materialien (ohne Parents in gt_bom_relations) *& '1' = Alle mit Position 4 = '1' im Code *& 'B' = Beides kombiniert *&---------------------------------------------------------------------* FORM protect_vknr_from_update USING iv_mode TYPE char1. DATA: lv_removed_count TYPE i, lv_code_char4 TYPE char4. " Nur im LZCode-Modus aktiv IF p_lzc <> abap_true. RETURN. ENDIF. " Durchlaufe gt_results und schütze Materialien LOOP AT gt_results ASSIGNING FIELD-SYMBOL(). DATA(lv_protect) = abap_false. " ══════════════════════════════════════════════════════════════ " PRÜFUNG 1: TOP-Material? (keine Parents) " ══════════════════════════════════════════════════════════════ IF iv_mode = 'T' OR iv_mode = 'B'. READ TABLE gt_bom_relations WITH KEY child = -matnr TRANSPORTING NO FIELDS. IF sy-subrc <> 0. " Keine Parents → TOP-Material lv_protect = abap_true. ENDIF. ENDIF. " ══════════════════════════════════════════════════════════════ " PRÜFUNG 2: Position 4 = '1'? " ══════════════════════════════════════════════════════════════ IF iv_mode = '1' OR iv_mode = 'B'. lv_code_char4 = -new_code. IF strlen( lv_code_char4 ) >= 4. IF lv_code_char4+3(1) = '1'. lv_protect = abap_true. ENDIF. ENDIF. ENDIF. " ══════════════════════════════════════════════════════════════ " SCHÜTZEN: changed = false, Code bleibt " ══════════════════════════════════════════════════════════════ IF lv_protect = abap_true. -changed = abap_false. -new_code = -old_code. ADD 1 TO lv_removed_count. ENDIF. ENDLOOP. IF lv_removed_count > 0. WRITE: / 'VKNR-Schutz (Modus:', iv_mode, '):', lv_removed_count, 'Materialien geschützt'. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Globale Tabelle für gesicherte VKNR-Codes *&---------------------------------------------------------------------* TYPES: BEGIN OF ty_vknr_backup, matnr TYPE matnr, old_code TYPE char4, END OF ty_vknr_backup. DATA: gt_vknr_backup TYPE STANDARD TABLE OF ty_vknr_backup. *&---------------------------------------------------------------------* *& Globale Variablen für Job-Steuerung *&---------------------------------------------------------------------* DATA: gv_job_was_active TYPE abap_bool, gv_job_count TYPE tbtcjob-jobcount. CONSTANTS: gc_job_name TYPE tbtcjob-jobname VALUE 'VC_AUFLOESUNG_ZLO', gc_job_user TYPE tbtcjob-authcknam VALUE 'KOI'. *&---------------------------------------------------------------------* *& Form SAVE_VKNR_CODES *& Sichert Codes von Materialien mit Position 4 = '1' *& UND Materialien ohne Buchstaben (nur numerisch = VKNR) *&---------------------------------------------------------------------* FORM save_vknr_codes. DATA: lv_code_char4 TYPE char4, lv_matnr_str TYPE string. CLEAR gt_vknr_backup. LOOP AT gt_materials INTO DATA(ls_mat). DATA(lv_save) = abap_false. " ══════════════════════════════════════════════════════════════ " PRÜFUNG 1: Position 4 = '1' " ══════════════════════════════════════════════════════════════ lv_code_char4 = ls_mat-zzlzcod. IF strlen( lv_code_char4 ) >= 4 AND lv_code_char4+3(1) = '1'. lv_save = abap_true. ENDIF. " ══════════════════════════════════════════════════════════════ " PRÜFUNG 2: MATNR nur numerisch (keine Buchstaben = VKNR) " ══════════════════════════════════════════════════════════════ lv_matnr_str = ls_mat-matnr. CONDENSE lv_matnr_str NO-GAPS. IF lv_matnr_str CO '0123456789'. lv_save = abap_true. ENDIF. " ══════════════════════════════════════════════════════════════ " SICHERN " ══════════════════════════════════════════════════════════════ IF lv_save = abap_true. APPEND VALUE #( matnr = ls_mat-matnr old_code = ls_mat-zzlzcod ) TO gt_vknr_backup. ENDIF. ENDLOOP. IF lines( gt_vknr_backup ) > 0. WRITE: / 'VKNR-Codes gesichert:', lines( gt_vknr_backup ), 'Materialien'. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Form RESTORE_VKNR_CODES *& Rekonstruiert gesicherte VKNR-Codes in gt_results *&---------------------------------------------------------------------* FORM restore_vknr_codes. DATA: lv_restored TYPE i. LOOP AT gt_vknr_backup INTO DATA(ls_backup). " Finde Material in gt_results READ TABLE gt_results ASSIGNING FIELD-SYMBOL() WITH KEY matnr = ls_backup-matnr. IF sy-subrc = 0. " Rekonstruiere: new_code = old_code, changed = false -new_code = ls_backup-old_code. -changed = abap_false. ADD 1 TO lv_restored. ENDIF. ENDLOOP. IF lv_restored > 0. WRITE: / 'VKNR-Codes rekonstruiert:', lv_restored, 'Materialien'. ENDIF. ENDFORM. /// bitte das einbauen, weisst du wie ?Perfekt, jetzt habe ich alle Informationen. Hier ist der komplette, einbaufertige Code: Zusammenfassung der Änderungen Was Wo Beschreibung Globale Variablen Nach DATA: gt_vknr_backup... Job-Steuerungsvariablen FORM deactivate_job Am Ende des Programms Entfernt Startbedingung vor Lauf FORM reactivate_job Am Ende des Programms Setzt Startbedingung nach Lauf Aufruf in execute In Methode execute Aufrufe am Anfang und Ende Code zum Einfügen 1. Globale Variablen (nach Zeile DATA: gt_vknr_backup TYPE STANDARD TABLE OF ty_vknr_backup.) *&---------------------------------------------------------------------* *& Globale Variablen für Job-Steuerung *&---------------------------------------------------------------------* DATA: gv_job_was_active TYPE abap_bool, gv_job_count TYPE tbtcjob-jobcount. CONSTANTS: gc_job_name TYPE tbtcjob-jobname VALUE 'VC_AUFLOESUNG_ZLO', gc_job_user TYPE tbtcjob-authcknam VALUE 'KOI'. 2. Änderung in Methode execute (Klasse lcl_lifecycle_processor) Ersetze die komplette Methode execute durch: METHOD execute. DATA: lv_start_time TYPE timestampl, lv_end_time TYPE timestampl. GET TIME STAMP FIELD lv_start_time. " *** NEU: Job deaktivieren (nur produktiv) *** PERFORM deactivate_job. " Phase 1: Datenladen load_materials( ). me->debug_show_relations( ). load_consumption_data( ). " Phase 5: Berechnungen basierend auf Auswahl durchführen IF p_lzc = abap_true. PERFORM save_vknr_codes. calculate_inheritance( ). PERFORM restore_vknr_codes. IF p_upda = abap_true AND p_test = abap_false. update_database( ). ENDIF. ELSEIF p_sort = abap_true. calculate_sortiment_inhe( ). IF p_upda = abap_true AND p_test = abap_false. update_database_sortiment( ). ENDIF. ENDIF. GET TIME STAMP FIELD lv_end_time. DATA(lv_runtime) = lv_end_time - lv_start_time. " *** NEU: Job reaktivieren (nur produktiv) *** PERFORM reactivate_job. ENDMETHOD. 3. Neue FORMs (am Ende des Programms, vor AT SELECTION-SCREEN) *&---------------------------------------------------------------------* *& Form DEACTIVATE_JOB *& Entfernt Startbedingung des Jobs VC_AUFLOESUNG_ZLO *& Nur im produktiven Modus (p_test = abap_false) *&---------------------------------------------------------------------* FORM deactivate_job. DATA: lt_joblist TYPE STANDARD TABLE OF tbtcjob, ls_job TYPE tbtcjob. " Nur im produktiven Modus IF p_test = abap_true. WRITE: / 'Testmodus: Job-Deaktivierung übersprungen'. RETURN. ENDIF. CLEAR: gv_job_was_active, gv_job_count. " Suche freigegebenen Job (Status 'S' = Scheduled/Released) CALL FUNCTION 'BP_JOB_SELECT' EXPORTING jobselect_dialog = abap_false jobname = gc_job_name username = gc_job_user TABLES jobselect_joblist = lt_joblist EXCEPTIONS OTHERS = 1. IF sy-subrc <> 0. WRITE: / 'WARNUNG: Job', gc_job_name, 'nicht gefunden'. RETURN. ENDIF. " Finde freigegebenen Job (Status S = Scheduled) LOOP AT lt_joblist INTO ls_job WHERE status = 'S'. EXIT. ENDLOOP. IF sy-subrc <> 0. WRITE: / 'INFO: Kein freigegebener Job', gc_job_name, 'gefunden'. RETURN. ENDIF. " Sichere Job-Count für spätere Reaktivierung gv_job_count = ls_job-jobcount. gv_job_was_active = abap_true. " Entferne Startbedingung (Job auf 'P' = Planned setzen) CALL FUNCTION 'BP_JOB_MODIFY' EXPORTING jobname = gc_job_name jobcount = gv_job_count new_status = 'P' EXCEPTIONS OTHERS = 1. IF sy-subrc = 0. WRITE: / 'Job', gc_job_name, 'deaktiviert (Startbedingung entfernt)'. ELSE. WRITE: / 'FEHLER: Job', gc_job_name, 'konnte nicht deaktiviert werden'. CLEAR gv_job_was_active. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Form REACTIVATE_JOB *& Setzt Startbedingung des Jobs wieder (01:00 nächster Tag, täglich) *& Nur wenn Job vorher aktiv war und produktiver Modus *&---------------------------------------------------------------------* FORM reactivate_job. DATA: lv_next_date TYPE sy-datum, lv_start_time TYPE sy-uzeit VALUE '010000'. " Nur im produktiven Modus und wenn Job vorher aktiv war IF p_test = abap_true. WRITE: / 'Testmodus: Job-Reaktivierung übersprungen'. RETURN. ENDIF. IF gv_job_was_active = abap_false. WRITE: / 'INFO: Job war nicht aktiv, keine Reaktivierung nötig'. RETURN. ENDIF. " Berechne nächsten Tag lv_next_date = sy-datum + 1. " Setze Startbedingung wieder (täglich um 01:00) CALL FUNCTION 'BP_JOB_MODIFY' EXPORTING jobname = gc_job_name jobcount = gv_job_count new_status = 'S' new_sdlstrtdt = lv_next_date new_sdlstrttm = lv_start_time new_prddays = 1 new_periodic = abap_true EXCEPTIONS OTHERS = 1. IF sy-subrc = 0. WRITE: / 'Job', gc_job_name, 'reaktiviert für', lv_next_date, lv_start_time. ELSE. WRITE: / 'FEHLER: Job', gc_job_name, 'konnte nicht reaktiviert werden!'. WRITE: / 'ACHTUNG: Job muss manuell in SM37 freigegeben werden!'. ENDIF. ENDFORM. Verhalten Situation Verhalten Testmodus (p_test = 'X') Job wird NICHT deaktiviert/reaktiviert Produktiv (p_test = ' ') Job wird vor Start deaktiviert, nach Ende reaktiviert Job nicht gefunden Warnung, Programm läuft weiter Programmabbruch Job bleibt deaktiviert (gewünscht) Reaktivierung Täglich 01:00 Uhr, periodisch Einbau-Anleitung Globale Variablen nach DATA: gt_vknr_backup... einfügen Methode execute ersetzen (2 neue PERFORM-Aufrufe) Zwei FORMs am Ende einfügen (vor AT SELECTION-SCREEN) Aktivieren und testen (erst mit p_test = 'X')