Quantcast
Channel: Tricktresor
Viewing all 214 articles
Browse latest View live

Application-Event stört gewaltig…

$
0
0

Ich bekam die Aufgabe, die Transaktion CO41 – Umsetzung Planaufträge um ein paar Funktionen zu erweitern. Nach einigem Hin- und Herüberlegen entschied ich mich dafür, an geeigneter Stelle (Include LCOUPF1N – Routine INIT_FBILD) mittels impliziter Erweiterung einen Docking-Container mit einem Dynamischen Dokument anzubinden. Der Docking Container war schnell erstellt und auch die ersten Zeichen inklusive Drucktaste waren zügig gezaubert.

Leider funktioniert dieser erste Wurf nicht, denn nach Drücken des Druckknopfes wurde nicht meine registrierte Methode angesprungen, sondern es kam die Meldung „Die angeforderte Funktion %_GC 1 ist hier nicht vorgesehen“.

Ich brauchte lange, um herauszufinden, dass die in einigen von der SAP verwendete FBS – Folgebildsteuerung der Grund für die Meldung war (Funktionsbaustein SCREEN_SEQUENCE_CONTROL). Beziehungsweise war das nicht in erster Linie der Grund, sondern der Umstand, dass bei den Dynamischen Dokumenten die zugehörigen Events alle standardmäßig als „application events“ registriert werden. Das hat zur Folge, dass die Ereignissteuerung nicht nur innerhalb der Klasse durchgeführt wird, sondern einen „Umweg“ über das Dynpro macht. Es wird in dem Fall ein Funktionscode ausgelöst, bei dem das PAI – Process After Input durchlaufen wird.

Meistens ist das egal, denn es werden vom Programm in der Regel nur die Funktionscodes abgeprüft, die auch definiert wurden. Man kann also in einer Transaktion „JIFDIJIFG“ eingeben und es passiert nichts. In der CO41 kann man das jedoch nicht, da für jeden Funktionscode in einer Tabelle nachgeprüft wird, wie bei diesem Funktionscode verfahren werden soll. Findet der Funktionsbaustein SCREEN_SEQUENCE_CONTROL keinen Eintrag, dann kommt die oben genannte Meldung.

Lösung

Ich machte mich schon darauf gefasst, die Klasse CL_DD_DOCUMENT abzuleiten, um die Methode DISPLAY, in der die Ereignisregistrierung stattfindet, zu redefinieren. Glücklicherweise ist das HTML-Objekt, in dem das Dynamische Dokument angezeigt wird und für das die Ereignisse registriert werden, öffentlich. Dadurch konnte ich das Ereignis SAPEVENT erneut, aber diesmal ohne APPL_EVENT, registrieren:

document->merge_document( ).
document->display_document( parent = docker ).
DATA(html) = document->html_control.

DATA: myevent_tab TYPE cntl_simple_events,
myevent TYPE cntl_simple_event.
myevent-eventid = html->m_id_sapevent.
myevent-appl_event = ' '.
APPEND myevent TO myevent_tab.
html->set_registered_events( events = myevent_tab ).

Nun konnte ich endlich meine Tasten drücken, ohne dass mir die Folgebildsteuerung in die Quere kam.

Der Beitrag Application-Event stört gewaltig… erschien zuerst auf Tricktresor.


REDUCE ABAP750 FOR x = u IN n = 1 THEN brainf*ck

$
0
0

Hoijoijoi. Ich gebe zu, ich tue mich echt schwer mit den neuen Befehlen im ABAP-Sprachschatz. Besonders die ganz neuen Features im ABAP Release 7.50 (oder 7.40 – ich blick da nicht mehr durch) fordern mich ziemlich (REDUCE, COND, FILTER etc).

Angeregt durch den Artikel von Jerry Wang im neuen SCN über REDUCE habe ich mich mit dem Befehl-REDUCE näher beschäftigt. Über die ABAP-Doku bin ich dann auf die Demoprogramme DEMO_REDUCE* gestolpert und beim DEMO_REDUCE_SIMPLE hängen geblieben.

Das Programm ermittelt mit Hilfe des REDUCE-Befehls die Summe von Werten in einer Tabelle. Das Programm DEMO_REDUCE_COND_ITERATION erzeugt mit Hilfe einer FOR-Schleife zusammengesetzte Texte (1 2 3 4 usw).

Ich wollte dann ein bisserl mit den Features herumspielen und hatte die Idee, einen HTML-Text zusammen zusetzen.

Aus der Tabelle

<HTML>
<BODY>
<P>

wollte ich die einzelnen Elemente plus einem separaten Text zu einem String zusammenfügen. Das funktionierte auch noch sehr einfach:

DATA(text) = REDUCE string(
               INIT html = ``
                FOR command IN html_commands 
                NEXT html = |{ html }{ command }| ) 
            && 'Hallo Welt'.

Eigentlich nur zum Spaß habe ich versucht, ob ich mit einem erneuten && auch ein erneutes REDUCE benutzen kann. Obwohl ich einigermaßen überrascht war, dass es anstandslos funktionierte, wäre ich auch maßlos enttäuscht gewesen, wenn es nicht geklappt hätte… 😉

Der nächste Schritt war dann etwas komplizierter: Ich wollte die einzelnen Tags auch wieder schließen. Natürlich in umgekehrter Reihenfolge. Und mit dem SLASH, der ein Ende-Tag anzeigt. Hier brauchte es dann etliche Versuche und verwirrte Blicke in die Doku um zu dem folgenden Ergebnis zu gelangen:

Und hier der Quelltext dazu:

REPORT zdemo_reduce_simple.

CLASS demo DEFINITION.
  PUBLIC SECTION.
  CLASS-METHODS main.
ENDCLASS.

CLASS demo IMPLEMENTATION.
  METHOD main.

  DATA(html_commands) = VALUE string_table(
                              ( `<html>` )
                              ( `<body>` )
                              ( `<p>` ) ).
  cl_demo_output=>write( html_commands ).


  DATA(text) = REDUCE string(
                INIT html = ``
                 FOR command IN html_commands NEXT html = |{ html }{ command }| )
             && 'Hallo Welt'
             && REDUCE string( INIT html = ``
                FOR n = lines( html_commands )
                THEN n - 1
                WHILE n > 0
                 NEXT html = html && COND #( LET command = html_commands[ n ]
                                              IN WHEN command IS NOT INITIAL
                                                 THEN command(1) && '/' && command+1 ) ).

   cl_demo_output=>write( text ).
   cl_demo_output=>display( ).
 ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.
 demo=>main( ).

Ich lasse es einfach so unkommentiert stehen. Wenn man weiß, was der Ausdruck macht, ist es einigermaßen klar.

Hier der Beitrag von Horst Keller zum Thema ABAP-740 Features: https://blogs.sap.com/2014/09/30/abap-news-for-740-sp08-iteration-expressions/

Table Expressions

Sehr gewöhnungsbedürftig für mich ist der Zusatz um die einzelne Tabellenzeile (die Tabelle hat ja keine Struktur) anzusprechen:

COND #( LET command = html_commands[ n ]
         IN WHEN command IS NOT INITIAL
            THEN command(1) && '/' && command+1 )

Mein erster Ansatz war Folgendes (was aber nicht funktionierte):

html_commands[ n ]-TABLE_LINE

Also bin ich zum COND-Ausdruck gekommen. Hier finde ich verwirrend, dass anscheinend zwingend eine WHEN-Klausel angegeben werden muss. Vielleicht gibt es auch eine einfachere Alternative?! Bestimmt. Lasst es mich gerne wissen.

Link zur Horst Kellers Blog: https://blogs.sap.com/2013/05/29/abap-news-for-release-740-table-expressions/

 

Ein komisches Gefühl, auf ein mal wieder Anfänger zu sein…

Der guten alten Zeiten Willen noch mal das Coding mit ABAPvor740. Viel Länger ist es auch nicht. Und ob die Programmierung mit ABAP740 eleganter oder besser ist, wage ich zu bezweifeln.

DATA text TYPE string.
LOOP AT html_commands INTO DATA(command).
  text = text && command.
ENDLOOP.
text = text && 'Hallo Welt'.

DATA line TYPE i.
line = lines( html_commands ).
DO lines( html_commands ) TIMES.
  DATA(cmd) = html_commands[ line ].
  text = text && cmd(1) && '/' && cmd+1.
  SUBTRACT 1 FROM line.
ENDDO.

Der Beitrag REDUCE ABAP750 FOR x = u IN n = 1 THEN brainf*ck erschien zuerst auf Tricktresor.

Ausnahmen mit T100-Nachricht [ABAP750]

$
0
0

Die Verwendung von T100-Nachrichten mit Klassen basierten Ausnahmen ist von Beginn an krampfig. Alt und Neu passte immer irgendwie nicht so richtig zusammen. Paul Hardy hat sich in seinem sehr guten Buch ABAP To The Future dafür ausgesprochen, ausschließlich die neue Variante – also ohne T100-Nachricht zu verwenden. Es sprechen aber im Kampf mit den täglichen Programmiermonstern viele Dinge dafür, die Exceptions mit T100-Nachricht zu verwenden.

Umständlich

Die Verwendung von Klassen basierten Ausnahmen in Verbindung mit T100-Nachrichten ist äußerst umständlich gewesen:

DATA exc_t100 TYPE scx_t100key.

 TRY.
     exc_t100-msgid = 'OO'.
     exc_t100-msgno = '000'.
     exc_t100-attr1 = 'Test 1'.
     RAISE EXCEPTION TYPE cx_demo_t100 EXPORTING textid = exc_t100.
   CATCH cx_demo_t100 INTO DATA(error).
     MESSAGE error TYPE 'I'.
 ENDTRY.

Ab ABAP740 ist es möglich, die T100-Struktur direkt mit VALUE zu füllen:

RAISE EXCEPTION TYPE cx_demo_t100
   EXPORTING
     textid = VALUE scx_t100key( 
                     msgid = 'OO'
                     msgno = '000'
                     attr1 = 'Test 2' ).

Beide Varianten haben jedoch den Nachteil, dass die Aufbereitung der Meldung mit GET_TEXT nicht korrekt ist:

Die &-Parameter sollten eigentlich nicht vorhanden sein. Meine Meinung nach handelt es sich dabei um einen Fehler in Methode CL_MESSAGE_HELPER->SET_SINGLE_MSG_VAR in der bei der Zuweisung der Attribute beim Assign die Ausnahme cx_sy_assign_cast_illegal_cast falsch interpretiert wird. Das Attribut wird dann extra mit den &-Zeichen aufgebaut:

CONCATENATE '&' arg '&' INTO target.

Rettung in Sicht

Als ich mich auf die Suche nach einer Lösung gemacht habe, bin ich über den Blog Post von Horst Keller gestoßen: ABAP News for Release 7.50 – Converting Messages into Exceptions. In diesem Beitrag beschreibt Horst die Lösung, die ab Release ABAP750 angewendet werden soll:

RAISE EXCEPTION TYPE cx_demo_t100
   MESSAGE ID 'OO'
   NUMBER '000'
   WITH 'Test 3'.

Hierdurch wird die korrekte Ausgabe erzeugt:

Allerdings scheint das erst für Ausnahmen zu gehen, die auch in Release ABAP750 erzeugt wurden. Für die bisher verwendete Ausnahmeklasse CX_DEMO_T100 wird jedenfalls die Meldung ausgegeben:

Die Ausnahmeklasse muss das Interface „IF_T100_DYN_MSG“ haben, um den Zusatz „WITH“ verwenden zu können.

 

Der Beitrag Ausnahmen mit T100-Nachricht [ABAP750] erschien zuerst auf Tricktresor.

Switch für Ausnahmen eines Funktionsbausteins [ABAP740]

$
0
0

Seit Release ABAP740 gibt es die Anweisung SWITCH. Sehr anschaulich beschrieben im Blog von Horst Keller: ABAP News for Release 7.40 – Constructor Operators COND and SWITCH

Was mich bei Funktionsbausteinen schon immer gestört hat ist, dass man bei der sinnvollen Protokollierung von Returncodes von Funktionsbausteinen umständliche CASE-Konstrukte benötigt. Natürlich nur, wenn der Funktionsbaustein bei einem Fehler lediglich RAISE EXCEPTION verwendet aber nicht MESSAGE RAISING. Und Ersteres ist leider sehr häufig der Fall.

Mit dem neuen Feature SWITCH jedenfalls lässt sich ein Konstrukt bauen, das immer noch nicht schön ist, jedoch einigermaßen gut verwendbar. Hier am Beispiel von Funktionsbaustein CO_RU_CONFIRMATION, der schön viele Ausnahmen hat:

"Rückmeldung buchen
 CALL FUNCTION 'CO_RU_CONFIRMATION'
   EXPORTING
     afrud_imp = ls_afrud
     aktyp_imp = 'H'
     commit_flag = 'X'
     no_dialog_flag = 'X'
     rueck_imp = ls_afrud-rueck
     split_imp = ls_afrud-split
     call_from_list = ' '
   EXCEPTIONS
     confirmation_not_allowed = 1
     conf_canceled = 2
     different_key = 3
     interrupt_by_user = 4
     key_not_defined = 5
     material_data_not_found = 6
     missing_authority = 7
     new_status_not_possible = 8
     not_allowed = 9
     not_found = 10
     operation_not_found = 11
     operation_not_selectable = 12
     order_already_locked = 13
     order_category_not_valid = 14
     order_deleted = 15
     order_not_found = 16
     order_without_operation = 17
     predec_not_confirmed = 18
     sequence_not_found = 19
     table_entry_not_found = 20
     unit_conversion_not_possible = 21
     conf_data_false = 22
     conf_date_in_future = 23
     standard_conf_not_possible = 24
     work_center_not_found = 25
     conversion_error = 26
     order_data_not_found = 27
     wrong_aktyp = 28
     split_not_found = 29
     OTHERS = 30.
 IF sy-subrc = 0.
   COMMIT WORK.
 ELSE.
   data(subrc) = sy-subrc.
   "Fehler
   RAISE EXCEPTION TYPE zcx_hkv_pp_mig_general
         MESSAGE ID 'OO'
         NUMBER '000'
         WITH 'Fehler CO_RU_CONFIRMATION:'
              |{ subrc }|
           SWITCH #( subrc
 WHEN 1 THEN 'confirmation_not_allowed '
 WHEN 2 THEN 'conf_canceled '
 WHEN 3 THEN 'different_key '
 WHEN 4 THEN 'interrupt_by_user '
 WHEN 5 THEN 'key_not_defined '
 WHEN 6 THEN 'material_data_not_found '
 WHEN 7 THEN 'missing_authority '
 WHEN 8 THEN 'new_status_not_possible '
 WHEN 9 THEN 'not_allowed '
 WHEN 10 THEN 'not_found '
 WHEN 11 THEN 'operation_not_found '
 WHEN 12 THEN 'operation_not_selectable '
 WHEN 13 THEN 'order_already_locked '
 WHEN 14 THEN 'order_category_not_valid '
 WHEN 15 THEN 'order_deleted '
 WHEN 16 THEN 'order_not_found '
 WHEN 17 THEN 'order_without_operation '
 WHEN 18 THEN 'predec_not_confirmed '
 WHEN 19 THEN 'sequence_not_found '
 WHEN 20 THEN 'table_entry_not_found '
 WHEN 21 THEN 'unit_conversion_not_possible '
 WHEN 22 THEN 'conf_data_false '
 WHEN 23 THEN 'conf_date_in_future '
 WHEN 24 THEN 'standard_conf_not_possible '
 WHEN 25 THEN 'work_center_not_found '
 WHEN 26 THEN 'conversion_error '
 WHEN 27 THEN 'order_data_not_found '
 WHEN 28 THEN 'wrong_aktyp '
 WHEN 29 THEN 'split_not_found '
 WHEN 30 THEN 'OTHERS' ).

