SAP Materialstamm mit BAPI über Excel Tabelle ändern

November 30, 2018 0 Von SAP Guy

Der Fall wird vermutlich in fast allen Unternehmen irgendwann einmal auftreten: Es gibt eine Prozessänderung im SAP und nun muss der Materialstamm gepflegt werden. Häufig werden dabei die Daten in Excel vorbereitet und sollen dann irgendwie in das SAP gelangen. Dazu gibt es dann natürlich diverse Wege wie Batch-Input Mappen, LSMW oder auch einen eigenen Report.

Welcher Weg der Beste ist bleibt wohl immer ein Diskussionsthema. In diesem Beitrag möchte ich einmal zeigen, wie über einen eigenen Report mit BAPIs der Materialstamm geändert werden kann. Ich werde dazu die Produkthierarchie in Materialien einfügen.

Datenquelle CSV aus Excel

Als Datenquelle dient eine CSV Datei aus Exel. In diesem Fall besteht diese nur aus zwei Spalten, der Materialnummer und der Produkthierarchie. Da die Produkthierarchie nur bei den Grunddaten gepflegt werden soll sind keine weiteren Informationen nötig.

Bei der Pflege von Feldern in anderen Sichten müssen natürlich je nach Sicht weitere Org-Daten mitgegeben werden.

Meine CSV-Datei sieht also wie folgt aus:

Materialnummer; Produkthierarchie;

Report zum Ändern der Materialstammdaten

Ich werde den Report hier in einzelnen Schritten erläutern. Am Ende werde ich den gesamten Report noch einmal eintragen.

Es werden diverse Datenfelder benötigt. Diese Speichern hauptsächlich den Dateiinhalt der CSV-Datei sowie die Nachrichten der BAdis um diese später auswerten zu können.
Wichtig ist hierbei die Struktur der CSV-Datei auch in SAP abzubilden. Dazu kann ein Datenelement angelegt werden oder man definiert es über die TYPES-Anweisung direkt im Report. Zweiteres lässt sich im Report schnell ändern, sollte die Struktur aber in anderen Reports ebenfalls zum Einsatz kommen, dann empfiehlt es sich eine Struktur und einen Tabellentyp anzulegen.

TABLES: mara.
 
* Struktur der CSV Datei
TYPES: BEGIN OF csv_struc,
         matnr LIKE mara-matnr,
         prdha LIKE mara-prdha,
       END OF csv_struc.
 
* Datenfelder
DATA:   lt_import    TYPE TABLE OF    csv_struc,          "CSV als ITab
        ls_line      TYPE             csv_struc,          "Einzelne Zeile der CSV
        rawdata      TYPE             truxs_t_text_data,  "Rohdaten der CSV
        lv_filename  TYPE             string,             "Dateiname
        lt_filetable TYPE             filetable,          "Tabelle von Dateinamen
        lf_rc        TYPE             i,                  "Returncode beim Auswählen der dAtei
        ls_file      TYPE             file_table,         "Einzelne Datei
        lt_return    LIKE             bapiret2,           "Rückgabetabelle der Badis
        lt_returnmsg TYPE TABLE OF    bapi_matreturn2,    "Nachrichten aus Badi
        ls_returnmsg TYPE             bapi_matreturn2,    "Einzelne Nachricht für Badi
        lv_matnr     TYPE             matnr,              "Materialnummer
        ls_head      LIKE             bapimathead,        "Kopfdaten Badi
        ls_cldata    LIKE             bapi_mara,          "Detaildaten Badi
        ls_cldatax   LIKE             bapi_marax,         "Detaildaten Badi Änderungsmarkierung
        lo_alv       TYPE REF TO cl_salv_table,          "ALV Objekt
        lo_functions TYPE REF TO cl_salv_functions_list. "ALV Optionen

Als nächstes müssen wir dem Benutzer die Möglichkeit geben eine Datei auszuwählen. Dafür gibt es die Methode “File_open_dialog” der Klasse “cl_gui_frontend_server”. Hier kann man auch schon mal die Dateiendung vorschlagen.

