115 KiB
&--------------------------------------------------------------------- *& 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.
" 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|.
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(<fs_mat_status>). IF <fs_mat_status>-mstae = '99'. " Status 99 = Ausgelaufen → ZZLZCODSORT leeren <fs_mat_status>-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(<fs_mat>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
" *** NEU: Status 99 = Ausgelaufen → Überspringen ***
IF <fs_mat>-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 = <fs_mat>-zzlzcodsort
new_code_sort = ''
changed_sort = COND #( WHEN <fs_mat>-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( <fs_mat>-zzlzcodsort ) >= 4 AND
<fs_mat>-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 <fs_mat>-zzlzcodsort <> lv_new_code.
" Speichere Änderung in Ergebnistabelle
READ TABLE gt_results ASSIGNING FIELD-SYMBOL(<fs_result>)
WITH KEY matnr = lv_matnr.
IF sy-subrc = 0.
<fs_result>-old_code_sort = <fs_mat>-zzlzcodsort.
<fs_result>-new_code_sort = lv_new_code.
<fs_result>-changed_sort = abap_true.
ELSE.
APPEND VALUE #(
matnr = lv_matnr
old_code_sort = <fs_mat>-zzlzcodsort
new_code_sort = lv_new_code
changed_sort = abap_true
) TO gt_results.
ENDIF.
" Update Material mit neuem Code
<fs_mat>-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. ----------------------------------------------------------------------
- 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(<fs_result>).
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 = <fs_result>-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 = <fs_result>-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.
<fs_result>-changed = abap_false.
<fs_result>-new_code = <fs_result>-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.
&--------------------------------------------------------------------- *& 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(<fs_result>)
WITH KEY matnr = ls_backup-matnr.
IF sy-subrc = 0.
" Rekonstruiere: new_code = old_code, changed = false
<fs_result>-new_code = ls_backup-old_code.
<fs_result>-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
- 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'.
-
Ä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.
- 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')