Zusammen mit dem Blockmarkiermodus (ALT-Taste während der Markierung des Quelltextes gedrückt halten) lässt sich auf diese Weise einfach eine saubere Protokollierung programmieren. Leider trotzdem sehr schade, dass man nicht mit Hilfe eines ABAP-Befehls den Text bzw. die Ausnahme erhalten kann.

PS: Der Zusatz MESSAGE zur Anweisung RAISE EXCEPTION ist erst ab Release ABAP750 vorhanden… Siehe Beitrag Ausnahmen mit T100-Nachricht

Der Beitrag Switch für Ausnahmen eines Funktionsbausteins [ABAP740] erschien zuerst auf Tricktresor.

Controls stapeln

$
0
0

Heute ist mir wieder eine kleine Spielerei unter die Finger gekommen, die einerseits wichtige Grundlagen zeigt und andererseits eine nette Spielerei ist, die durchaus einen Nutzwert hat. Es geht um die Anzeige und Steuerung von Controls.

Um ein Control anzeigen zu können, benötigt man einen Container. In diesem Beispiel verwende ich einen Docking-Container.

go_dock = NEW #( side = cl_gui_docking_container=>dock_at_right ratio = 80 ).

In diesen Container hänge ich ein Text-Control.

go_text = NEW #( parent = go_dock ).

Allerdings ist das noch nichts Besonderes.

Controls stapeln

Besonders wird es, wenn ich noch ein ALV-Grid und noch ein Picture-Control in den gleichen Container packe.

" ALV-Grid
go_grid = NEW #( i_parent = go_dock ).
go_grid->set_table_for_first_display(
  EXPORTING i_structure_name = 'T000'
  CHANGING it_outtab = gt_data_alv ).

" Picture
go_pic = NEW cl_gui_picture( parent = go_dock ).

Die Controls sind nun gewissermaßen gestapelt. Sie liegen übereinander in dem Container. Das zuletzt instantiierte Control wird angezeigt. Die anderen Controls sind jedoch noch da! Und sie sind auch nutzbar. Man muss lediglich die jeweils darüber liegenden Controls auf „nicht sichtbar“ stellen.

Jedes Control hat die Methode SET_VISIBLE mit der man die Sichtbarkeit eines Controls steuern kann (Vererbung von CL_GUI_CONTROL). Jeder Container hat übrigens ebenfalls diese Eigenschaft (denn auch diese erben von CL_GUI_CONTROL)! Das heißt, es kann auch ein Docking-Container komplett ausgeblendet werden, ohne dass er wirklich „weg“ ist.

In seltenen Fällen kann man sich diesen Umstand zu Nutze machen. Bei diesem Trick verwende ich ein ähnliches Verfahren: Werte aus Excel per DOI (unsichtbar). Hier wird nur nicht der Container unsichtbar geschaltet, sondern das Control wird an ein Standard-Dynpro gehängt, dass nicht angezeigt wird.

Man kann also dadurch, dass man nur das gewünschte Control auf „sichtbar“ und alle anderen auf „unsichtbar“ stellt, zwischen den einzelnen Control hin- und her schalten. Es ist dementsprechend nicht notwendig, das im Container befindliche Control zu zerstören und das neue aufzubauen um einen Wechsel zu realisieren.

Beliebter Fehler

Den Zustand, den ich hier bewusst und mit voller Absicht herbei führe, ist wahrscheinlich schon häufig die Ursache vieler verzweifelter Stunden im Debugger und einiger grauer Haare gewesen. Häufig passiert es nämlich (nicht nur Anfängern, sondern auch Profis!), dass man ein und dasselbe Control mehrfach instantiiert und dem gleichen Container zuordnet. Das macht SAP auch klaglos mit und stellt die neuen Control-Instanzen immer wieder in den Container hinein. Sichtbar ist einzig und allein das zuerst erzeugte Control.

Das ist genau das tückische daran, denn durch diesen Umstand ergeben sich eine Vielzahl von Symptomen, die man sich auch nach stundenlangem Debugging häufig nicht erklären kann:

  • Geänderte Daten werden nicht im ALV-Grid angezeigt
  • Datenänderungen werden vom ALV nicht in die interne Tabelle übernommen
  • Doppelklick funktioniert nicht mehr
  • Änderungen am Control werden nicht sichtbar (geändertes Bild, Icon, aktualisierte Website, …)

Sofern man daran denkt, dass der Fehler einer Mehrfach-Instantiierung vielleicht vorliegen könnte, kann man sehr leicht prüfen, ob das wirklich der Fall ist. Jeder Container hat das Attribut CHILDREN. In dieser Tabelle werden die dem Container zugeordneten Controls verwaltet:

Wenn man in seinem Programm zwar den Container nur einmal erzeugt, aber bei jedem Tastendruck (PAI) eventuell ein neues Control, dann könnte es so aussehen, wie hier:

TIPP
In den meisten Fällen ist es sinnvoll und ausreichend, wenn man abfragt, ob der Container bereits erzeugt wurde. Falls er noch nicht erzeugt wurde (Programmstart etc.), dann erzeugt man den Container und auch gleich das Control.

Häufig treten solche Fehler auf, wenn man Codezeilen von einer Routine oder Methode in ein Programm-Ereignis kopiert oder umgekehrt. Auf einmal befindet sich der Codeabschnitt, der vorher nur einmal aufgerufen wurde, an einer Stelle im Programm, die mehrmals durchlaufen wird.

Screenshots

Die Anzeige wird über die entsprechenden Radiobuttons gesteuert.

Code

REPORT zz_switch_controls.


*== Data
DATA gt_data_alv TYPE STANDARD TABLE OF t000 WITH NON-UNIQUE DEFAULT KEY.
DATA go_dock TYPE REF TO cl_gui_docking_container.
DATA go_text TYPE REF TO cl_gui_textedit.
DATA go_grid TYPE REF TO cl_gui_alv_grid.
DATA go_pic TYPE REF TO cl_gui_picture.

*== Selektionsbild
PARAMETERS: rb_text RADIOBUTTON GROUP rb1 DEFAULT 'X' USER-COMMAND space,
 rb_grid RADIOBUTTON GROUP rb1,
 rb_pic RADIOBUTTON GROUP rb1.

AT SELECTION-SCREEN.
 "steuern der controls
  CASE 'X'.
    WHEN rb_grid. 
      go_text->set_visible( space ).
      go_pic->set_visible( space ).
      go_grid->set_visible( 'X' ).
    WHEN rb_text.
      go_text->set_visible( 'X' ).
      go_pic->set_visible( space ).
      go_grid->set_visible( space ).
    WHEN rb_pic.
      go_text->set_visible( space ).
      go_pic->set_visible( 'X' ).
      go_grid->set_visible( space ).
  ENDCASE.

INITIALIZATION.

*== Docker
 go_dock = NEW #( side = cl_gui_docking_container=>dock_at_right ratio = 80 ).

*== Textedit
 go_text = NEW #( parent = go_dock ).

*== ALV-Grid
 SELECT *
 INTO TABLE gt_data_alv
 FROM t000.

 go_grid = NEW #( i_parent = go_dock ).
 go_grid->set_table_for_first_display(
     EXPORTING i_structure_name = 'T000'
     CHANGING it_outtab = gt_data_alv ).

*== Picture
 go_pic = NEW cl_gui_picture( parent = go_dock ).
 go_pic->load_picture_from_sap_icons( icon_booking_ok ).
 go_pic->set_display_mode( cl_gui_picture=>display_mode_fit ).
 

Der Beitrag Controls stapeln erschien zuerst auf Tricktresor.

Preisfindung im Kundenauftrag von außen anstossen

$
0
0

In diesem Beitrag zeige ich dir, wie du die Preisfindung eines Kundenauftrags neu ausführen lassen kannst. Das grundsätzliche Verfahren sieht so aus:

  1. Userexit in SAPMV45A anpassen
  2. Parameter setzen
  3. BAPI aufrufen
  4. Parameter zurücknehmen

Um die Preisfindung von außen triggern zu können, musst du Änderungen im Programm SAPMV45A durchführen. Zuerst benötigst du jedoch die Möglichkeit, einen Parameter zur Laufzeit zu setzen, der dann im SAPMV45A abgefragt werden kann. Das kann gut über eine der beiden Methoden erfolgen:

  1. EXPORT TO MEMORY und IMPORT FROM MEMORY
  2. Öffentliches Attribut der eigenen globalen Klasse

Anlage der globalen Klasse

Als erstes musst du eine Klasse anlegen mit der die neue Preisfindung durchgeführt werden soll. In meinem Beispiel heißt sie ZCL_SD_NP (New Pricing).

Lege das öffentliche Klassenattribut KNPRS vom Typ KNPRS an (static). Das ist die Preisfindungsart, mit der die Art der neuen Preisfindung gesteuert werden kann.

Quelltext (relevanter Teil ) in MV45AFZB:

FORM userexit_new_pricing_vbap CHANGING new_pricing.
  IF zcl_sd_np=>knprs IS NOT INITIAL.  
     new_pricing = zcl_sd_np=>knprs.
   ENDIF.
ENDFORM.                    "USEREXIT_NEW_PRICING_VBAP
FORM userexit_new_pricing_vbkd CHANGING new_pricing.
  IF zcl_sd_np=>knprs IS NOT INITIAL. 
     new_pricing = zcl_sd_np=>knprs.
   ENDIF.
ENDFORM.                    "USEREXIT_NEW_PRICING_VBKD

Quelltext Klasse

Nun brauchen wir noch die Methode TRIGGER_NEW_PRICING

DATA: 
  ls_bapisdh1x  TYPE bapisdh1x,
  lt_pos        TYPE STANDARD TABLE OF bapisditm WITH NON-UNIQUE DEFAULT KEY,
  lt_posx       TYPE STANDARD TABLE OF bapisditmx WITH NON-UNIQUE DEFAULT KEY,
  lt_return     TYPE bapiret2_t.
FIELD-SYMBOLS: 
  <ls_pos>      LIKE LINE OF lt_pos,
  <ls_posx>     LIKE LINE OF lt_posx.


CLEAR ct_bapiret2.
knprs = iv_knprs.
*--------------------------------------------------------------------*
* Get all positions to be redermined
*--------------------------------------------------------------------*
SELECT posnr AS itm_number werks AS plant
INTO CORRESPONDING FIELDS OF TABLE lt_pos
FROM vbap
WHERE vbeln = iv_vbeln_va.

LOOP AT lt_pos ASSIGNING <ls_pos>.
APPEND INITIAL LINE TO lt_posx ASSIGNING <ls_posx>.
<ls_posx>-itm_number = <ls_pos>-itm_number.
<ls_posx>-updateflag = 'U'.
ENDLOOP.

ls_bapisdh1x-updateflag = 'U'.
CALL FUNCTION 'BAPI_SALESORDER_CHANGE'
  EXPORTING
    salesdocument    = iv_vbeln_va
    order_header_inx = ls_bapisdh1x
  TABLES
    return           = ct_bapiret2
    order_item_in    = lt_pos
    order_item_inx   = lt_posx
  EXCEPTIONS
    ERROR_MESSAGE = 1.

CLEAR knprs.  " Only once

TRY.
    DATA(ls_return) = ct_bapiret2[ type = 'E' ].
    CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    RAISE EXCEPTION TYPE zcx_my_exception.
  CATCH CX_SY_ITAB_LINE_NOT_FOUND.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
ENDTRY.

Durch Aufruf der Methode ZCL_SD_NP=>TRIGGER_NEW_PRICING( … ) kannst du nun einen Beleg dazu bewegen, eine neue Preisfindung durchzuführen.

Der Beitrag Preisfindung im Kundenauftrag von außen anstossen erschien zuerst auf Tricktresor.

Komponenten einem Fertigungsauftrag hinzufügen

$
0
0

Für den Fertigungsaufträge sind die BAPIs leider sehr rar gesät und man muss auf andere Bausteine ausweichen. Um einem Auftrag Komponenten hinzuzufügen, habe ich nur den Baustein CO_XT_COMPONENT_ADD gefunden. Die CO_XT-Funktionsbausteine sind zwar prinzipiell „extern“ und in der Funktionsgruppe „APIs Fertigungsauftrag“, jedoch sind die Bausteine allesamt sehr mit Vorsicht zu genießen.

Das folgende Coding fügt einem Fertigungsauftrag eine Komponente (Materialnummer) hinzu.

Code

"Lokale Daten
DATA ls_return TYPE coxt_bapireturn.
DATA lt_return TYPE coxt_t_bapireturn.
DATA lv_error_occurred TYPE boolean.
DATA ls_resbd_created TYPE resbd.
DATA lt_resbt_exp TYPE STANDARD TABLE OF resbb.
DATA lv_posnr TYPE positionno.

DATA ls_quan TYPE coxt_s_quantity.
DATA ls_stor_loc TYPE coxt_s_storage_location.
DATA ls_stor_loc_x TYPE coxt_s_storage_locationx.

ls_quan-quantity    = menge.
ls_quan-uom         = meins.

ls_stor_loc-werks   = werks.
ls_stor_loc-lgort   = lgort.
ls_stor_loc_x-werks = abap_true.
ls_stor_loc_x-lgort = abap_true.

"Komponente hinzufügen
CALL FUNCTION 'CO_XT_COMPONENT_ADD'
  EXPORTING
    is_order_key         = aufnr
    i_material           = matnr
    is_requ_quan         = ls_quan
    i_operation          = 1
    is_storage_location  = ls_stor_loc
    is_storage_locationx = ls_stor_loc_x
    i_postp              = 'L' "Lagerposition
    i_posno              = lv_posnr
  IMPORTING
    es_bapireturn        = ls_return
    e_error_occurred     = lv_error_occurred
    es_resbd_created     = ls_resbd_created
  TABLES
    resbt_exp            = lt_resbt_exp.

IF lv_error_occurred = abap_false.
*== PRE-Commit
  CALL FUNCTION 'CO_XT_ORDER_PREPARE_COMMIT'
    TABLES
      et_bapireturn = lt_return.

"Keine Fehler!
  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
ELSE.
"Fehlerbehandlung
ENDIF.

Fehlende Positionsnummer