CALL METHOD cl_gui_frontend_services=>file_open_dialog
  EXPORTING
    window_title            = 'CSV Datei auswählen'
    file_filter             = '*.csv'
    multiselection          = abap_false
  CHANGING
    file_table              = lt_filetable
    rc                      = lf_rc
  EXCEPTIONS
    file_open_dialog_failed = 1
    cntl_error              = 2
    error_no_gui            = 3
    not_supported_by_gui    = 4
    OTHERS                  = 5.
* Prüfen ob alles funktioniert hat
IF sy-subrc <> 0.
  MESSAGE ID sy-msgid TYPE 'S' NUMBER sy-msgno
             DISPLAY LIKE 'E'
             WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  EXIT.
ENDIF.

Der “file_open_dialog” ermöglicht es mehrere Dateien gleichzeitig auszuwählen. Dass wollen wir nicht, daher nehmen wir die erste Datei aus der Tabelle und prüfen auch gleich ob überhaupt etwas gefunden wurde.

READ TABLE lt_filetable INTO ls_file INDEX 1.
CHECK sy-subrc = 0.
lv_filename = ls_file-filename.

Nun da die Datei ausgewählt wurde muss sie noch hochgeladen werden. Das Format nach dem Upload lässt sich aber nicht gut verarbeiten, daher konvertieren wir den Inhalt noch in eine Interne Tabelle.

CALL FUNCTION 'GUI_UPLOAD'
  EXPORTING
    filename                = lv_filename
  TABLES
    data_tab                = rawdata
  EXCEPTIONS
    file_open_error         = 1
    file_read_error         = 2
    no_batch                = 3
    gui_refuse_filetransfer = 4
    invalid_type            = 5
    no_authority            = 6
    unknown_error           = 7
    bad_data_format         = 8
    header_not_allowed      = 9
    separator_not_allowed   = 10
    header_too_long         = 11
    unknown_dp_error        = 12
    access_denied           = 13
    dp_out_of_memory        = 14
    disk_full               = 15
    dp_timeout              = 16
    OTHERS                  = 17.
 
* Die Daten werden nun in ein Format umgewandelt mit dem wir arbeiten könne (ITAB)
CALL FUNCTION 'TEXT_CONVERT_CSV_TO_SAP'
  EXPORTING
    i_field_seperator    = ';'
*   i_line_header        =
    i_tab_raw_data       = rawdata
*   i_filename           = 'd:\myfile1.txt'
  TABLES
    i_tab_converted_data = lt_import
  EXCEPTIONS
    conversion_failed    = 1
    OTHERS               = 2.

In einer Schleife die wir über diese zuvor erstellte interne Tabelle laufen lassen werden die Änderungen für den BAPI_MATERIAL_SAVEDATA durchgeführt.
Dabei muss dem BAPI im Kopf mitgeteilt werden, welches Material geändert werden muss und welche Sicht gewünscht ist. In dem Fall hier ist das nur die Grunddatensicht. Anschließend werden die Felder dieser Sicht geändert. Dazu gibt es immer zwei Strukturen. Eine Struktur für die tatsächlichen Inhalte und eine weitere Struktur die auf X endet für eine Markierung der geänderten Felder. Bitte also darauf achten, dass immer beide Strukturen geändert werden.

Zur Sicherheit prüfen wir auch, ob das Material überhaupt existiert. Wenn das nicht der Fall ist, dann fügen wir in der Rückgabetabelle für die BAPI-Nachrichten einen Eintrag hinzu. Über diesen Eintrag können wir später dann die Materialien mit Problemen finden.

LOOP AT lt_import INTO ls_line.
 
* Prüfen ob das Material existiert, könnte aber auch über BAPI_MATERIAL_GET_ALL erfolgen, falls man z.B. mehr Daten braucht
  SELECT SINGLE matnr FROM mara INTO lv_matnr WHERE matnr EQ ls_line-matnr.
  IF sy-subrc EQ 0.
 
* Kopfdaten setzen. Hier wird markiert, welche Sichten geändert werden sollen
    ls_head-material    = ls_line-matnr.
    ls_head-basic_view  = 'X'.
 
* Detaildaten: Hier werden die tatsächlichen Daten eingefügt. Zusätzlich sind diese als "geändert" zu markieren in der X-Tabelle
    ls_cldata-prod_hier  = ls_line-prdha.
    ls_cldatax-prod_hier = 'X'.
 