Der Baustein hat leider einen kleinen Schönheitsfehler: Die Positionsnummer (RSB-POSNR) wird leider nicht gesetzt und kann auch nicht ohne weiteres geändert werden. Um die Positionsnummer trotzdem ändern zu können, habe ich zwei Lösungen gefunden:

  1. Die Änderung der internen Tabelle RESB_BT  über einen Dirty-Assign
  2. Änderung über die Standardbausteine
    • CO_BT_RESB_READ_WITH_KEY
    • CO_BT_RESB_GET_LAST_POSNR
    • CO_BT_RESB_UPDATE

Die Änderung muss nach dem CO_XT_COMPONENT_ADD und vor dem Commit erfolgen.

Positionsnummer ändern über Dirty-Assign

*--------------------------------------------------------------------*
* set item number
*--------------------------------------------------------------------*
  TYPES: BEGIN OF ts_resb_bt.
    INCLUDE TYPE resbb.
    TYPES: indold LIKE sy-tabix,
    no_req_upd LIKE sy-datar,
  END OF ts_resb_bt.

  TYPES tt_resb_bt TYPE TABLE OF ts_resb_bt.
  FIELD-SYMBOLS <lt_resb_bt> TYPE tt_resb_bt.
  FIELD-SYMBOLS <ls_resb_bt> TYPE ts_resb_bt.

  ASSIGN ('(SAPLCOBC)RESB_BT[]') TO <lt_resb_bt>.
  LOOP AT <lt_resb_bt> ASSIGNING <ls_resb_bt>.
    IF <ls_resb_bt>-posnr IS INITIAL.
      <ls_resb_bt>-posnr = CONV numc04( <ls_resb_bt>-rspos * 10 ).
    ENDIF.
  ENDLOOP.

Änderung der Positionsnummer über Funktionsbausteine

DATA resbd_exp TYPE resbd.
 DATA posnr_max TYPE tcn41-posnr_mat.
 DATA index_exp TYPE sy-tabix.
 DATA vbkz_exp  TYPE resbb-vbkz.
 DATA nfgrp_exp TYPE resbd-nfgrp.
 DATA posnr_exp TYPE resbd-posnr.

 CALL FUNCTION 'CO_BT_RESB_READ_WITH_KEY'
   EXPORTING
     rsart_imp = ls_resbd-rsart
     rsnum_imp = ls_resbd-rsnum
     rspos_imp = ls_resbd-rspos
   IMPORTING
     index_exp = index_exp " Index interner Tabellen
     posnr_exp = posnr_exp " Nummer der Stücklistenposition
     resbd_exp = resbd_exp " Reservierung/Sekundärbedarf
     nfgrp_exp = nfgrp_exp " Ein-/Auslaufdaten: Nachfolgegruppe
     vbkz_exp  = vbkz_exp " Verbuchungskennzeichen
   EXCEPTIONS
     not_found = 1
     OTHERS    = 2.

 CALL FUNCTION 'CO_BT_RESB_GET_LAST_POSNR'
   EXPORTING
     aufpl     = aufpl
     aplzl     = aplzl
   IMPORTING
     posnr_max = posnr_max. 

 resbd_exp-posnr = posnr_max + 10.

 CALL FUNCTION 'CO_BT_RESB_UPDATE'
   EXPORTING
     resb_new  = resbd_exp
     tabix_old = index_exp.

Dialog oder nicht Dialog?

Ein weiteres Problem könnte der Baustein CO_XT_ORDER_PREPARE_COMMIT machen, denn bei der Änderung des Fertigungsauftrags können Popups erscheinen, die vom Anwender bestätigt werden müssen.

Wenn man Dialoge verhindern möchte, dann muss der Update-Baustein CO_ZV_ORDER_POST direkt aufgerufen werden. Dieser hat einen Parameter NO_DIALOG, den man entsprechend mit X besetzen kann.

 

 

Der Beitrag Komponenten einem Fertigungsauftrag hinzufügen erschien zuerst auf Tricktresor.

Vorgang im Fertigungsauftrag anlegen

$
0
0

BAPIs sollten eigentlich programmtechnisch das möglich machen, was auch im Dialog möglich ist. Leider gibt es sehr viele Unterschiede zwischen BAPI und entsprechender Dialog-Transaktion.

Im Produktionsumfeld ist es so, dass ein Fertigungsauftrag gar nicht komplett mit Hilfe von BAPIs angelegt werden kann. Die Anlage bzw. Änderung muss in mehrere Schritten erfolgen. Normalerweise reicht es dem BAPI_PRODORD_CREATE unter Angabe einer Materialnummer und eines Werkes um einen Fertigungsauftrag anzulegen.

Aber natürlich gibt es immer Umstände, die eine gezielte Anlage oder Manipulation des Fertigungsauftrags notwendig machen. In diesem Beispiel zeige ich die Anlage eines Vorgangs. Hierfür gibt es leider keinen BAPI und ich verwende den Baustein CO_SE_PRODORD_CHANGE, da dieser problemlos separat aufzurufen war. Es gibt noch den Baustein CO_XT_OPERATION_CHANGE, der jedoch Vorbereitungen mit anderen CO_XT-Bausteinen benötigt.

Code

"Lokale Daten
 DATA lv_aufnr TYPE aufnr.
 DATA ls_return TYPE bapiret2.
 DATA lt_operations TYPE cose_t_operation.
 DATA ls_operation TYPE cose_s_operation.
 DATA ls_header TYPE cose_s_header.

"Vorgangsdaten
 ls_operation-standard_value_01 = data-vgw01.
 ls_operation-standard_value_01_unit = data-vge01.
 ls_operation-standard_value_02 = data-vgw02.
 ls_operation-standard_value_02_unit = data-vge02.
 ls_operation-short_text = data-vorgangstext.

 ls_operation-sequence = space.
 ls_operation-operation = data-vornr.
 ls_operation-work_center = data-arbpl.
 ls_operation-control_key = data-steus. 
 ls_operation-earliest_end_exec_date = data-endtermin.
 ls_operation-earliest_end_exec_time = '210000'.
 ls_operation-base_quantity = data-menge.
 ls_operation-dispatch_indicator = abap_false.
 ls_operation-user_field_02_character = data-xyz.

 ls_operation-standard_value_01_x = abap_true.
 ls_operation-standard_value_01_unit_x = abap_true.
 ls_operation-standard_value_02_x = abap_true.
 ls_operation-standard_value_02_unit_x = abap_true.
 ls_operation-work_center_x = abap_true.
 ls_operation-earliest_end_exec_date_x = abap_true.
 ls_operation-earliest_end_exec_time_x = abap_true.
 ls_operation-short_text_x = abap_true.
 ls_operation-base_quantity_x = abap_true.
 ls_operation-user_field_02_character_x = abap_true.
 APPEND ls_operation TO lt_operations.

 SET UPDATE TASK LOCAL.

"Anlage/ Änderung des Vorgangs
 CALL FUNCTION 'CO_SE_PRODORD_CHANGE'
   EXPORTING
     iv_order_number = lv_aufnr
     is_header = ls_header
     it_operation = lt_operations
     iv_commit = abap_true
   IMPORTING
     es_return = ls_return.
     export_werks_to_memory( space ).

 IF ls_return IS INITIAL.
   COMMIT WORK.
 ELSE.
   RAISE EXCEPTION TYPE zcx_order.
 ENDIF.

Keine Werksänderung möglich

Leider hat der verwendete Baustein eine Einschränkung, die es im Dialog nicht gibt: Es kann kein abweichendes Werk im Vorgang übergeben werden. Dies war jedoch zwingend notwendig. Ich wusste dann keine andere Möglichkeit, als an geeigneter Stelle eine Erweiterungsimplementierung zu erstellen, die das vom Baustein gefundene Default-Werk überschreibt.

Die Erweiterung habe ich im Programm LCO_SEF03 am Ende der FORM-Routine opr_set_default_values gesetzt. Vor Aufruf des Bausteins CO_SE_PROORD_CHANGE exportiere ich das zu verwendende Werk über EXPORT TO MEMORY in den SAP-Speicher. Die Erweiterungsimplementierung überschreibt das Feld gs_afvgd-werks  mittels IMPORT FROM MEMORY (sofern vorhanden). Hiermit ist nur die Anlage oder Änderung eines Vorgangs möglich. Für das Ändern mehrerer Vorgänge muss die Logik entsprechend angepasst werden und man muss in der Erweiterung prüfen, welcher Vorgang gerade bearbeitet wird.

Der Beitrag Vorgang im Fertigungsauftrag anlegen erschien zuerst auf Tricktresor.


Fertigungsauftrag rückmelden

$
0
0

Ein kurzes und knappes Code-Beispiel um eine Rückmeldung zu einem Fertigungsauftrag mit Hilfe des Bausteins BAPI_PRODORDCONF_CREATE_TT zu erfassen. Die Rückmeldedaten werden erfasst und dann dem Baustein übergeben.

Um das ganze etwas interessanter zu machen, verwende ich die neuen ABAP-740 Sprachfeatures VALUE und SWITCH…

Time Ticket füllen

append value #(
   "Rückmeldedaten füllen
    orderid        = aufnr
    operation      = vornr
    work_cntr      = arbpl
    plant          = werks
    postg_date     = sy-datum
    conf_text      = |Rückmeldung Hugo|
    yield          = gutmenge
    conf_quan_unit = 'ST'
    recordtype     = COND #( WHEN gutmenge >= gesamtmenge THEN 'L40' ELSE 'L20' )
  ) TO tickets.

Rückmeldung buchen

DATA return     TYPE bapiret1.
 DATA return_det TYPE STANDARD TABLE OF bapi_coru_return.
 DATA return_det TYPE bapi_coru_return.

 "Rückmeldung buchen
 CALL FUNCTION 'BAPI_PRODORDCONF_CREATE_TT'
   EXPORTING
     post_wrong_entries = '0'
     testrun       = abap_false
   IMPORTING
     return        = return
   TABLES
     timetickets   = tickets
     detail_return = return_det.
Wie bei allen BAPIs muss die Buchung durch ein BAPI_TRANSACTION_COMMIT bestätigt werden

Der Beitrag Fertigungsauftrag rückmelden erschien zuerst auf Tricktresor.

Moderne UI mit altem SAPGUI und ALV-Grid

$
0
0

Ich habe eine kleine Spielerei gebaut, weil ich eine Möglichkeit brauchte um Parameter ein- und auszuschalten. Die normale Methode mit „X“ und „Space“ oder Checkbox fand ich langweilig und mir kam die Idee, dass es möglich sein müsste, eine etwas modernere Art der Darstellung möglich sein müsste.

Sowas in dieser Art:

Bild: http://pixabay.com

Neue UI

Natürlich sind die grafischen Möglichkeiten etwas beschränkt, aber die Funktionalität, wie man sie von jedem aktuellen Smartphone kennt, müsste machbar sein. In Frage kam nur der ALV-Grid (CL_GUI_ALV_GRID). Ich habe mit dem CL_SALV_TABLE angefangen, aber hier lassen sich die Rahmenlinien nicht ausblenden; das geht leider nur im ALV-Grid.

Und tatsächlich:

Ich habe noch mit ein paar anderen Varianten herumgespielt, aber das Prinzip ist immer das gleiche:

Beschreibung

Das Programm baut aus der Parametertabelle eine neue Parametertabelle, die jeweils für ON und OFF ein eigenes Feld für ein Icon hat. Für die Felder wurde die Hotspot-Funktionalität gesetzt, damit man per Klick den Zustand des Schalters ändern kann.

Um die Funktion „wasserdicht“ zu machen, müsste noch verhindert werden, dass die Spaltenbreite verändert werden kann (passiert schnell beim Klicken auf die ausgeblendete Rahmenlinie in der Mitte):

Geschützte Methoden nutzen für die Methode  SET_RESIZE_COLS.

Code

REPORT zz_swwwwwwitch.

"Dummy parameter do display docker
PARAMETERS p.

CLASS main DEFINITION.
 PUBLIC SECTION.
   INCLUDE <cl_alv_control>.
   TYPES: BEGIN OF ty_param,
            name TYPE string,
            text TYPE string,
            status TYPE boolean,
          END OF ty_param,
          ty_params TYPE STANDARD TABLE OF ty_param WITH NON-UNIQUE DEFAULT KEY.

   METHODS init_grid IMPORTING parent TYPE REF TO cl_gui_container.
   METHODS add_parameter
     IMPORTING name TYPE clike
       text TYPE clike OPTIONAL
       status TYPE boolean OPTIONAL.
   METHODS get_params
     RETURNING VALUE(parameters) TYPE ty_params.

   "Settings color
   CONSTANTS color_on TYPE i VALUE col_positive.
   CONSTANTS color_off TYPE i VALUE col_negative.

   "Settings icons
* CONSTANTS status_icon_on TYPE icon_text VALUE icon_businav_szenario.
* CONSTANTS status_icon_off TYPE icon_text VALUE icon_businav_szenario.

* CONSTANTS status_icon_on TYPE icon_text VALUE icon_led_green.
* CONSTANTS status_icon_off TYPE icon_text VALUE icon_led_red.

* CONSTANTS status_icon_on TYPE icon_text VALUE ICON_oo_class.
* CONSTANTS status_icon_off TYPE icon_text VALUE ICON_oo_class.

* CONSTANTS status_icon_on TYPE icon_text VALUE ICON_oo_object.
* CONSTANTS status_icon_off TYPE icon_text VALUE ICON_oo_class.

 CONSTANTS status_icon_on TYPE icon_text VALUE icon_ps_network_activity.
 CONSTANTS status_icon_off TYPE icon_text VALUE icon_ps_network_activity.

* CONSTANTS status_icon_on TYPE icon_text VALUE ICON_add_row.
* CONSTANTS status_icon_off TYPE icon_text VALUE ICON_remove_row.

* CONSTANTS status_icon_on TYPE icon_text VALUE icon_org_unit.
* CONSTANTS status_icon_off TYPE icon_text VALUE icon_org_unit.

   TYPES: BEGIN OF ty_ui_param,
     name TYPE string,
     text TYPE string,
     status_on TYPE icon_text,
     status_off TYPE icon_text,
     t_color TYPE lvc_t_scol,
     t_style TYPE lvc_t_styl,
   END OF ty_ui_param,
   ty_ui_params TYPE STANDARD TABLE OF ty_ui_param.

 PROTECTED SECTION.
   DATA grid TYPE REF TO cl_gui_alv_grid.
   DATA params TYPE ty_params.
   DATA ui_params TYPE ty_ui_params.

   METHODS set_ui.
   METHODS set_color
     IMPORTING status TYPE boolean
     RETURNING VALUE(color) TYPE lvc_t_scol.
   METHODS handle_click FOR EVENT hotspot_click OF cl_gui_alv_grid
     IMPORTING e_row_id.

ENDCLASS.