* Geänderte Daten speichern
    CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
      EXPORTING
        headdata       = ls_head
        clientdata     = ls_cldata
        clientdatax    = ls_cldatax
 
      IMPORTING
        return         = lt_return
      TABLES
 
        returnmessages = lt_returnmsg
 
      .
  ELSE.
*    Wenn das Material nicht gefunden wurde fügen wir eine Nachricht zur Sammlung hinzu
    ls_returnmsg-type = 'E'.
    ls_returnmsg-number = '999'.                         "Damit man später filtern kann
    CONCATENATE 'Material nicht gefunden: ' ls_line-matnr INTO ls_returnmsg-message RESPECTING BLANKS.
    APPEND ls_returnmsg TO lt_returnmsg.
  ENDIF.
 
ENDLOOP.

Damit die Daten auch wirklich geändert werden ist ein Commit erforderlich.

CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.

So könnten die Daten jetzt schon geändert werden. Hilfreich ist aber in vielen Fällen sicherlicht zu erfahren, ob auch alles funktioniert hat. Daher können wir am Ende noch die Nachrichten die der BAPI zurück gibt in einem ALV ausgeben. Dieser lässt sich dann später auswerten.

cl_salv_table=>factory( IMPORTING r_salv_table = lo_alv CHANGING t_table = lt_returnmsg ).
 
lo_functions = lo_alv->get_functions( ).
lo_functions->set_all( abap_true ).
 
lo_alv->display( ).

Das war schon alles.

Der gesamte Report

REPORT zmm_update_ph_by_file.
 
TABLES: mara.
 
 
* Struktur der CSV Datei
TYPES: BEGIN OF csv_struc,
         matnr LIKE mara-matnr,
         prdha LIKE mara-prdha,
       END OF csv_struc.
 
* Datenfelder
DATA:   lt_import    TYPE TABLE OF    csv_struc,          "CSV als ITab
        ls_line      TYPE             csv_struc,          "Einzelne Zeile der CSV
        rawdata      TYPE             truxs_t_text_data,  "Rohdaten der CSV
        lv_filename  TYPE             string,             "Dateiname
        lt_filetable TYPE             filetable,          "Tabelle von Dateinamen
        lf_rc        TYPE             i,                  "Returncode beim Auswählen der dAtei
        ls_file      TYPE             file_table,         "Einzelne Datei
        lt_return    LIKE             bapiret2,           "Rückgabetabelle der Badis
        lt_returnmsg TYPE TABLE OF    bapi_matreturn2,    "Nachrichten aus Badi
        ls_returnmsg TYPE             bapi_matreturn2,    "Einzelne Nachricht für Badi
        lv_matnr     TYPE             matnr,              "Materialnummer
        ls_head      LIKE             bapimathead,        "Kopfdaten Badi
        ls_cldata    LIKE             bapi_mara,          "Detaildaten Badi
        ls_cldatax   LIKE             bapi_marax,         "Detaildaten Badi Änderungsmarkierung
        lo_alv       TYPE REF TO cl_salv_table,          "ALV Objekt
        lo_functions TYPE REF TO cl_salv_functions_list. "ALV Optionen
 
 
* Open File Dialog um die Datei auf dem PC auszuwählen
CALL METHOD cl_gui_frontend_services=>file_open_dialog
  EXPORTING
    window_title            = 'CSV Datei auswählen'
    file_filter             = '*.csv'
    multiselection          = abap_false
  CHANGING
    file_table              = lt_filetable
    rc                      = lf_rc
  EXCEPTIONS
    file_open_dialog_failed = 1
    cntl_error              = 2
    error_no_gui            = 3
    not_supported_by_gui    = 4
    OTHERS                  = 5.
* Prüfen ob alles funktioniert hat
IF sy-subrc <> 0.
  MESSAGE ID sy-msgid TYPE 'S' NUMBER sy-msgno
             DISPLAY LIKE 'E'
             WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  EXIT.
ENDIF.
 
* Dateiname aus Tabelle auslesen
READ TABLE lt_filetable INTO ls_file INDEX 1.
CHECK sy-subrc = 0.
lv_filename = ls_file-filename.
 
* Datei wurde nur ausgewählt, muss aber noch an den SAP Server übertragen werden
CALL FUNCTION 'GUI_UPLOAD'
  EXPORTING
    filename                = lv_filename
  TABLES
    data_tab                = rawdata
  EXCEPTIONS
    file_open_error         = 1
    file_read_error         = 2
    no_batch                = 3
    gui_refuse_filetransfer = 4
    invalid_type            = 5
    no_authority            = 6
    unknown_error           = 7
    bad_data_format         = 8
    header_not_allowed      = 9
    separator_not_allowed   = 10
    header_too_long         = 11
    unknown_dp_error        = 12
    access_denied           = 13
    dp_out_of_memory        = 14
    disk_full               = 15
    dp_timeout              = 16
    OTHERS                  = 17.
 
* Die Daten werden nun in ein Format umgewandelt mit dem wir arbeiten könne (ITAB)
CALL FUNCTION 'TEXT_CONVERT_CSV_TO_SAP'
  EXPORTING
    i_field_seperator    = ';'
*   i_line_header        =
    i_tab_raw_data       = rawdata
*   i_filename           = 'd:\myfile1.txt'
  TABLES
    i_tab_converted_data = lt_import
  EXCEPTIONS
    conversion_failed    = 1
    OTHERS               = 2.
 
* Schleife über die Zeilen. Hier ggf. Überschriftenzeile beachten!
LOOP AT lt_import INTO ls_line.
 
* Prüfen ob das Material existiert, könnte aber auch über BAPI_MATERIAL_GET_ALL erfolgen, falls man z.B. mehr Daten braucht
  SELECT SINGLE matnr FROM mara INTO lv_matnr WHERE matnr EQ ls_line-matnr.
  IF sy-subrc EQ 0.
 
* Kopfdaten setzen. Hier wird markiert, welche Sichten geändert werden sollen
    ls_head-material    = ls_line-matnr.
    ls_head-basic_view  = 'X'.
 
* Detaildaten: Hier werden die tatsächlichen Daten eingefügt. Zusätzlich sind diese als "geändert" zu markieren in der X-Tabelle
    ls_cldata-prod_hier  = ls_line-prdha.
    ls_cldatax-prod_hier = 'X'.
 
* Geänderte Daten speichern
    CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
      EXPORTING
        headdata       = ls_head
        clientdata     = ls_cldata
        clientdatax    = ls_cldatax
*       PLANTDATA      =
*       PLANTDATAX     =
*       FORECASTPARAMETERS         =
*       FORECASTPARAMETERSX        =
*       PLANNINGDATA   =
*       PLANNINGDATAX  =
*       STORAGELOCATIONDATA        =
*       STORAGELOCATIONDATAX       =
*       VALUATIONDATA  =
*       VALUATIONDATAX =
*       WAREHOUSENUMBERDATA        =
*       WAREHOUSENUMBERDATAX       =
*       SALESDATA      =
*       SALESDATAX     =
*       STORAGETYPEDATA            =
*       STORAGETYPEDATAX           =
*       FLAG_ONLINE    = ' '
*       FLAG_CAD_CALL  = ' '
*       NO_DEQUEUE     = ' '
*       NO_ROLLBACK_WORK           = ' '
      IMPORTING
        return         = lt_return
      TABLES
*       MATERIALDESCRIPTION        =
*       UNITSOFMEASURE =
*       UNITSOFMEASUREX            =
*       INTERNATIONALARTNOS        =
*       MATERIALLONGTEXT           =
*       TAXCLASSIFICATIONS         =
        returnmessages = lt_returnmsg
*       PRTDATA        =
*       PRTDATAX       =
*       EXTENSIONIN    =
*       EXTENSIONINX   =
      .
  ELSE.
*    Wenn das Material nicht gefunden wurde fügen wir eine Nachricht zur Sammlung hinzu
    ls_returnmsg-type = 'E'.
    ls_returnmsg-number = '999'.                         "Damit man später filtern kann
    CONCATENATE 'Material nicht gefunden: ' ls_line-matnr INTO ls_returnmsg-message RESPECTING BLANKS.
    APPEND ls_returnmsg TO lt_returnmsg.
  ENDIF.
 
ENDLOOP.
 
* Datenübernahme muss mit Commit bestätigt werden
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
 
cl_salv_table=>factory( IMPORTING r_salv_table = lo_alv CHANGING t_table = lt_returnmsg ).
 
lo_functions = lo_alv->get_functions( ).
lo_functions->set_all( abap_true ).
 
lo_alv->display( ).