CLASS main IMPLEMENTATION.

 METHOD init_grid.

 "Local data
 DATA fieldcat TYPE lvc_t_fcat.
 DATA field TYPE lvc_s_fcat.
 DATA layout TYPE lvc_s_layo.

 "Transform parameter data to display UI
 set_ui( ).

 "Create grid in given container
 CREATE OBJECT grid
   EXPORTING
     i_parent = parent.

 "Set fields
 CLEAR field.
 field-fieldname = 'NAME'.
 field-outputlen = 20.
 field-colddictxt = 'Parameter'.
 field-style = alv_style_font_bold.
 APPEND field TO fieldcat.

 CLEAR field.
 field-fieldname = 'TEXT'.
 field-outputlen = 40.
 field-colddictxt = 'Description'.
 APPEND field TO fieldcat.

 CLEAR field.
 field-fieldname = 'STATUS_ON'.
 field-outputlen = 4.
 field-colddictxt = 'On'.
 field-hotspot = abap_true.
 field-icon = abap_true.
 field-fix_column = abap_true.
 APPEND field TO fieldcat.

 CLEAR field.
 field-fieldname = 'STATUS_OFF'.
 field-outputlen = 4.
 field-colddictxt = 'Off'.
 field-hotspot = abap_true.
 field-icon = abap_true.
 field-fix_column = abap_true.
 APPEND field TO fieldcat.

 "Layout
 layout-stylefname = 'T_STYLE'.
 layout-ctab_fname = 'T_COLOR'.
 layout-no_toolbar = abap_true.
 layout-no_headers = abap_true.

 "Display Grid
 grid->set_table_for_first_display(
   EXPORTING
     is_layout = layout
   CHANGING
     it_outtab = ui_params
     it_fieldcatalog = fieldcat
   EXCEPTIONS
     OTHERS = 4 ).

   "Set handler
   SET HANDLER handle_click FOR grid.

 ENDMETHOD.

 METHOD get_params.
   "return current settings
   parameters = params.
 ENDMETHOD.

 METHOD add_parameter.
   "add parameter to parameter table
   APPEND VALUE #( name   = name
                   text   = text
                   status = status ) TO params.
 ENDMETHOD.

 METHOD set_color.

   CASE status.
     WHEN abap_true.
       "set color for switched on
       color = VALUE #( ( fname = 'STATUS_OFF' color-col = color_on )
                        ( fname = 'STATUS_ON' color-col = color_on ) ).
     WHEN abap_false.
       "set color for switched off
       color = VALUE #( ( fname = 'STATUS_OFF' color-col = color_off )
                        ( fname = 'STATUS_ON' color-col = color_off ) ).
   ENDCASE.

 ENDMETHOD.


 METHOD set_ui.

 "for each parameter
 LOOP AT params INTO DATA(param).

   "check if ui entry exists
   READ TABLE ui_params ASSIGNING FIELD-SYMBOL(<ui_param>) WITH KEY name = param-name.
   IF sy-subrc > 0.

   CASE param-status.
     WHEN abap_true.
       "set parameter switched on
       APPEND VALUE #( name = param-name
                       text = param-text
                       status_on = status_icon_on
                       status_off = 'ON'
                       t_color = set_color( abap_true )
                       t_style = VALUE #( ( fieldname = 'STATUS_ON' style2 = alv_style2_no_border_right )
                                          ( fieldname = 'STATUS_OFF' style2 = alv_style2_no_border_left ) )
                     ) TO ui_params ASSIGNING <ui_param>.

     WHEN abap_false.
     "set parameter switched off
       APPEND VALUE #( name = param-name
                       text = param-text
                       status_on = 'OFF'
                       status_off = status_icon_off
                       t_color = set_color( abap_false )
                       t_style = VALUE #( ( fieldname = 'STATUS_ON' style2 = alv_style2_no_border_right )
                                          ( fieldname = 'STATUS_OFF' style2 = alv_style2_no_border_left ) )
                     ) TO ui_params ASSIGNING <ui_param>.

   WHEN abap_undefined.
     "Set parameter not yet defined
     APPEND VALUE #( name = param-name
                     text = param-text
                     status_on = space
                     status_off = space
                     t_style = VALUE #( ( fieldname = 'STATUS_ON' style2 = alv_style2_no_border_right )
                                        ( fieldname = 'STATUS_OFF' style2 = alv_style2_no_border_left ) )
                   ) TO ui_params ASSIGNING <ui_param>.

       ENDCASE.
     ENDIF.
   ENDLOOP.
 ENDMETHOD.

 METHOD handle_click.
 "read parameter entries
 READ TABLE ui_params ASSIGNING FIELD-SYMBOL(<ui_param>) INDEX e_row_id-index.
 READ TABLE params ASSIGNING FIELD-SYMBOL(<param>) WITH KEY name = <ui_param>-name.

 IF <ui_param>-status_on = status_icon_on OR
   <ui_param>-status_on = status_icon_off.
   "set switch to OFF
   <ui_param>-status_on = 'OFF'.
   <ui_param>-status_off = status_icon_off.
   <ui_param>-t_color = set_color( abap_false ).
   <param>-status = abap_false.
 ELSE.
   "Set switch to ON
   <ui_param>-status_on = status_icon_on.
   <ui_param>-status_off = 'ON'.
   <ui_param>-t_color = set_color( abap_true ).
   <param>-status = abap_true.
 ENDIF.
 "Make changes visible
 grid->refresh_table_display( i_soft_refresh = abap_true ).
 ENDMETHOD.

ENDCLASS.

INITIALIZATION.

 DATA(main) = NEW main( ).

 main->add_parameter( name = 'DISPLAY_TECH_DESCR' text = 'Display technical description' status = abap_true ).
 main->add_parameter( name = 'DISPLAY_VALUES' text = 'Display values' status = abap_false ).
 main->add_parameter( name = 'AUTOSAVE' text = 'Autosave' status = abap_true ).
 main->add_parameter( name = 'INST_CALC' text = 'Instant calculation' status = abap_undefined ).

 main->init_grid( NEW cl_gui_docking_container( ratio = 60 side = cl_gui_docking_container=>dock_at_bottom ) ).

AT SELECTION-SCREEN.
 "Enter on selection screen displays current parameters
 DATA(params) = main->get_params( ).
 cl_demo_output=>display_data( params ).
 

Der Beitrag Moderne UI mit altem SAPGUI und ALV-Grid erschien zuerst auf Tricktresor.

ABAP 740-Features unter der Lupe

$
0
0

Aus einer einfachen Anfängerfrage im abapforum.com hat sich eine recht spannende Antwortserie entwickelt, die auf die neuen Sprachfeatures von ABAP740 eingeht. Ich habe diese einmal zusammen gefasst und auch Laufzeitmessungen durchgeführt.

Die Frage

Die Frage von debianfan lautete: Wie ermittele ich die Anzahl von Datensätzen bestimmter Ausprägung in einer internen Tabelle?

Die interne Tabelle NAMES besteht nur aus den Feldern

  • NAME (string)
  • TF (boolean)

Die folgenden Lösungen sind teilweise vereinfacht und ohne DATA-Definitionen. Die einzelnen lauffähigen Lösungen sind unten im Beispielprogramm ersichtlich.

Lösung 1 – 2xLOOP+WHERE(DATA)

Die einfachste und auf der Hand liegende Antwort von Tron war:

LOOP AT names INTO name WHERE tf = abap_true.
  ADD 1 TO zaehler_true.
ENDLOOP.

LOOP AT names INTO name WHERE tf = abap_false.
  ADD 1 TO zaehler_false.
ENDLOOP.

Die Lösung ist einfach und verständlich.

Der Einwand von Ralf war, dass bei WHERE die gesamte Tabelle durchlaufen werden muss, wenn kein Index verwendet wird. Das kann sich bei großen Tabellen negativ auf die Laufzeit auswirken.

Mein Gedanke war, dass ich zwei LOOPs nicht schön finde und außerdem ein LOOP mit einer Case-Anweisung noch einen Tacken einfacher und deutlich sein müsste. Dazu später mehr.

Lösung 2 – FILTER

Haubi hat dann den Vorschlag gemacht, die einzelnen Einträge mittels FILTER zu zählen:

DATA(lv_true)  = lines( FILTER #( names WHERE tf = abap_true ) ).
 DATA(lv_false) = lines( FILTER #( names WHERE tf = abap_false ) ).

Diese Lösung finde ich sehr schlank und gut lesbar. Was mich hier stört, ist, dass durch FILTER alle verarbeiteten Tabelleneinträge kopiert werden. Es werden alle Datensätze die der WHERE-Anweisung entsprechen in eine neue Tabelle kopiert. Die Tabelle ist zwar temporär und wird nur für die Zeit der Verarbeitung des FILTER-Befehls verwendet, aber bei großen Tabellen kann sich die zusätzliche Speicherlast negativ auswirken.

Lösung 3 – REDUCE

Ich wollte dann unbedingt noch eins drauf setzen und eine Lösung haben, die auch bei vielen Ausprägungen von TF funktioniert und die Werte von TF nicht bekannt sind. Zudem wollte ich komplett die neuen Sprachfeatures verwenden.

Bei beiden vorhergehenden Lösungen fand ich es nicht gut, dass gezielt im Programm auf ABAP_TRUE und ABAP_FALSE abgefragt wurde. In diesem Beispiel ist es in Ordnung, weil das die Vorgabe war. Der häufigere Fall ist jedoch, dass eine Gruppe viele und gegebenenfalls nicht bekannte Ausprägungen hat (Verkaufsorganisation, Datum, Materialnummer, etc.).

Meine Lösung bestand dann aus einer Kombination aus VALUE und REDUCE:

DATA(sum) = VALUE ttf( FOR GROUPS grp OF <name> IN names
                        WHERE ( name IS NOT INITIAL )
                        GROUP BY ( tf = <name>-tf )
                          ( tf    = grp
                            count = REDUCE #( INIT i = 0
                                       FOR name IN names
                                       WHERE ( tf = grp )
                                       NEXT i = i + 1 ) ) ).

Diese Lösung baut eine Tabelle auf aus TF und COUNT, so dass alle Gruppenwerte mit der entsprechenden Anzahl Einträge in der Tabelle SUM landen.

Eigentlich müsste diese Lösung die langsamste sein, denn es werden zuerst die Gruppen gebildet. Dafür muss die gesamte Tabelle durchlaufen werden. Dann werden zu jedem Gruppeneintrag erneut die zugehörigen Einträge gelesen und gezählt. Deswegen wollte ich zuerst gar keine Laufzeitmessung machen. Die Herausforderung für mich war in erster Linie, die Problemstellung mit den neuen Sprachfeatures abzubilden, da ich mich mit der Syntax eher schwer tue.

Lösung 4 – 1xLOOP+WHERE(DATA)

Ich habe mit den vorhandenen drei Lösungen ein Testprogramm geschrieben um die Laufzeit mit der Transaktion SAT analysieren zu können.

Allerdings habe ich gemerkt, dass ich die Lösung von Tron falsch übernommen hatte, nämlich folgendermaßen:

LOOP AT names INTO name.
  CASE name-tf.
    WHEN abap_true.
      ADD 1 TO zaehler_true.
    WHEN abap_false.
      ADD 1 TO zaehler_false.
  ENDCASE.
ENDLOOP.

Anstatt zweier LOOPs hatte ich nur einen LOOP und eine CASE-Abfrage.

Da ich die schon dabei war zu testen, wollte ich Trons Code genau so übernehmen, da ich davon ausging, dass meine Variante mit CASE schneller sein würde. Allerdings war dem nicht so…

Update

Zusätzlich zu den LOOP-Lösungen, die mit dem Zusatz INTO workarea arbeiten, habe ich noch die Varianten mit ASSIGNING (Feldsymbol) und TRANSPORTING NO FIELDS aufgenommen.

Lösung 5 – 1xLOOP+CASE(Fieldsymbol)

Die Lösung mit einem LOOP und CASE-Anweisung jedoch mit LOOP-ASSIGNING.

Lösung 6 – 2xLOOP+WHERE(Fieldsymbol)

Die Lösung mit zwei LOOPs und entsprechender WHERE-Bedingung jedoch mit LOOP-ASSIGNING.

Lösung 7 – 2xLOOP+WHERE(ohne Feldtransport)

Die Lösung mit zwei LOOPs und entsprechender WHERE-Bedingung jedoch mit dem Zusatz TRANSPORTING NO FIELDS.

Laufzeitanalyse

 

Der Vollständigkeit halber habe ich die Messung auch noch einmal mit der Variante „SORTED TABLE“ durchgeführt. Und wieder war ich überrascht: Die Variante mit Sorted Table ist deutlich langsamer als die Variante mit Standard Table…

Hier das Ergebnis der Laufzeitmessungen mit 100.000 Datensätzen und STANDARD TABLE:

Variante          Laufzeit
P01_REDUCE         76.602
P02_FILTER         36.755
P03_LOOP_CASE      33.891
P04_LOOP_WHERE     27.282
P05_LOOP_CASE_FS   25.097
P06_LOOP_WHERE_FS  18.805
P07_LOOP_WHERE_NO  17.774

Code

Methode rnd_name baut aus zufälligen Buchstaben Fantasienamen auf.

Methode rnd_bool liefert per Zufall den Wert TRUE oder FALSE zurück.

Die Methoden p01 – p07 enthalten die jeweils erwähnten Lösungsvarianten.

REPORT.
" http://www.abapforum.com/forum/viewtopic.php?f=1&t=21900&p=82017#p82017

PARAMETERS p TYPE i DEFAULT 100000.

CLASS help DEFINITION.
 PUBLIC SECTION.
 CLASS-METHODS rnd_name RETURNING VALUE(name) TYPE string.
 CLASS-METHODS rnd_bool RETURNING VALUE(tf) TYPE boolean.
 CLASS-METHODS class_constructor.
 CLASS-METHODS p01_reduce.
 CLASS-METHODS p02_filter.
 CLASS-METHODS p03_loop_case.
 CLASS-METHODS p04_loop_where.
 CLASS-METHODS p05_loop_case_fs.
 CLASS-METHODS p06_loop_where_fs.
 CLASS-METHODS p07_loop_where_no.
 PROTECTED SECTION.
 CLASS-DATA rnd TYPE REF TO cl_abap_random.
 TYPES:
 BEGIN OF lst_names,
 name TYPE string,
 tf TYPE abap_bool,
 END OF lst_names,
 ltt_names TYPE STANDARD TABLE OF lst_names
 WITH NON-UNIQUE KEY name
 WITH NON-UNIQUE SORTED KEY key_tf COMPONENTS tf.

* ltt_names TYPE SORTED TABLE OF lst_names
* WITH NON-UNIQUE KEY name
* WITH NON-UNIQUE SORTED KEY key_tf COMPONENTS tf.
 CLASS-DATA names TYPE ltt_names.
ENDCLASS.

CLASS help IMPLEMENTATION.
 METHOD class_constructor.
 rnd = cl_abap_random=>create( ).
 names = VALUE ltt_names( FOR i = 1 THEN i + 1 WHILE i <= p
 ( name = help=>rnd_name( ) tf = help=>rnd_bool( ) ) ).

 ENDMETHOD.

 METHOD rnd_name.
 DATA(len) = rnd->intinrange( low = 5 high = 40 ).
 DO len TIMES.
 DATA(pos) = rnd->intinrange( low = 0 high = 25 ).
 name = name && sy-abcde+pos(1).
 ENDDO.
 ENDMETHOD.

 METHOD rnd_bool.
 CASE rnd->intinrange( low = 0 high = 1 ).
 WHEN 0.
 tf = abap_false.
 WHEN 1.
 tf = abap_true.
 ENDCASE.
 ENDMETHOD.

 METHOD p01_reduce.
 TYPES:
 BEGIN OF stf,
 tf TYPE abap_bool,
 count TYPE i,
 END OF stf,
 ttf TYPE SORTED TABLE OF stf WITH UNIQUE KEY tf.

 DATA(sum) = VALUE ttf( FOR GROUPS grp OF <name> IN names
 WHERE ( name IS NOT INITIAL )
 GROUP BY ( tf = <name>-tf )
 ( tf = grp
 count = REDUCE #( INIT i = 0
 FOR name IN names
 WHERE ( tf = grp )
 NEXT i = i + 1 ) ) ).
* cl_demo_output=>display_data( sum ).
 ENDMETHOD.

 METHOD p02_filter.
 DATA(lv_true) = lines( FILTER #( names USING KEY key_tf WHERE tf = abap_true ) ).
 DATA(lv_false) = lines( FILTER #( names USING KEY key_tf WHERE tf = abap_false ) ).

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.

 METHOD p03_loop_case.

 DATA lv_true TYPE i.
 DATA lv_false TYPE i.

 LOOP AT names INTO DATA(name).
 CASE name-tf.
 WHEN abap_true. ADD 1 TO lv_true.
 WHEN abap_false. ADD 1 TO lv_false.
 ENDCASE.
 ENDLOOP.

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.

 METHOD p04_loop_where.

 DATA lv_true TYPE i.
 DATA lv_false TYPE i.

 LOOP AT names INTO DATA(name) WHERE tf = abap_true.
 ADD 1 TO lv_true.
 ENDLOOP.
 LOOP AT names INTO name WHERE tf = abap_false.
 ADD 1 TO lv_false.
 ENDLOOP.

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.

 METHOD p05_loop_case_fs.

 DATA lv_true TYPE i.
 DATA lv_false TYPE i.

 LOOP AT names ASSIGNING FIELD-SYMBOL(<name>).
 CASE <name>-tf.
 WHEN abap_true. ADD 1 TO lv_true.
 WHEN abap_false. ADD 1 TO lv_false.
 ENDCASE.
 ENDLOOP.

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.

 METHOD p06_loop_where_fs.

 DATA lv_true TYPE i.
 DATA lv_false TYPE i.

 LOOP AT names ASSIGNING FIELD-SYMBOL(<name>) WHERE tf = abap_true.
 ADD 1 TO lv_true.
 ENDLOOP.
 LOOP AT names ASSIGNING <name> WHERE tf = abap_false.
 ADD 1 TO lv_false.
 ENDLOOP.

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.

 METHOD p07_loop_where_no.

 DATA lv_true TYPE i.
 DATA lv_false TYPE i.

 LOOP AT names TRANSPORTING NO FIELDS WHERE tf = abap_true.
 ADD 1 TO lv_true.
 ENDLOOP.
 LOOP AT names TRANSPORTING NO FIELDS WHERE tf = abap_false.
 ADD 1 TO lv_false.
 ENDLOOP.

* DATA(out) = cl_demo_output=>new( ).
* out->write( lv_true )->write( lv_false )->display( ).
 ENDMETHOD.


ENDCLASS.

START-OF-SELECTION.


 help=>p01_reduce( ).
 help=>p02_filter( ).
 help=>p03_loop_case( ).
 help=>p04_loop_where( ).
 help=>p05_loop_case_fs( ).
 help=>p06_loop_where_fs( ).
 help=>p07_loop_where_no( ).

Der Beitrag ABAP 740-Features unter der Lupe erschien zuerst auf Tricktresor.

REDUCE + SWITCH + COND [ABAP740]

$
0
0

Heute im Code-Dojo hatte ich die Aufgabe gestellt, eine Funktion zu schreiben, die einen String mit variabel zu bestimmender Länge und zufälligen Zeichenfolgen aus Zahlen und Buchstaben zurück liefert. Zum Beispiel „I71B7HJ4BG“ oder „6EE17ICBF54IE486EHD8“.

Idee

Mit VALUE und FOR sollte ein String Zeichen für Zeichen zusammengesetzt werden. Mit einer Zufallsfunktion sollte ermittelt werden, ob ein Buchstabe oder eine Zahl eingesetzt werden soll. Per SWITCH sollte ebenfalls eine Zufallsfunktion aufgerufen werden, die eine Zahl bzw. einen Buchstaben zurück liefert.. Per String-Konkatenation sollten die zufälligen Zeichen zusammengesetzt werden.

Abweichung

die Aufgabe lässt sich mit VALUE nicht lösen. Stattdessen muss REDUCE genommen werden.

Code

Für jede Stelle des zu generierenden Strings (FOR – UNTIL – NEXT) wird eine Funktion RND_TYPE aufgerufen. Diese gibt zufällig den Wert TRUE oder FALSE zurück. Per SWITCH-Anweisung wird entschieden, ob eine Zahl (FALSE) oder ein Buchstabe (TRUE) generiert werden soll. Das generierte Zeichen wird per String-Konkatenation Zeichen für Zeichen zusammengebaut.

 

REPORT.

CLASS main DEFINITION.
   PUBLIC SECTION.
     DATA rnd_num TYPE REF TO cl_abap_random_int.
     DATA rnd_chr TYPE REF TO cl_abap_random_int.
     METHODS constructor.
     METHODS rnd_type
       RETURNING VALUE(type) TYPE boolean.
     METHODS create_random_string
       IMPORTING max           TYPE i
       RETURNING VALUE(string) TYPE string.
     METHODS get_random_char
       RETURNING VALUE(char) TYPE char01.
     METHODS get_random_number
       RETURNING VALUE(number) TYPE numc01.
 ENDCLASS.

CLASS main IMPLEMENTATION.
   METHOD constructor.
     rnd_chr = cl_abap_random_int=>create( seed = CONV #( sy-uzeit ) min = 0 max = 25 ).
     rnd_num = cl_abap_random_int=>create( seed = CONV #( sy-uzeit ) min = 0 max = 9 ).
   ENDMETHOD.
   METHOD rnd_type.

    type = COND #( LET random = get_random_number( ) IN
                    WHEN random <= 5 THEN abap_true
                    ELSE abap_false ).
   ENDMETHOD.

  METHOD get_random_char.
     DATA(offset) = rnd_num->get_next( ).
     char = sy-abcde+offset(1).
   ENDMETHOD.

  METHOD get_random_number.
     number = rnd_num->get_next( ).
   ENDMETHOD.

  METHOD create_random_string.

    string = REDUCE #( INIT text = ``
                        FOR i = 1
                        UNTIL i > max
                        NEXT text = text && SWITCH #( rnd_type( )
                                              WHEN abap_true  THEN get_random_char( )
                                              WHEN abap_false THEN get_random_number( ) ) ).

  ENDMETHOD.

ENDCLASS.

PARAMETERS p_len type i DEFAULT 10.
PARAMETERS p_str TYPE char20 MODIF ID a.

AT SELECTION-SCREEN OUTPUT.
   LOOP AT SCREEN.
     CASE screen-group1.
       WHEN 'A'.
         screen-input = '0'.
         MODIFY SCREEN.
     ENDCASE.
   ENDLOOP.

AT SELECTION-SCREEN.
   p_str = NEW main( )->create_random_string( p_len ).

Lessons Learned

Mit REDUCE können Operationen auf einen Datentyp „reduziert“ werden. Mit VALUE funktioniert das nicht.

string = REDUCE #( INIT text = ``
                   FOR i = 1
                   UNTIL i > max
                   NEXT text = text && SWITCH #( rnd_type( )
                                         WHEN abap_true  THEN get_random_char( )
                                         WHEN abap_false THEN get_random_number( ) ) ).

Mit SWITCH können nur Exakte Werte abgefragt werden (wie bei CASE auch mit OR verknüpft). Es sind jedoch keine „Größer-/ Kleiner-Vergleiche“ möglich.

[...] SWITCH #( rnd_type( )
        WHEN abap_true  THEN get_random_char( )
        WHEN abap_false THEN get_random_number( ) ) ).

Mit COND können beliebige Bedingungen geprüft werden. Allerdings muss hier jede Bedingung separat angegeben werden. Wenn der abzufragende Wert das Ergebnis einer Funktion ist, so sollte mit LET gearbeitet werden, um nicht für jede Bedingung die Funktion aufrufen zu müssen.

var = COND #( LET random = get_random_number( ) IN
              WHEN random <= 5 THEN abap_true
              ELSE abap_false ).

Die implizite Typ-Definition mit INIT (bei der REDUCE-Anweisung) ist mit Vorsicht zu genießen! Ich hatte aus Gewohnheit einen leeren „String“ mit Hochkomma-Space-Hochkomma definiert. In Wirklichkeit hatte ich damit aber einen CHAR(1)-Feld definiert und die Funktion hat immer nur ein Zeichen zurück geliefert. Die String-Konkatenation hat diesen fest definierten Typ also nicht automatisch erweitert, so wie es beim String der Fall ist. Erst die Verwendung eines echten Strings durch die Backticks liefert das gewünschte Ergebnis.

Es kann auch der Typ direkt angegeben werden (INIT text TYPE string) aber dann ist keine Vorbelegung mehr möglich. Eine implizite Definition durch Vorbelegung ist dann jedoch wieder durch die Verwendung von CONV möglich: INIT text = CONV string( ‚hallo‘ )

Bei der FOR-Funktion (FOR i = 1) muss das Hochzählen der Variable (THEN i + 1) nicht zwingend definiert werden! Wird THEN nicht angegeben, so wird implizit die Inkrementierung um Eins vorgenommen:

[...] FOR i = 1 UNTIL i > 10 [...]

 

Der Beitrag REDUCE + SWITCH + COND [ABAP740] erschien zuerst auf Tricktresor.

Tricktresorsche Glockenkurve

$
0
0

Eine kleine Fingerübung im old-style, die aus einem Denkansatz heraus abgefallen ist: Eine kleine „grafische“ Spielerei zur Darstellung der Gauß-Funktion/ Glockenkurve/ Normalverteilung.

Da dies für mich bereits höhere Mathematik ist, bin ich besonders stolz auf diese kleine Spielerei. Eine schönere Möglichkeit wäre sicherlich die Darstellung im GFW-Framework, aber ich wollte nur schnell sehen, ob die berechneten Werte irgendwie stimmig sind.

GFW-Framework

Bei Interesse schau dir die Demoprogramme GFW_PROG* und GFW_DEMO* einmal an:

GFW_DEMO_HIER3 GFW: Demonstration einer Hierarchie-/Präsentationsgrafik (Drag&Drop)
GFW_DEMO_PRES GFW: Demonstration von GFW mit sichtbarem Datencontainer
GFW_DEMO_PRES1 GFW: Demonstration von Präsentationsgrafiken mit GFW
GFW_DEMO_PRES_MAIN  class with application logic of example report „GFW_DEMO_PRES“

GFW_PROG_BAR GFW: Programmierbeispiel für ein Balkendiagramm
GFW_PROG_COLUMNS_AND_TIME_AXIS GFW: Programmierbeispiel für ein Balkendiagramm mit Zeitachse
GFW_PROG_CREATE_CUSTOMIZING GFW: Programmierbeisp. für einfachen Gebrauch von Customizing-Bündeln
GFW_PROG_DC_PERFORMANCE GFW: Programmierbeispiel für die schnelle Datencontainerverwendung
GFW_PROG_GET_CU_BUNDLE GFW: Programmierbeispiel mit Methode if_graphic_proxy~get_cu_bundle
GFW_PROG_HISTOGRAM GFW: Programmierbeispiel für ein Histogramm
GFW_PROG_LABELS GFW: Programmierbeispiel für dieselben Beschriftungen, lange Beschr.
GFW_PROG_MTA GFW: Programmierbeispiel für eine Meilensteintrendanalyse
GFW_PROG_PIE GFW: Programmierbeispiel für ein Kreisdiagramm
GFW_PROG_POINT_WITH_LABEL GFW: Programmierbeispiel für Diagramm mit gekennzeichnetem Punkt
GFW_PROG_PORTFOLIO GFW: Programmierbeispiel für ein Balkendiagramm
GFW_PROG_SPEEDOMETER GFW: Programmierbeispiel für ein Balkendiagramm
GFW_PROG_TIME_AXIS GFW: Programmierbeispiel für ein Punktdiagramm mit Zeitachse
GFW_PROG_TUTORIAL GFW: Programmierbeispiel für eine einfache PräsGrafik = GFW-Tutorial

Code

REPORT zz_gauss_timer_demo NO STANDARD PAGE HEADING LINE-SIZE 1000.


CLASS main DEFINITION.
  PUBLIC SECTION.
    METHODS start.
  PROTECTED SECTION.
    DATA sigma TYPE f VALUE '0.1'.
    DATA my TYPE f VALUE 0.
    DATA count TYPE i.
    METHODS gauss.
    DATA timer TYPE REF TO cl_gui_timer.
    METHODS finished FOR EVENT finished OF cl_gui_timer.
    METHODS clear_screen.
ENDCLASS.

CLASS main IMPLEMENTATION.
  METHOD start.
    gauss( ).
    timer = NEW #( ).
    SET HANDLER finished FOR timer.
    timer->interval = 1.
    timer->run( ).
  ENDMETHOD.

  METHOD finished.
    gauss( ).
    ADD 1 TO count.
    IF count < 10.
      timer->run( ).
    ELSE.
      SKIP TO LINE 28.
      POSITION 120.
      WRITE 'FINISHED'.
    ENDIF.
  ENDMETHOD.

  METHOD gauss.

    DATA e   TYPE f VALUE '2.718281828459'.
    DATA pi  TYPE f VALUE '3.14159265359'.
    DATA x   TYPE f.
    DATA erg TYPE f.
    DATA anz TYPE i VALUE 51.
    DATA l   TYPE i. "value for result (scaled)

    clear_screen( ).

    SKIP TO LINE 3.

    ADD '0.02' TO sigma.

    x = -1.


    DO anz TIMES.

      "calculate gauss
      erg = ( 1 / sigma * sqrt( 2 * pi ) ) * e **
            ( '0.5-' * (  ( x - my ) / (  sigma  ) ) ** 2 ) .

      "write result
      WRITE: / x EXPONENT 0 DECIMALS 2, erg EXPONENT 0 DECIMALS 5.

      "scale result
      l = erg * 4.

      "write graph
      DO l TIMES.
        WRITE sym_checkbox as SYMBOL NO-GAP.
*        WRITE icon_bw_apd_source as icon NO-GAP.
      ENDDO.

      "add step
      x = x + 1 / ( ( anz - 1 ) / 2 ) .

    ENDDO.
  ENDMETHOD.

  METHOD clear_screen.
    SKIP TO LINE 1.

    write: /20 'x-value                 result;   sigma=', sigma EXPONENT 0 DECIMALS 3 LEFT-JUSTIFIED,
               'my=', my EXPONENT 0 DECIMALS 3 LEFT-JUSTIFIED.

    SET BLANK LINES ON.
    DO 50 TIMES.
      DO sy-linsz TIMES.
        WRITE space NO-GAP.
      ENDDO.
    ENDDO.
  ENDMETHOD.

ENDCLASS.


START-OF-SELECTION.
  NEW main( )->start( ).
 

Der Beitrag Tricktresorsche Glockenkurve erschien zuerst auf Tricktresor.

Falle beim Left Outer Join

$
0
0

Häufig sind es die Kleinigkeiten, die einem das Leben schwer machen. Die berühmten letzten 20%. Häufig bemerkt man jedoch gar nicht, dass man überhaupt ein Problem hat, weil alles scheinbar so funktioniert, wie man es sich vorstellt.

Aufgabe: SELECT

Die Aufgabe sollte sein: Selektiere aus Tabelle ZZT1 alle Einträge anhand der gegebenen Selektionskriterien (AREA). Lies zusätzlich mit einem LEFT OUTER JOIN alle Einträge aus Tabelle ZZT2, die über das Feld LINK verknüpft sind hinzu. Einträge aus Tabelle ZZT2 mit gesetztem Löschkennzeichen dürfen nicht berücksichtigt werden.

Demodaten

Die folgenden Demodaten stehen zur Verfügung:

  • Tabelle ZZT1
  • Tabelle ZZT2

Selektionskriterium soll sein: AREA = TEST

Tabelle ZZT1 (links)

KEY1 TEXT LINK AREA
1 Eins A TEST
2 Zwei B TEST
3 Drei C TEST
4 Vier TEST
5 Fünf A NEU

Tabelle ZTT2 (rechts)

LINK TEXT LOEVM
A Info A
B Info B X
C Info C

Überlegungen

Wenn aus Tabelle ZZT1 alle Einträge selektiert werden, bei denen das Feld AREA mit „TEST“ gefüllt ist, dann erwarte ich, dass die folgenden Einträge selektiert werden: 1, 2, 3 und 4.

Zusätzlich muss bei den Einträgen 1 und 3 der LINK_TEXT gefüllt sein, denn diese Einträge verweisen auf Einträge in Tabelle ZZT2, die keine Löschvormerkung haben.

Erster Versuch

Der erste Wurf sieht folgendermaßen aus:

SELECT z1~key1,
       z1~text,
       z1~link,
       z1~area,
       z2~text AS link_text
  FROM zzt1 AS z1
  LEFT OUTER JOIN zzt2 AS z2 ON z1~link = z2~link
  INTO TABLE @DATA(t_data)
 WHERE z1~area  = 'TEST'.
   AND z2~loevm = @space.

Der SELECT ist syntaktisch (!) fehlerfrei, liefert jedoch leider ein falsches Ergebnis:

KEY1 TEXT LINK AREA LINK_TEXT
1 Eins A TEST Info A
3 Drei C TEST Info C

Zweiter Versuch

Ohne die Einschränkung über die WHERE-Bedingung auf der rechten Seite (AND z2~loevm = @space) funktioniert die Selektion wie erwartet.

Das ist nicht ganz das, was ich erwartet habe… Ich habe mich dann damit beschäftigt, warum das Ergebnis so aussieht und warum Ergebniszeilen fehlen. Beziehungsweise habe ich zuerst versucht, das richtige Ergebnis zu bekommen. Der richtige Select lautet so:

SELECT z1~key1,
       z1~text,
       z1~link,
       z1~area,
       z2~text AS link_text
  FROM zzt1 AS z1
  LEFT OUTER JOIN zzt2 AS z2 ON z1~link = z2~link
                            AND z2~loevm = @space
  INTO TABLE @DATA(t_data)
 WHERE z1~area = 'TEST'.

Ergebnis:

KEY1 TEXT LINK AREA LINK_TEXT
1 Eins A TEST Info A
2 Zwei B TEST
3 Drei C TEST Info C
4 Vier TEST

Lessons learned

Bei einem LEFT OUTER JOIN darf die WHERE-Bedingung keine Einschränkung auf die rechte Tabelle haben. In der Hilfe zum JOIN steht:

Eine WHERE-Bedingung für eine SELECT-Anweisung mit Joins wirkt auf die durch die Joins gebildete Ergebnismenge.

Ich deute das so, dass die WHERE-Bedingung sozusagen erst nachträglich angewendet wird. Wobei das für mich ein Zirkelschluss ist, den ich nicht verstehe. Zudem enthält die Ergebnismenge ja gar kein Feld „LOEVM“.

Auf jeden Fall sollte man die Verwendung beziehungsweise die gesammelten Daten eines LEFT OUTER JOINS sehr genau prüfen. Schnell schleicht sich hier ein Fehler ein, mit dem man nicht gerechnet hat.

Der Beitrag Falle beim Left Outer Join erschien zuerst auf Tricktresor.

IF cl_qualification=>i_speak( ‚ABAP‘ ) = abap_true. me->apply( ). ENDIF.

$
0
0

Kurz und knapp

Ich suche neue Kolleginnen und Kollegen!

Du kannst ABAP oder suchst als SAP-Berater eine neue Herausforderung?

Dann bewirb dich bei uns: bewerbung@inwerken.de.

Die etwas längere Version

Der ein oder andere kennt mich als „Mr. Tricktresor“ oder als ewx im ABAPforum.com. Allerdings wissen, glaube ich, nur wenige, dass ich bei der Firma INWERKEN AG angestellt bin. Dort programmiere und berate ich in Kundenprojekten als auch bei internen Projekten. Ich habe bei Inwerken angefangen, weil ich begeistert von den Kollegen war und ich meine eigenen Ziele verfolgen konnte. Heute, ziemlich genau neun Jahre später, bin ich immer noch überzeugt von dem technischen Knowhow  meiner Kollegen und dem Zusammenhalt in der Firma. Das ABAP-Kochbuch wäre ohne meine Kollegen nie zustande gekommen.

Wer mich kennt, der weiß, dass ich ein ehrlicher Mensch bin. Deswegen muss ich auch klar sagen: Es ist nicht alles perfekt. Natürlich gibt es bei Inwerken Probleme und Dinge, die nicht optimal funktionieren. Aber wir können immer über alles sprechen und meistens finden wir eine zufriedenstellende Lösung. Ich möchte nun nicht die ganzen Buzzwords von wegen flache Strukturen, Teamgeist und so weiter aufzählen, das kann nur in die Hose gehen. Am besten machst du dir selbst ein Bild.

Das Team Inwerken

Da wir ein SAP-Beratungshaus sind, ist der Großteil natürlich in der SAP-Beratung und ABAP-Programmierung tätig. Wir beschäftigen uns dabei mit der ganzen breiten Palette von SAP-Produkten. Wir machen komplette SAP-Einführungen als auch Add-On-Programmierungen und Anpassungen an bestehenden Systemen. Wir optimieren Prozesse und Programme. In der Regel verstehen wir das große Ganze und verlieren trotzdem die Details nicht aus dem Blick.

Wir haben ein hervorragendes Backoffice bestehend aus fünf Kolleginnen, die sich um alles kümmern, was nicht mit SAP zu tun hat.

Die interne vierköpfige IT und Basisadministration betreut unsere Software und Notebooks sowie unsere Inwerken-eigenen SAP-Systeme. Ganz neu im Fuhrpark sind gerade vier HANA und S/4HANA Systeme!

Im Beratungsbereich haben wir für fast jedes Modul fähige Mitarbeiter und Mitarbeiterinnen. Egal ob Preisfindung im SD, Chargenfindung in der Lieferung, Arbeitsplanerstellung bei Fertigungsaufträgen oder Infocubes im Business Warehouse: Fast immer gibt es jemanden, der sich auskennt.

ABAP-Programmierung besteht nicht nur aus IF-ELSE-ENDIF oder SELECT, sondern aus einer Vielzahl von Komponenten und modulspezifischen Funktionen Und auch hier sind zu vielen Spezialthemen Leute unterwegs, die genau wissen, was sie tun. Dabei ist es egal ob es sich um BAPIs, Programmierkonzepte oder die Analyse von schwer nachzustellenden Fehlern geht, bei Inwerken ist meistens jemand, der sich schon einmal mit dem Thema auseinandergesetzt hat oder schlichtweg hilft, wenn ein Programm nicht so funktioniert, wie es soll. Auf jeden Fall kann man sich immer zusammensetzen und Lösungen diskutieren oder Ideen sammeln.

Die Firma

Die Inwerken AG ist aus der 1999 gegründeten COMbridge AG hervorgegangen. Bereits zu COMbridge-Zeiten hatte die Firma den Ruf, Probleme anzupacken und lösen. Mit Inwerken ist daraus eine Firma entwachsen, die ich durchaus einen sehr attraktiven Arbeitgeber nennen würde. Inwerken besteht aktuell aus 57 Mitarbeitern, davon acht Auszubildende.

Wir organisieren regelmäßig Team-Events und Feste, bei denen der Spaß und Teamgeist nicht zu kurz kommen. Neben dem Spaßfaktor bietet Inwerken jedoch auch flexible Arbeitszeitmodelle, Homeoffice, ergonomische Arbeitsplätze, vielfältige soziale Leistungen, Kaffee und Mineralwasser, Firmenwagenregelung und interne Weiterbildungsmöglichkeiten (Englishkurs, Code-Dojo, Workshops).

Community

Wir engagieren uns stark in der SAP-Community. Regelmäßig richten wir die SAP CodeJam zu verschiedenen Themen aus. Die CodeJam ist ein beliebtes Event bei dem man unkompliziert mit neuen Techniken (SAPUI5, HANA, Gateway etc.) in Kontakt treten kann. Ein erfahrener SAP-Mitarbeiter führt durch den Tag und steht Fragen Rede und Antwort.

Im Februar 2017 haben wir unseren ersten SAP Inside Track veranstaltet, der ein großer Erfolg war. Bei einem SAP Inside Track halten SAP-Profis aus der SAP-Community Vorträge über verschiedenste Themen. Die Themen sind meistens technisch und in der Regel mit irgend einem SAP-Bezug. Es wurden jedoch auch schon ein Vortrag über Rum mit anschließender Verköstigung gehalten. Die Inside Tracks sind eine gute Gelegenheit um viele verschiedene Eindrücke zu meistens sehr aktuellen Themen zu bekommen.

Bewerbung – Wir suchen dich!

Inwerken ist klar auf Wachstumskurs. Aus diesem Grund suchen wir neue Mitarbeiter: Berater und Entwickler aus (fast) allen SAP-Teilbereichen. Wenn du gerne in einem modernen Arbeitsumfeld in einem leistungsstarken Team an anspruchsvollen Projekten mitarbeiten möchtest, dann bewirb dich und schicke uns eine Mail: bewerbung@inwerken.de.

Schaue dich gerne bei unseren offenen Stellen um. Es ist nichts für dich dabei? Wenn du das Gefühl hast, dass Inwerken die richtige Firma ist, dann sprich uns trotzdem an! Gute Leute können wir immer gebrauchen.

Ich bin gespannt auf deine Bewerbung!!

Der Beitrag IF cl_qualification=>i_speak( ‚ABAP‘ ) = abap_true. me->apply( ). ENDIF. erschien zuerst auf Tricktresor.


Automatisierte Qualitätsprüfung mit SAP Code Inspector und ABAP Test Cockpit

$
0
0

Das manuelle, also händische Überprüfen von eigenen Entwicklungen kann im Eifer des Gefechts schnell vergessen werden oder in den Hintergrund geraten. Doch genau diese regelmäßigen Qualitätschecks können die letzten Prozente deiner Entwicklung herauskitzeln.

Du möchtest deine ABAP Entwicklungen automatisiert einer Qualitätsprüfung unterziehen? Der Code Inspector in Kombination mit dem ABAP Test Cockpit sind in diesem Fall die Werkzeuge der Wahl.

Neben der manuellen Prüfung durch den Code Inspector und dem Ausführen von statischen Prüfvarianten ist es möglich, die Qualitätsprüfung automatisiert durch eine Transportauftragsfreigabe zu realisieren. Durch ein paar Einstellungen im System lässt sich die Überprüfung gebunden an entsprechende Benutzer definieren.

Definieren einer Prüfvariante

Beginnen wir als Erstes mit dem Code Inspector und dem Definieren einer Prüfvariante.

Abbildung 1: Definieren einer Prüfvariante im Code Inspector

Wie in Abbildung 1 zu sehen ist, sind die Einstellungsmöglichkeiten vielfältig. Angefangen von simpler Prüfung von Namenskonvention bis hin zu HANA Readiness Checks ist beinahe alles möglich.

Objektprüfung bei Auftragsfreigabe

Als Nächstes stellen wir im Transport Organizer (SE03) die „Objektprüfung bei Auftragsfreigabe“ auf „vom Benutzer einstellbar“. Das bedeutet nichts anderes, als jeder User mit den entsprechenden Rechten in den persönlichen Einstellungen festlegen kann, ob eine automatisierte Prüfung durchgeführt werden soll.

Abbildung 2: Objektprüfung bei Auftragsfreigabe

Die persönlichen Einstellungen können auf mehrere Wege erreicht werden. Beispielsweise über die ABAP Workbench -> Object Navigator (SE80) und über die Menüoptionen – Hilfsmittel -> Einstellungen -> Transport Organizer können unter der Option „Individuelle Einstellungen“ die notwendigen Änderungen vorgenommen werden.

Abbildung 3: Freigabe der Objektprüfung in den individuellen Einstellungen

Durch Setzen des Hakens, wie in Abbildung 3 dargestellt, wird eine Code Überprüfung ausschließlich für diesen User festgelegt.

ABAP Test Cockpit

Als Letztes kommt das ABAP Test Cockpit ins Spiel. Hier findet die Verheiratung zwischen der definierten Prüfvariante im Code Inspector und dem Transportsystem statt. Zunächst wird die festgelegte Prüfvariante ausgewählt.

Abbildung 4: Festlegung einer globalen Prüfvariante

Anschließend wird das Verhalten bei gefundenen Meldungen in der Anwendung konfiguriert. Die SAP schlägt dabei vor, den Code Inspector als Testtreiber zu deaktivieren und das Verhalten bei der Freigabe durch das ABAP Test Cockpit zu regeln.

Abbildung 5: Konfiguration des Verhaltens bei gefundenen Meldungen im ABAP Test Cockpit

Es gibt insgesamt drei Abstufungen von Meldungen die geworfen werden können:

  1. Über Fehler informieren (Priorität 1 und 2)
  2. Block auf jedem Fehler (Priorität 1 und 2)
  3. Keine ATC Prüfung

Variante 1 zeigt die gefunden Meldungen an. Der Entwickler kann die Meldungen beheben. Jedoch wird die Freigabe des Transports nicht verhindert. Es ist trotz gefundener Fehler möglich, eine Freigabe durchzuführen.

Die Variante 2 zwingt den Entwickler, alle Fehler und Meldungen zu beheben, bevor eine Auftragsfreigabe erfolgen darf

Die Variante 3 schaltet die Überprüfung durch das ABAP Test Cockpit aus.

Fazit

Durch Aktivieren der automatisierten Prüfung wird verhindert, dass die Qualitätsprüfung einmal vergessen oder weggelassen wird. Mit wenigen Klicks ist es möglich Quellcode zu überprüfen, der sich an den definierten Unternehmensstandards orientiert.

Der Beitrag Automatisierte Qualitätsprüfung mit SAP Code Inspector und ABAP Test Cockpit erschien zuerst auf Tricktresor.

Aus alt mach neu [Bastelprojekt]-1

$
0
0
Da ich in meinem Urlaub eine fixe Idee hatte und immer noch im Reparier- und Bastelmodus bin, folgt heute ein Tricktresor-untypischer Beitrag über Hardware.
Alles begann mit meiner Mama (fängt nicht alles mit der Mutter an…?), deren Small-Form-Factor-PC immer älter und langsamer wird. Als gut erzogener Sohn machte ich mir natürlich sofort Gedanken, wie ich Abhilfe schaffen könnte.
Erster Gedanke: Austausch der Festplatte durch eine SSD. Allerdings wäre dann der PC immer noch recht alt geblieben. Ein neuer PC wäre natürlich möglich, aber auch recht teuer. Also würde ein gebrauchter PC vielleicht in Frage kommen. Ich suchte also ebay-Kleinanzeigen nach entsprechendem Ersatz durch. Da auch der Monitor bereits sehr alt war und ein schlechtes Bild hatte, habe ich auch nach Monitoren Ausschau gehalten.

Anforderungen

Wenn man nach PC’s sucht, dann stolpert man zwangsläufig auch über Notebooks. Mit einem Notebook wollte meine Mama sich jedoch nicht anfreunden. Trotzdem sollte der Computer möglichst leise, klein sein und für normale Office-Arbeiten, surfen und Onlinebanking ausreichen.
Da ich bei ebay Kleinanzeigen nach „monitor“ und „computer“ suchte, bin ich auch über defekte Notebooks gestolpert. Und da kam auf einmal diese fixe Idee von irgendwo her: nimm ein Notebook mit defektem Display, demontiere das Display und baue um das Notebook einen schicken Holzkasten drumherum. Diese Idee hatte mehrere Vorteile:
  • defekte Notebooks sind günstig
  • Notebooks sind prinzipiell leiser als PC’s
  • Mein Spiel- und Basteltrieb würde ausreichend befriedigt
  • Ein Holzgehäuse sieht schick aus

Notebook

Also habe ich Ausschau gehalten nach einem einigermaßen tauglichen Notebook mit defektem Display. Das Stöbern bei ebay und ebay Kleinanzeigen hat ein paar Tage und gedauert. Immer in Verbindung mit der Google-Suche um mich über Notebook-Modelle, CPU-Performance und Speichergrößen zu informieren und zu vergleichen. In der Regel sind Notebooks aufgrund der Wärmeentwicklung, des beschränkten Energiebedarfs und der kompakten Maße eher schwächer als PC’s. Deswegen sollte es kein mehrere Jahre altes Notebook sein. Aber „aktuelle“ Notebooks mit Displayschaden werden immer noch für deutlich über 100 Euro gehandelt. Das ist auch okay, denn immerhin ist es nach wie vor ein vollwertiger PC. Da ich prinzipiell jedoch geizig bin, wollte ich möglichst wenig für einen nicht zwingend notwendigen PC ausgeben.

Gehäuse

Ich habe mich auf die Suche nach Ideen für PC-Gehäuse begeben. Die meisten Ergebnisse beziehen sich jedoch auf Standard-PC-Gehäuse. Und die meisten waren auch sehr aufwändig.
Es gibt ein Bastelprojekt, dass mir sehr gut gefallen hat: Leider finde ich es nicht wieder… 🙁
Der Kollege hat jedenfalls ein einfaches Holzgehäuse gebastelt und die Notebook-Platine drauf geschraubt. Wenn man gutes Holz nimmt, dann sieht auch ein einfaches Gehäuse sehr schick aus, eben weil es Holz ist. Ich habe mich dann noch mit Steampunk-Design beschäftigt und Zeit damit zugebracht, nach passenden Accessoires zu suchen: Kupferrohre, Fittings, alte Druckanzeiger, Zahnräder usw. Vieles war jedoch sehr aufwändig. Gerade bei Steampunk kommt es sehr auf’s Detail an. Ich bastele zwar gerne, aber es sollte auch nicht ausarten.

Hardware

Während des Suchens, Ideenfindens und Vergleichens bin ich in einer lokalen Facebook-Flohmarktgruppe auf ein altes auf Antik getrimmtes Radio gestoßen. Das passte perfekt. Es war kaputt und ich habe es für fünf Euro im Nachbardorf abgeholt.
Ziemlich zeitgleich habe ich mich dann für ein Lenovo G570 mit gesplittertem Display aus der Nähe entschieden, dass ich für EUR 40,- erstanden habe.
Durch die verschiedensten Bilder, Bastelanleitungen und Videos bin ich jedoch auf viele mögliche Hindernisse gestossen. In einem Video hat jemand ein Notebook-Motherboard an Strom angeschlossen, das aber nicht starten wollte. Er hat dann – unter anderem – einen Dummy-Stecker für die Notebooktastatur gelötet, mit dem er eine angeschlossene Tastatur simuliert hat. Gottseidank funktioniert das G570 sofort mit externem Bildschirm über HDMI als auch ohne eingesteckte Tastatur.
Nun musste nur noch das wichtige Innenleben des Notebooks in mein Radiogehäuse passen. Auch hier war des Glück mit die Dummen: das Motherboard passt exakt in das Gehäuse.

Kühlung

Beim Testen des Systems ohne Notebookgehäuse ist mir aufgefallen, dass a) das Notebook gar nicht mal sooo leise ist und b) der Prozessor auch ziemlich heiß wird. Da ich einen leisen PC möchte, dachte ich mir, dass es ja nicht schwer sein dürfte, einen großen Kühlkörper (passiv oder mit Lüfter) anstelle der kleinen verbauten Kühlvorrichtung zu verwenden. Das erwies sich jedoch schwieriger als gedacht. Ich habe zwar in dem Gehäuse prinzipiell genug Platz für einen großen Kühlkörper, aber die Kühlkörper haben alle die Standardmaße für einen PC. Dabei ist der Kühlkörper selbst gar nicht das Problem, sondern die Befestigung. Ein Kühlkörper wird entweder auf einen passenden Prozessorsockel geklipst oder mit vier quadratisch angeordneten Schrauben im Abstand von ca 9 cm aufgeschraubt. In dem Notebook habe ich jedoch eine Halterung die nur etwa 6 x 4,5 cm gross ist…
Eine Google-Recherche hat mir bisher jedoch nicht geholfen, da ich nicht 100%ig in wie weit der verwendete Prozessorsockel mit der Kühlkörperhalterung zusammen hängt. Anscheinend gibt es sogenannte Retention mount modules, die bei der Anbringung helfen können. Leider habe ich auch hier keine passende gefunden, da sich die Beschreibungen immer nur auf die verwendeten Prozessoren oder Sockel beziehen aber nie die Größen der Halterung selbst angegeben werden.

Ideen

Ich hätte gerne der Notebook-Tastatur ebenfalls einen schönen Holzrahmen verpasst. Leider scheitert die Umsetzung daran, dass das Flachbandkabel vom Motherboard zur Tastatur nur ca. 10 cm lang ist. Das Flachbandkabel ist extrem dünn und die Leiterbahnen sind in einer Plastikfolie eingeschweißt. Löten ist wahrscheinlich unmöglich. Das Flachband selbst ist wahrscheinlich auch speziell für dieses Notebook angefertigt worden, so dass es kein Verlängerungskabel hierfür gibt. Wenn ich wüsste, wie das Kabel oder die Steckerverbindung heißt, könnte ich sicherlich fündig werden, aber die Vielfalt der Stecker und Buchsen und Kabel ist enorm.
Wenn ich alte Sachen zum Sperrmüll fahre, werde ich mal nach Kühlkörpern in alten PC’s Ausschau halten. Vielleicht kann ich einen Kühlkörper irgendwie pragmatisch und unprofessionell befestigen.

Sound

Den Lautsprecher des Radio habe ich übrigens bereits an das Notebook angeschlossen. Der Klang ist recht dumpf. Zusammen mit dem internen Lautsprecher, den ich in der Balance etwas lauter eingestellt habe, als die Radiomembran, ergibt sich aber ein gar nicht mal so übler Klang.

Knöpfe

Das alte Radio hat noch herrlich alte Drehregler. Leider werde ich den Lautstärkeregler nicht verwenden können, da die Lautstärke des Notebooks nur per Software oder die Funktionstasten der Notebooktastatur geändert werden kann.
Den drehbaren An-Aus-Schalter mit seinem beim Drehen zu überwindenden Kleinen Widerstand und dem vernehmbaren „Klick“ werde ich auch so nicht verwenden können, da der Powerknopf des Notebooks auf einer Platine aufgelötet ist. Da werde ich nicht das Risiko eingehen, die Funktion durch meine nicht vorhandenen Lötkenntnisse zu gefährden. Glücklicherweise ist der Powerknopf auf einer separaten Platine, so dass ich diese nach vorne legen und den alten Drehknopf immerhin als Druckknopf zum Einschalten missbrauchen kann.

Next

Die Grundlagen habe ich nun geschaffen: Das Mainboard funktioniert auch ohne Gehäuse und es passt in das Radio. Als nächstes werde ich versuchen, das mainboard auf eine Holzplatte zu schrauben, so,dass ich alle losen Teile fixieren kann (WLAN-Modul, Festplatte, Lüfter, …).
Dann muss ich aus einem passenden Stück Holz die Auslässe für die USB-, den HDMI- und LAN-Anschluss ausfräsen. Dieses Hilfsstück soll dann in die Seitenwand des Radios geklebt werden, damit man gut und sicher an diese Anschlüsse kommt. Die Hilfsplatte muss dann irgendwie im Korpus des Radios verschraubt werden, so dass beim Ein- und Ausstecken von Steckern alles an Ort und Stelle bleibt.
stay tuned…

Der Beitrag Aus alt mach neu [Bastelprojekt]-1 erschien zuerst auf Tricktresor.

Datenstrukturen EXPORT TO MEMORY

$
0
0

Die Befehle EXPORT TO MEMORY und IMPORT FROM MEMORY sind einfach zu benutzen und sind eine große Hilfe bei der Speicherung von Daten jeder Art. Mit dem Medium DATABASE werden die Daten in einer Clustertabelle gespeichert. Die bekannteste ist INDX. Zusätzlich muss ein Gebiet angegeben werden (zweistelliges Kürzel) und eine ID.

Mit folgendem kleinen Testprogramm zeige ich dir, wie du einen strukturierten Datensatz ablegen und wieder laden kannst.

Coding

REPORT.

*== Typisierung der Datenstruktur
TYPES: BEGIN OF ty_data,
         key    TYPE matnr,
         mara   TYPE mara,
         t_mard TYPE STANDARD TABLE OF mard WITH DEFAULT KEY,
         t_makt TYPE STANDARD TABLE OF makt WITH DEFAULT KEY,
       END OF ty_data.

*== Datenstruktur
DATA s_data TYPE ty_data.

*== Auswahl: Import oder Export der Daten
PARAMETERS p_import RADIOBUTTON GROUP mode DEFAULT 'X'.
PARAMETERS p_export RADIOBUTTON GROUP mode.
*== Zu lesendes Element (MaterialnummeR)
PARAMETERS p_matnr TYPE matnr DEFAULT '1000002'.

START-OF-SELECTION.

  CASE 'X'.
    WHEN p_export.
      "MARA-Daten lesen
      SELECT SINGLE * FROM mara INTO @s_data-mara WHERE matnr = @p_matnr.
      IF sy-subrc = 0.
        "Schlüssel setzen
        s_data-key = p_matnr.
        "Zusätzliche Tabellen lesen
        SELECT * FROM mard INTO TABLE @s_data-t_mard WHERE matnr = @p_matnr.
        SELECT * FROM makt INTO TABLE @s_data-t_makt WHERE matnr = @p_matnr.
        "Daten in INDX exportieren
        EXPORT data FROM s_data TO DATABASE indx(z1) ID p_matnr.
      ENDIF.

    WHEN p_import.
      "Exportierte Daten zum Schlüssel wieder einlesen
      IMPORT data TO s_data FROM DATABASE indx(z1) ID p_matnr.
  ENDCASE.

  "Daten im Debugger überprüfen
  BREAK-POINT.
Im Debugger sehen die Daten wie folgt aus:

Vorteile

Die Handhabung der beiden Befehle zum Exportieren und Einlesen der Daten ist extrem einfach. Auf diese Weise können einfache Feldleisten, interne Tabellen oder auch komplexe Datenstrukturen unkompliziert in der Datenbank abgelegt werden.

Die Testdaten zu Funktionsbausteinen werden übrigens auf diese Weise verwaltet (Clustertabelle EUFUNC und Gebiet FL).

Die Technik gibt es bereits sehr lange und dürfte ausreichend schnell und sicher in der Anwendung sein.

Nachteile

Änderbarkeit

Der große Nachteil bei der Verwendung: Die Datenstrukturen müssen immer gleich bleiben. Sobald sich die Datenstrukturen ändern, kann es sein, dass der Import alter Daten nicht mehr funktioniert. Die größte Chance auf alte Daten zuzugreifen hat man noch, wenn neue Felder ans Ende der Struktur gesetzt werden. Werden Objekte aus der Mitte der Struktur gelöscht oder eingefügt, kann die Zuordnung der Daten nicht mehr erfolgen. Es erfolgt der Laufzeitfehler CONNE_IMPORT_WRONG_COMP_TYPE. Die gute Nachricht: Du kannst den Laufzeitfehler mit der Ausnahme CX_SY_IMPORT_MISMATCH_ERROR abfangen. Die zweite gute Nachricht: es gibt eine ebenfalls recht komfortable Alternative: Daten dynamisch verwalten

Lesbarkeit

Die Daten werden in einem Datencluster abgespeichert. Dieser Cluster sieht in etwa folgendermaßen aus:

In der gespeicherten Tabelle kannst du also noch sehen, dass Einträge zu einer ID vorhanden sind, aber sie sind nicht mehr lesbar.

Typsisierung

Um die Daten aus der Clustertabelle wieder lesen zu können, musst du zwingend die Struktur der Daten kennen. Ansonsten wird es s o gut wie unmöglich, diese wieder sichtbar zu machen. Aber auch hier hilft der Tipp Daten dynamisch verwalten.

Aufruf der Befehle

Die Methode mit IMPORT TO DATABASE wird häufig in verschiedenen Programmen verwendet, um auf Daten zugreifen zu können, die eigentlich an anderer Stelle nicht mehr zur Verfügung stehen. Dadurch ist es häufig sehr schwer zu erkennen, wo das jeweilige Gegenstück des Befehls verwendet wird. Wenn du an einer Stelle über den IMPORT gestolpert bist, dann ist es eventuell sehr schwer, die Stelle zu finden, die den EXPORT macht und umgekehrt.

Aber auch hier gibt es eine einfache Lösung. Sie bedeutet einen kleinen Aufwand, sollte aber in jedem Fall gemacht werden: Der EXPORT und IMPORT der Daten wird in eine eigene Klasse ausgelagert. Export und Import erfolgen jeweils über die gleichnamige Methode der Klasse. So sind EXPORT und IMPORT für ein Objekt an einer Stelle vorhanden. Zusätzlich kann über einen Verwendungsnachweis der Aufruf ermittelt werden.

Analyse

Der Grund für diesen Artikel ist allerdings, dass ich heute erst – nachdem ich den Befehl schon mehrere Jahre lang verwendet habe – erfahren habe, dass es einen Report gibt, der die Struktur der Daten ausgibt: RSINDX00

Der Ausgabe des Reports sieht man sein Alter an… Erstellt wurde er vor über 15 Jahren. Aber, wenn alle Stricke reißen, so kann er eine gute Hilfe sein, um die gespeicherten Daten zu analysieren:

Der Beitrag Datenstrukturen EXPORT TO MEMORY erschien zuerst auf Tricktresor.

Fibonacci

$
0
0

Ein kleines Beispielprogramm um die verschiedenen Arten der Berechnung von Fibonacci-Zahlen und deren Geschwindigkeit zu demonstrieren.

Ursprünglich habe ich das Programm auf Grund eines Beitrags im ABAP-Forum geschrieben (der aber leider gelöscht wurde) und um die verschiedenen Arten der Berechnung zu vergleichen. Hauptsächlich sollte es ein Vergleich sein zwischen der rekursiven und der iterativen Variante. Dazu gekommen ist dann noch eine Lösung, die mit einer internen Tabelle arbeitet. Ausschlaggebend für die Veröffentlichung war dann ein Beispiel von Lars Hvam dafür, wie man nicht programmieren sollte. Wie man an den Ergebnissen sieht, auch im Sinne der schlechten Performance…

Code

REPORT zz_fibonacci.

DATA result_f TYPE f.
DATA start    TYPE i.
DATA stopp    TYPE i.
DATA i        TYPE i.

PARAMETERS p_n       TYPE i.
PARAMETERS p_reku RADIOBUTTON GROUP b USER-COMMAND space.
PARAMETERS p_iter RADIOBUTTON GROUP b.
PARAMETERS p_tabl RADIOBUTTON GROUP b.
PARAMETERS p_hvam RADIOBUTTON GROUP b.
PARAMETERS p_res  TYPE text50 MODIF ID x.
PARAMETERS p_time TYPE i      MODIF ID x.

CLASS lcl_fibonacci DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS calc_rekursiv IMPORTING n TYPE i RETURNING VALUE(result) TYPE f.
    CLASS-METHODS calc_iterativ IMPORTING n TYPE i RETURNING VALUE(result) TYPE f.
    CLASS-METHODS calc_read_table IMPORTING x TYPE i RETURNING VALUE(result) TYPE f.
    CLASS-METHODS calc_hvam IMPORTING n TYPE i RETURNING VALUE(r) TYPE f.
  PRIVATE SECTION.
    CLASS-METHODS f IMPORTING i TYPE f RETURNING VALUE(f) TYPE f.
ENDCLASS.

CLASS lcl_fibonacci IMPLEMENTATION.

  METHOD calc_iterativ.

    DATA f1 TYPE f VALUE 0.
    DATA f2 TYPE f VALUE 1.
    DATA x  TYPE f VALUE 0.

    IF n <= 0.
      result = 0.
    ELSEIF n = 1.
      result = 1.
    ELSE.
      x = n - 1.
      DO x TIMES.
        result = f1 + f2.
        f1 = f2.
        f2 = result.

      ENDDO.
    ENDIF.

  ENDMETHOD.

  METHOD calc_rekursiv.
    DATA f TYPE f.
    f = n.
    result = f( f ).
  ENDMETHOD.

  METHOD calc_read_table.
    "http://www.abapforum.com/forum/viewtopic.php?f=1&t=21045

    TYPES BEGIN OF ts_fibonacci.        "Strukturtyp
    TYPES n         TYPE i.             "Zählvariable    (Spalte)
    TYPES fib_n     TYPE i.             "Fibonacci-Zahl  (Spalte)
    TYPES rechnung  TYPE string.        "Rechenweg       (Spalte)
    TYPES END OF ts_fibonacci.

    DATA gf_zahl1 TYPE i.
    DATA gf_zahl1_s TYPE string.
    DATA gf_zahl2 TYPE i.
    DATA gf_zahl2_s TYPE string.
    DATA gt_fibzahl TYPE TABLE OF ts_fibonacci.     "Tabelle
    DATA gs_fib TYPE ts_fibonacci.

    DO x TIMES.

      IF sy-index = 1 OR sy-index = 2.
        gs_fib-n = sy-index.
        gs_fib-fib_n = 1.
        gs_fib-rechnung = '-'.

      ELSE.
        READ TABLE gt_fibzahl
        INTO gs_fib
        INDEX sy-index - 1.
        gf_zahl1 = gs_fib-fib_n.

        READ TABLE gt_fibzahl
        INTO gs_fib
        INDEX sy-index - 2.
        gf_zahl2 = gs_fib-fib_n.

        gs_fib-fib_n = gf_zahl1 + gf_zahl2.
        gs_fib-n = sy-index.
        gf_zahl1_s = gf_zahl1.
        gf_zahl2_s = gf_zahl2.
        CONCATENATE gf_zahl1_s '+' gf_zahl2_s INTO gs_fib-rechnung SEPARATED BY space.
      ENDIF.

      APPEND gs_fib TO gt_fibzahl.
      CLEAR gs_fib.

    ENDDO.

    READ TABLE gt_fibzahl INDEX lines( gt_fibzahl ) INTO gs_fib.
    result = gs_fib-fib_n.

  ENDMETHOD.

  METHOD f.
    DATA x TYPE f.
    DATA y TYPE f.

    IF i <= 0.
      f = 0.
    ELSEIF i = 1.
      f = 1.
    ELSE.
      x = i - 2.
      y = i - 1.
      f = f( x ) + f( y ).
    ENDIF.
  ENDMETHOD.

  METHOD calc_hvam.

    "negative example of Lars Hvam for how _NOT_ to code!
    "https://gist.github.com/larshp/cc5326dec8fe413bdc29e4d6b8c64b4f
    DATA n1 TYPE i.
    DATA n2 TYPE i.
    DATA r1 TYPE p.
    DATA r2 TYPE f.

    n2 = n - 1.
    n1 = n2 - 1.
    IF n = 1.
      r = n.
    ELSEIF n := 2.
      r = n - 1.
    ELSE.
      r2 = calc_hvam( n1 ).
      r1 = calc_hvam( n2 ).
    ENDIF.
    r = r + r1 + r2.

  ENDMETHOD.

ENDCLASS.



AT SELECTION-SCREEN OUTPUT.
  LOOP AT SCREEN.
    IF screen-group1 = 'X'.
      screen-input = '0'.
      MODIFY SCREEN.
    ENDIF.
  ENDLOOP.

  GET RUN TIME FIELD start.
  CASE abap_true.
    WHEN p_iter.
      result_f = lcl_fibonacci=>calc_iterativ( p_n ).
    WHEN p_reku.
      result_f = lcl_fibonacci=>calc_rekursiv( p_n ).
    WHEN p_tabl.
      result_f = lcl_fibonacci=>calc_read_table( p_n ).
    WHEN p_hvam.
      result_f = lcl_fibonacci=>calc_hvam( p_n ).
  ENDCASE.

  WRITE result_f TO p_res EXPONENT 0 DECIMALS 0 LEFT-JUSTIFIED.
  GET RUN TIME FIELD stopp.
  p_time = stopp - start.


START-OF-SELECTION.

  DO p_n TIMES.
    i = sy-index.

    GET RUN TIME FIELD start.
    CASE abap_true.
      WHEN p_iter.
        result_f = lcl_fibonacci=>calc_iterativ( i ).
      WHEN p_reku.
        result_f = lcl_fibonacci=>calc_rekursiv( i ).
      WHEN p_tabl.
        result_f = lcl_fibonacci=>calc_read_table( i ).
    ENDCASE.

    WRITE result_f TO p_res EXPONENT 0 DECIMALS 0 LEFT-JUSTIFIED.
    WRITE: / i, p_res.

  ENDDO.

  GET RUN TIME FIELD stopp.
  p_time = stopp - start.

  WRITE: / 'Time:', p_time.
 

Der Beitrag Fibonacci erschien zuerst auf Tricktresor.

Abhängige Suchhilfe

$
0
0

Immer wieder ein Thema in Selektionsbildschirmen oder Dynpros: Abhängig vom Feldwert eines anderen Feldes die Suchhilfe für die angeforderte Suchhilfe einschränken.

Die wirklich einfachste Möglichkeit ist die über die Verknüpfung der Felder in einer Struktur: Feldabhängige Selektion

Manchmal reicht das jedoch nicht aus oder man möchte anhand anderer Feldwerte unterschiedliche Suchhilfen aufrufen. Das folgende Coding zeigt die Möglichkeit mit Hilfe der Funktionsbausteine DYNP_VALUES_READ und DYNP_VALUES_UPDATE.

Feldübertragung / PAI – PBO

Eine kurze Info, warum mit den genannten Bausteinen gearbeitet werden muss: Ein Dynpro ist ein eigenes Objekt. Es können zwar Feldnamen (Variablen) aus dem ABAP-Programm verwendet werden, aber diese sind erst einmal unabhängig vom Dynpro. Ebenso können im Dynpro Felder definiert werden, die im ABAP-Programm nicht bekannt sind.

Die Übertragung der Feldwerte erfolgt über Namensgleichheit.

Ein Dynpro wird im PBO – Process Before Output initialisiert. Hier können Feldattribute (Sichtbar, eingabebereit, …) gesetzt werden. Der GUI-Status und GUI-Titel können gesetzt bzw. geändert werden. Felder können befüllt werden bzw. werden aus dem ABAP-Programm übernommen.

Danach wird das Dynpro mit den Feldern, Feldwerten und Feldattributen angezeigt.

Bei Tastendruck wird das PAI – Process After Input ausgeführt. Hier werden geänderte Feldwerte in die ABAP-Variablen, nach Prüfung auf Richtigkeit (Datum, Festwerte etc), übernommen. Der OK-Code kann ausgewertet werden.

Es gibt drei Ausnahmen von dieser Logik:

  • F1-Hilfe: Die Taste F1 wird ausgeführt, ohne dass der PAI angestoßen wird.
  • F4-Wertehilfe: Auch die F4-Hilfe bewirkt keinen PAI. Das bedeutet, dass ein soeben eingegebener Wert in ein Eingabe bereites Feld nicht in die entsprechende ABAP-Variable übernommen wird!
  • Exit-Kommandos: Funktionen, die im GUI-Status als „Exit-Funktion“ gekennzeichnet sind bewirken zwar einen PAI, jedoch ohne dass Feldprüfungen statt finden oder Feldwerte übernommen werden

F4-Falle

Diese Besonderheiten muss man wissen, um zu verstehen, warum bei einer F4-Hilfe die Datenübertragung zwischen Dynpro und ABAP-Programm nachprogrammiert werden muss. Wenn du im Dynpro in FELD1 einen Wert eingibst und dann, ohne eine Funktionstaste zu drücken (ENTER, F2, F3, etc.) und mit dem Cursor in das FELD2 springst, dann ist der Wert aus FELD1 noch nicht im ABAP-Programm bekannt! Wenn du nun in FELD2 die F4-Werthilfe betätigst, weiß das ABAP-Programm nichts von dem soeben eingegebenen Wert in FELD1. Der Wert muss erst mit DYNP_VALUES_READ ermittelt werden.

Änderst du den Wert für FELD2, also das Feld für das du die Werthilfe aufgerufen hast, dann kannst du im ABAP-Programm dieses Feld einfach füllen. Der Transport zum Dynpro erfolgt automatisch. Wenn du allerdings ein anderes Feld ändern möchtest, dann musst du DYNP_VALUES_UPDATE verwenden.

Beispielprogramm

Das Beispielprogramm demonstriert die Verwendung von DYNP_VALUES_GET und DYNP_VALUES_UPDATE. Bei F4 im Feld P_EINS werden Werte gesetzt. Bei F4 im Feld P_ZWEI wird abhängig von P_EINS ein anderer Wert gesetzt.

Code

REPORT.

PARAMETERS p_eins TYPE char10.
PARAMETERS p_zwei TYPE char10.


AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_eins.
  PERFORM eins.


AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_zwei.
  PERFORM zwei.

FORM eins.

  DATA lt_fields TYPE STANDARD TABLE OF dynpread.
  DATA ls_field  TYPE dynpread.

  p_eins = 'Z'.
  p_zwei = '999'.

  ls_field-fieldname  = 'P_ZWEI'.
  ls_field-fieldvalue = p_zwei.
  APPEND ls_field TO lt_fields.

  CALL FUNCTION 'DYNP_VALUES_UPDATE'
    EXPORTING
      dyname     = sy-cprog
      dynumb     = sy-dynnr
    TABLES
      dynpfields = lt_fields
    EXCEPTIONS
      OTHERS     = 8.
  IF sy-subrc = 0.
    MESSAGE 'Feldwert gesetzt' TYPE 'S'.
  ENDIF.
ENDFORM.

FORM zwei.

  DATA lt_fields TYPE STANDARD TABLE OF dynpread.
  DATA ls_field  TYPE dynpread.

  CALL FUNCTION 'DYNP_VALUES_READ'
    EXPORTING
      dyname                   = sy-repid
      dynumb                   = sy-dynnr
      translate_to_upper       = 'X'
      request                  = 'A'
      perform_conversion_exits = 'X'
    TABLES
      dynpfields               = lt_fields
    EXCEPTIONS
      OTHERS                   = 11.

  READ TABLE lt_fields INTO ls_field WITH KEY fieldname = 'P_EINS'.
  IF sy-subrc = 0.
    p_eins = ls_field-fieldvalue.
  ENDIF.

  CASE p_eins.
    WHEN 'A'.
      p_zwei = '1'.
    WHEN 'B'.
      p_zwei = '2'.
    WHEN 'C'.
      p_zwei = '3'.
  ENDCASE.

ENDFORM.
 

Der Beitrag Abhängige Suchhilfe erschien zuerst auf Tricktresor.

Viewing all 214 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>