<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-34883048</id><updated>2012-01-31T22:08:47.026-05:00</updated><title type='text'>Your Friendly ABAPer</title><subtitle type='html'>Look what I've learned today!</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-34883048.post-6470000165325086093</id><published>2009-12-01T19:06:00.001-05:00</published><updated>2009-12-04T09:56:02.007-05:00</updated><title type='text'>Office 2007 and a Secret Handshake</title><content type='html'>I have a suspicion there is a whole department in Microsoft Corp. the only purpose of which is to make everyone’s life more difficult. It was created when Windows 95 came out and suddenly the file names were not only longer than 8 characters but could also contain symbols from any funky Unicode language out there. That's when &lt;em&gt;ye olde faithful&lt;/em&gt; Norton Commander became pretty much useless, unless I had to repair a file that had the above mentioned funky symbols and could not be opened by some less advanced applications. Oh good old days…&lt;br /&gt;&lt;br /&gt;This time the evil geniuses from the Microsoft’s Department of Screw-You-All decided to mess with the file extensions: familiar ‘doc’ for some reason has become ‘docx’ and ‘xls’ – ‘xlsx’. And while the above mentioned file name expansion in Windows 95 at least had some benefits for the users, this one is just an act of pure evil, if you ask me. Is there a single person in the whole world who is not employed by Microsoft and cares for these new extensions? I honestly doubt that. But noooo, they just had to do this… What is wrong with these people?&lt;br /&gt;&lt;br /&gt;Oh well, the sad reality is that now even ABAP developers have to deal with this somehow. It looks like even the SAP masterminds did not expect such audacious move by Microsoft (&lt;em&gt;check and mate!&lt;/em&gt;), because all the function modules that are somehow involved with the file manipulations (including SO_OBJECT_INSERT, which I used in my previous GOS-related &lt;a href="http://friendlyabaper.blogspot.com/2008/07/oh-my-gos.html"&gt;post&lt;/a&gt;) have allocated only 3 characters for the variable to hold the file extension. &lt;br /&gt;&lt;br /&gt;But there is no need to fear - SAP is here. Well, sort of. I’m not sure if this information is supposed to be given on strictly “need to know” basis or if there is some kind of a secret handshake one must master first, but the solution below doesn’t seem to be documented or disclosed anywhere. We got it from the SAP support team when we sent them a message asking what are we supposed to do with the new 4-character extensions.&lt;br /&gt;&lt;br /&gt;As it turns out, the filename with extension may also be passed to the function modules using the table OBJHEAD (I’m talking about FM SO_OBJECT_INSERT here, but a similar table is present, for example, in SO_DOCUMENT_INSERT_API1 and many other SO_... FMs). The only thing is that you can’t just put the filename there, it has to be preceded by “the secret code” (wait for it… wait for it…) '&amp;SO_FILENAME='. Why? I don’t know why, but I guess the SAP developers saw this coming and they created some kind of a back entrance in case something goes wrong (and it did).&lt;br /&gt;&lt;br /&gt;Another interesting thing – in the previous post I mentioned that it’s not necessary to calculate the exact object size and it was true for Office 2003 files (at least it worked for me). However, this didn’t work with Office 2007 files and I ended up doing the same thing as everyone else: get the number of lines, read the last line, etc.&lt;br /&gt;&lt;br /&gt;Also, since the extension is now not just the last 3 characters of the filename, I used FM CH_SPLIT_FILENAME to split the full filename. Interestingly enough, the extension that we pass in ls_obj_data-file_ext, in fact, does not matter much. You can pass the Word document filename in OBJHEAD table but ‘XLS’ extension and it will still open the attachment in Word. However, in the GOS menu the file will be displayed with an Excel icon, so it’ll just look more neat if we pass the correct extension (it will be truncated to 3 characters, but it’s fine).&lt;br /&gt;&lt;br /&gt;Here is the full code from the previous post, enhanced for the Office 2007 extensions (and it should also work fine with the old extensions):&lt;br /&gt;&lt;blockquote&gt;PARAMETERS: p_key  TYPE swo_typeid OBLIGATORY,&lt;br /&gt;            p_type TYPE swo_objtyp OBLIGATORY,&lt;br /&gt;            p_file TYPE string OBLIGATORY,&lt;br /&gt;            p_desc TYPE so_obj_des OBLIGATORY.&lt;br /&gt;&lt;br /&gt;DATA: ls_fol_id   TYPE soodk,&lt;br /&gt;      ls_obj_id   TYPE soodk,&lt;br /&gt;      ls_obj_data TYPE sood1,&lt;br /&gt;      ls_folmem_k TYPE sofmk,&lt;br /&gt;      ls_note     TYPE borident,&lt;br /&gt;      ls_object   TYPE borident,&lt;br /&gt;      lv_ep_note  TYPE borident-objkey,&lt;br /&gt;*      lv_offset   TYPE i,&lt;br /&gt;      lv_lines    TYPE i,&lt;br /&gt;      lv_filename TYPE c LENGTH 100, " file name and ext&lt;br /&gt;      lv_extension TYPE c LENGTH 4.  " extension only&lt;br /&gt;&lt;br /&gt;DATA: it_objhead TYPE STANDARD TABLE OF soli,&lt;br /&gt;      it_content LIKE STANDARD TABLE OF soli,&lt;br /&gt;      wa_content LIKE soli.&lt;br /&gt;&lt;br /&gt;ls_object-objkey = p_key.&lt;br /&gt;ls_object-objtype = p_type.&lt;br /&gt;&lt;br /&gt;TRY.&lt;br /&gt;    OPEN DATASET p_file FOR INPUT IN BINARY MODE.&lt;br /&gt;    WHILE sy-subrc = 0.&lt;br /&gt;      READ DATASET p_file INTO wa_content.&lt;br /&gt;      APPEND wa_content TO it_content.&lt;br /&gt;    ENDWHILE.&lt;br /&gt;    CLOSE DATASET p_file.&lt;br /&gt;  CATCH cx_sy_file_access_error.&lt;br /&gt;    MESSAGE 'Error reading file' TYPE 'E'.&lt;br /&gt;ENDTRY.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_CONVERT_CONTENTS_BIN'&lt;br /&gt;  EXPORTING&lt;br /&gt;    it_contents_bin = it_content[]&lt;br /&gt;  IMPORTING&lt;br /&gt;    et_contents_bin = it_content[].&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'&lt;br /&gt;  EXPORTING&lt;br /&gt;    region    = 'B'&lt;br /&gt;  IMPORTING&lt;br /&gt;    folder_id = ls_fol_id&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    OTHERS    = 1.&lt;br /&gt;&lt;br /&gt;* Get file name and extension&lt;br /&gt;CALL FUNCTION 'CH_SPLIT_FILENAME'&lt;br /&gt;  EXPORTING&lt;br /&gt;    complete_filename = p_file&lt;br /&gt;  IMPORTING&lt;br /&gt;    extension         = lv_extension&lt;br /&gt;    name_with_ext     = lv_filename&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    invalid_drive     = 1&lt;br /&gt;    invalid_path      = 2&lt;br /&gt;    OTHERS            = 3.&lt;br /&gt;IF sy-subrc &lt;&gt; 0.&lt;br /&gt;  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno&lt;br /&gt;          WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.&lt;br /&gt;ENDIF.&lt;br /&gt;&lt;br /&gt;ls_obj_data-objsns = 'O'.&lt;br /&gt;ls_obj_data-objla  = sy-langu.&lt;br /&gt;ls_obj_data-objdes = p_desc.&lt;br /&gt;*lv_offset = STRLEN( p_file ) - 3.&lt;br /&gt;*ls_obj_data-file_ext = p_file+lv_offset(3).&lt;br /&gt;ls_obj_data-file_ext = lv_extension.&lt;br /&gt;*ls_obj_data-objlen = LINES( it_content ) * 255.&lt;br /&gt;CLEAR wa_content.&lt;br /&gt;DESCRIBE TABLE it_content LINES lv_lines.&lt;br /&gt;READ TABLE it_content INTO wa_content INDEX lv_lines.&lt;br /&gt;ls_obj_data-objlen = ( lv_lines - 1 ) * 255 + STRLEN( wa_content ).&lt;br /&gt;&lt;br /&gt;* Object header&lt;br /&gt;CLEAR wa_content.&lt;br /&gt;CONCATENATE '&amp;SO_FILENAME=' lv_filename INTO wa_content.&lt;br /&gt;APPEND wa_content TO it_objhead.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_OBJECT_INSERT'&lt;br /&gt;  EXPORTING&lt;br /&gt;    folder_id             = ls_fol_id&lt;br /&gt;    object_type           = 'EXT'&lt;br /&gt;    object_hd_change      = ls_obj_data&lt;br /&gt;  IMPORTING&lt;br /&gt;    object_id             = ls_obj_id&lt;br /&gt;  TABLES&lt;br /&gt;    objhead               = it_objhead&lt;br /&gt;    objcont               = it_content&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    active_user_not_exist = 35&lt;br /&gt;    folder_not_exist      = 6&lt;br /&gt;    object_type_not_exist = 17&lt;br /&gt;    owner_not_exist       = 22&lt;br /&gt;    parameter_error       = 23&lt;br /&gt;    OTHERS                = 1000.&lt;br /&gt;&lt;br /&gt;IF sy-subrc = 0 AND ls_object-objkey IS NOT INITIAL.&lt;br /&gt;  ls_folmem_k-foltp = ls_fol_id-objtp.&lt;br /&gt;  ls_folmem_k-folyr = ls_fol_id-objyr.&lt;br /&gt;  ls_folmem_k-folno = ls_fol_id-objno.&lt;br /&gt;  ls_folmem_k-doctp = ls_obj_id-objtp.&lt;br /&gt;  ls_folmem_k-docyr = ls_obj_id-objyr.&lt;br /&gt;  ls_folmem_k-docno = ls_obj_id-objno.&lt;br /&gt;  lv_ep_note = ls_folmem_k.&lt;br /&gt;  ls_note-objtype = 'MESSAGE'.&lt;br /&gt;  ls_note-objkey  = lv_ep_note.&lt;br /&gt;  CALL FUNCTION 'BINARY_RELATION_CREATE_COMMIT'&lt;br /&gt;    EXPORTING&lt;br /&gt;      obj_rolea    = ls_object&lt;br /&gt;      obj_roleb    = ls_note&lt;br /&gt;      relationtype = 'ATTA'&lt;br /&gt;    EXCEPTIONS&lt;br /&gt;      OTHERS       = 1.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Not OK' TYPE 'I'.&lt;br /&gt;  RETURN.&lt;br /&gt;ENDIF.&lt;br /&gt;&lt;br /&gt;IF sy-subrc = 0.&lt;br /&gt;  MESSAGE 'OK' TYPE 'I'.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Not OK' TYPE 'I'.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;&lt;br /&gt;Not everything is rainbows and butterflies with this solution, unfortunately. So far I have been unable to get it to work absolutely correctly with the Word 2007 files. The files do open, but Word keeps complaining that they’re corrupt and wants to “recover” them. If you click “Yes’ to recover, the file will open just fine, but this is very confusing and annoying to the users. Excel 2007 works fine, but Word just refuses to cooperate. I tried to use GUI_UPLOAD and upload the file from PC, tried different things with the uploaded table, but with no success so far. I know it must be possible to upload Word 2007 files correctly, because it is somehow working in transaction SO01, for example, when adding an attachment to the workflow (don’t forget to implement the note 1364402 beforehand). If anyone finds some solution to this problem, please let us know.&lt;br /&gt;&lt;br /&gt;To make up for this – a small bonus feature. While doing some debugging, I discovered another “secret code” – if you add a line to OBJHEAD (or a similar) table with '&amp;SO_FORMAT=BIN' then the file/attachment contents will always be treated as binary. And actually I got to use this one already in another program that calls SO_DOCUMENT_INSERT_API1 and where we had problems with HTML attachments being interpreted as plain text.&lt;br /&gt;&lt;br /&gt;Enjoy – no secret handshake required!&lt;a href="http://friendlyabaper.blogspot.com/2008/07/oh-my-gos.html"&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-6470000165325086093?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/6470000165325086093/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=6470000165325086093' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/6470000165325086093'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/6470000165325086093'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2009/12/office-2007-and-secret-handshake.html' title='Office 2007 and a Secret Handshake'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-8220488904965376691</id><published>2009-07-31T19:13:00.001-04:00</published><updated>2009-07-31T19:13:00.542-04:00</updated><title type='text'>My Super-awesome Selection Screen</title><content type='html'>&lt;a href="http://1.bp.blogspot.com/_Eh4NJZiXByQ/SnNQ2uW9diI/AAAAAAAAAk0/JmhL7n9MalM/s1600-h/sel_screen.bmp"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 165px;" src="http://1.bp.blogspot.com/_Eh4NJZiXByQ/SnNQ2uW9diI/AAAAAAAAAk0/JmhL7n9MalM/s320/sel_screen.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5364720482215884322" /&gt;&lt;/a&gt;For a long time I was pretty happy with plain and simple selection screens and, even though I was aware that they could be manipulated, I’ve never ventured there myself. But I guess there is time for everything. &lt;br /&gt;Recently we got a request for practically the same report from two different users. One of them wanted to see the information for the whole year in monthly “buckets” (i.e. one month = one column in the report), but the other wanted to run the report for any period of time and get the total number for the period. My first reaction was to ask the second guy to export the report to Excel and run the total there, but that wouldn’t be very nice, would it? &lt;br /&gt;&lt;br /&gt;So I decided it was time for me to learn how to create a somewhat dynamic selection screen to make both users happy with one report. An obvious quick and dirty solution would be to have two parameters on the screen with a validation that at least one of them (but not both) must be entered. But I wanted something more elegant and decided to explore an option of opening and hiding the parameters. The users would be provided with 2 mutually exclusive options (radio button): full year and any period. Depending on the choice, an additional parameter for either the year or a period would open (see the picture on the left).&lt;br /&gt;&lt;br /&gt;After just a couple of hours of research and scratching my head I came to this prototype (the commentary follows):&lt;br /&gt;&lt;blockquote&gt;TABLES: sscrfields.&lt;br /&gt;DATA: lv_spmon TYPE spmon.&lt;br /&gt;&lt;br /&gt;PARAMETERS:&lt;br /&gt;  rb_year TYPE flag RADIOBUTTON GROUP g1 DEFAULT 'X' USER-COMMAND radio,&lt;br /&gt;  p_year TYPE gjahr MODIF ID yr,&lt;br /&gt;  rb_mth TYPE flag RADIOBUTTON GROUP g1.&lt;br /&gt;SELECT-OPTIONS: s_period FOR lv_spmon NO-EXTENSION MODIF ID mth.&lt;br /&gt;&lt;br /&gt;AT SELECTION-SCREEN ON RADIOBUTTON GROUP g1.&lt;br /&gt;  PERFORM change_screen.&lt;br /&gt;&lt;br /&gt;AT SELECTION-SCREEN.&lt;br /&gt;  IF sscrfields-ucomm = 'ONLI'.&lt;br /&gt;    IF rb_year = 'X' AND p_year IS INITIAL.&lt;br /&gt;      MESSAGE 'Enter Year' TYPE 'E'.&lt;br /&gt;    ENDIF.&lt;br /&gt;    IF rb_mth = 'X' AND s_period[] IS INITIAL.&lt;br /&gt;      MESSAGE 'Enter Period' TYPE 'E'.&lt;br /&gt;    ENDIF.&lt;br /&gt;  ENDIF.&lt;br /&gt;&lt;br /&gt;AT SELECTION-SCREEN OUTPUT.&lt;br /&gt;  PERFORM change_screen.&lt;br /&gt;&lt;br /&gt;START-OF-SELECTION.&lt;br /&gt;&lt;br /&gt;  WRITE 'Hello world'.&lt;br /&gt;&lt;br /&gt;*&amp;---------------------------------------------------------------------*&lt;br /&gt;*&amp;      Form  change_screen&lt;br /&gt;*&amp;---------------------------------------------------------------------*&lt;br /&gt;&lt;br /&gt;FORM change_screen.&lt;br /&gt;&lt;br /&gt;  LOOP AT SCREEN.&lt;br /&gt;    IF screen-group1 = 'MTH'.&lt;br /&gt;      IF rb_year = 'X'.&lt;br /&gt;        screen-active = '0'.&lt;br /&gt;      ELSE.&lt;br /&gt;        screen-active = '1'.&lt;br /&gt;      ENDIF.&lt;br /&gt;      MODIFY SCREEN.&lt;br /&gt;    ELSEIF screen-group1 = 'YR'.&lt;br /&gt;      IF rb_mth = 'X'.&lt;br /&gt;        screen-active = '0'.&lt;br /&gt;      ELSE.&lt;br /&gt;        screen-active = '1'.&lt;br /&gt;      ENDIF.&lt;br /&gt;      MODIFY SCREEN.&lt;br /&gt;    ENDIF.&lt;br /&gt;  ENDLOOP.&lt;br /&gt;&lt;br /&gt;ENDFORM.                    "change_screen&lt;/blockquote&gt;&lt;br /&gt;OK, so here we have a routine change_screen that is called either before the selection screen is displayed or when a user clicks on the radio button. Note that RB_YEAR (i.e. whole year option) is the default choice and in this case we have to declare the default value ‘X’ explicitly, because otherwise RB_YEAR value will be initial on the first pass (i.e. before displaying the screen). &lt;br /&gt;&lt;br /&gt;Help on MODIF ID command kind of implies that you have to use it to be able to modify the field. Well, this is not entirely accurate. MODIF ID simply provides a convenient way to modify not just the parameter field itself but all the related fields as well. For example, I started with using SCREEN-NAME field, but quickly realized that P_YEAR means only the actual input field. The corresponding label is actually a separate field. So at first I ended up with a hidden input field, but the label was still displayed. True, you could use SCREEN-NAME CP ‘*P_YEAR*’, but modification ID makes it much cleaner and easier.&lt;br /&gt;&lt;br /&gt;Surprisingly, the most challenging piece was to make sure that the correct parameter would be required. My first idea was to use SCREEN-REQUIRED, but it turned out that I couldn’t modify this field at the right moment – for example, S_PERIOD would still be required after the user clicked on the RB_YEAR radio button. I did discover an unexpected benefit of this option though: it can make both “from” and “to” fields in a selection option required.&lt;br /&gt;&lt;br /&gt;After that failed, I moved the validation to the AT SELECTION-SCREEN event. But I still wasn’t happy because it kicked in even when the user just hit the Enter key and I wanted it to work only when the user would click the Execute button to actually run the report. Fortunately, I was able to use SSCRFIELDS-UCOMM field to check the Execute button’s OK code. Note that SSCRFIELDS had to be declared with TABLES. Normally I’m trying to avoid this kind of declaration because it’s usually not necessary and is just a waste of space. I hate when people declare the whole table with 200 fields when they really need just a few variables – it’s just lazy. Often developers use TABLES to be able to refer to a field in SELECT-OPTIONS, but, as you see in this example, this is not required either – you can actually use any variable in SELECT-OPTIONS.&lt;br /&gt;&lt;br /&gt;A word of caution – this kind of validation on selection screen won’t work if the user starts the report in the background mode, that's why in the actual program I’ve added some default values (e.g. current year for P_YEAR) in case the validation gets bypassed.&lt;br /&gt;&lt;br /&gt;There are some more things you can do with the selection screens (or any screens, for that matter) using the SCREEN structure. For example, by using SCREEN-HIDDEN the input field may be covered with asterisks, like the password field on the logon screen. If you set SCREEN-INTENSIFIED to 1, the field will be displayed in a brighter color. Sadly, the SCREEN-COLOR doesn’t do anything. There is a good reason for it though: each user could have his/her own color theme, so if we started coloring the fields explicitly it would not make much sense.&lt;br /&gt;&lt;br /&gt;A good demo of all the SCREEN possibilities is the report &lt;strong&gt;DEMO_DYNPRO_MODIFY_SCREEN&lt;/strong&gt;. You will also find it in the ABAP Examples (in the ABAP Editor go to the menu &lt;em&gt;Environment -&gt; Examples -&gt; ABAP Examples&lt;/em&gt;). In fact, there are very many excellent code examples there; I highly recommend exploring that area.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-8220488904965376691?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/8220488904965376691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=8220488904965376691' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/8220488904965376691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/8220488904965376691'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2009/07/my-super-awesome-selection-screen.html' title='My Super-awesome Selection Screen'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Eh4NJZiXByQ/SnNQ2uW9diI/AAAAAAAAAk0/JmhL7n9MalM/s72-c/sel_screen.bmp' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-143490954071433762</id><published>2008-07-05T11:08:00.003-04:00</published><updated>2008-08-18T20:18:07.981-04:00</updated><title type='text'>Oh my GOS!</title><content type='html'>&lt;a href="http://bp3.blogger.com/_Eh4NJZiXByQ/SG-Or4D3TvI/AAAAAAAAAPg/IJUPqYKCV74/s1600-h/GOS_1.bmp"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_Eh4NJZiXByQ/SG-Or4D3TvI/AAAAAAAAAPg/IJUPqYKCV74/s200/GOS_1.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5219547377579806450" /&gt;&lt;/a&gt;Behold, here comes another 3-letter abbreviation from the creators of SAP - GOS or Generic Object Services. Unlike some other abbreviations, such as USCIS (United States Citizenship and Immigration Services) or MVS (Motor Vehicle Services), this one is not a tool of oppression and actually does provide service of some sort.&lt;br /&gt;&lt;br /&gt;GOS manifests its presence in SAP systems in the form of an inconspicuous button on certain transaction screens. In this particular example (see picture) it is transaction VF02 - Billing Document Change (note that you have to enter a document number and hit Enter key first). If you click on this button, either a menu or a toolbar will open (depending on whether you click on the right or the left part of the button), but both provide the same functionality. By using it, one can add attachments, notes, URLs, start the workflow, check the IDocs for the document and what's not. For instance, with this button it would be possible to attach a PDF file (say, a scanned invoice with the customer's signature) to the corresponding billing document (e.g. invoice) in SAP. This is, of course, just an example, there are actually quite a few things you can do with it. Naturally, at some point the users will find out about this and they will want you to do their work and to write a program that will read their mind and attach their deep thoughts in a proprietary format to the SAP documents. But fear not - even though the mind-reading SAP module  probably won't be out until at least ECC 7.0 (or whatever they decide to call it), the rest can be successfully accomplished in good old ABAP.&lt;br /&gt;&lt;br /&gt;Actually almost all of the work has been done for us already by Mr. Ram Manohar Tiwari, who kindly posted a very nice code sample in an SDN blog. He even provided a non-object version in the second installment of the blog for the release-challenged or OO-phobic (which kind of includes me).&lt;br /&gt;&lt;br /&gt;As much as I was hoping that I'll just copy-paste the code and be done with it, this did not happen. From the blog comments it seemed that many others ran into the same problem (more on that later), yet no one, including the author, could provide a solution. Now I had to get to the bottom of this!&lt;br /&gt;&lt;br /&gt;When I'm re-reading the Mr. Tiwari's blog now, it seems that he's explaining everything rather well, but when I saw it for the first time, it wasn't nearly as clear. So let's see if we can dumb it down a notch. The second code sample (with FMs, not classes/methods) to me is easier to use and explain, so I'll go with that. Also all this GOS stuff can be done with other documents and objects, but I'll use the specific example with creating a non-plain-text attachment for an invoice for illustration purposes.&lt;br /&gt;&lt;br /&gt;The starting point: we have an invoice 90052466 in SAP and we have, say, a Word document (.doc) file on a server. The goal is to somehow attach that file to the invoice by running a program. To do so, we'll perform the following steps.&lt;br /&gt;&lt;br /&gt;1. Read the file into memory. For that we can use OPEN DATASET if the file is on a server or FM GUI_DOWNLOAD if the file is on a local PC (e.g. on your laptop's C: drive). Don't worry that it's a Word file - if you use the binary mode, it will be OK.&lt;br /&gt;2. Get a "folder ID" by calling SO_FOLDER_ROOT_ID_GET. Now this part is rather confusing (does this FM actually create a folder somewhere?), but just look at it this way - you're getting an address of where your file contents will be stored in SAP, that's all.&lt;br /&gt;3. Put the file into that "folder" by calling SO_OBJECT_INSERT. The content of the file is passed to this function module in a table.&lt;br /&gt;4. The FM SO_OBJECT_INSERT will give you the "object ID" for your file. At this point your file is already stored somewhere in the depths of SAP Office (read the OSS note 904711 to find more, if you wish) and the last step is to link this "object ID" to the invoice. To do so, we call the FM BINARY_RELATION_CREATE_COMMIT. &lt;br /&gt;&lt;br /&gt;But, dig this, there is no VBELN parameter or anything like that, right? So how does it know to link to the invoice? Well, for you it's an invoice, but for GOS it's just an abstract "object" with "object type" and "object key". "Object type" is sort of a type identifier, it starts with 'BUS' followed by 4 digits (e.g. BUS2037 for invoice or BUS2012 for purchase order). But "object key" in our case is the invoice number (VBELN), leading zeroes and all.&lt;br /&gt;&lt;br /&gt;How do I know that invoice is BUS2037? Funny you should ask... I'm sure there is a transaction where you can see all those "BUSes", but here is what I do. Go to ST05, activate the trace with filter by your user ID. Then go to transaction VF02 and create an attachment. Now go back to ST05, deactivate the trace and display it.&lt;br /&gt;&lt;br /&gt;On the list you will most likely see something like this (do Ctrl-F and type in BUS to search for this line):&lt;blockquote&gt;SELECT WHERE "MANDT" = '100' AND "SAP_OBJECT" = 'BUS2037' AND "OBJECT_ID" LIKE '0090052466%'&lt;/blockquote&gt;&lt;br /&gt;See? Now not only we know what the type is, but also what the object key should look like.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://bp3.blogger.com/_Eh4NJZiXByQ/SG-RmnF8DDI/AAAAAAAAAPo/1lR6CiEgY7A/s1600-h/GOS_2.bmp"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_Eh4NJZiXByQ/SG-RmnF8DDI/AAAAAAAAAPo/1lR6CiEgY7A/s320/GOS_2.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5219550585660640306" /&gt;&lt;/a&gt;The last piece of magic that separates this post from being just an SDN blog's rip-off is the crucial step, which was sort of overlooked by Mr. Tiwari. After running the copy/pasted code sample (with few adjustments), the .DOC attachment was created successfully, but Word could not open it. And this is not very useful, isn't it? Something was obviously wrong with the file content interpretation in SAP... After a futile attempt to debug the GOS button in transaction, I turned to search on SDN and OSS for anything related to the binary file conversion. Luckily, rather soon I stumbled upon the function module SO_CONVERT_CONTENTS_BIN. After being inserted between OPEN DATASET and FM SO_OBJECT_INSERT, it finally forced the evil Word to open the attachment as the nature intended. I didn't think I would ever be so happy to see the words 'Test line number 1' in my life!&lt;br /&gt;&lt;br /&gt;Since, apparently, some SDN blog readers were able to use the code, I guess that this issue might affect only the ECC 6.0 release and/or the Unicode systems. So try the code without the SO_CONVERT_CONTENTS_BIN FM first, maybe it will work for you.&lt;br /&gt;&lt;br /&gt;Other adjustments that I made in the original code (apart from the general clean-up):&lt;br /&gt;1) OPEN DATASET has to be in either TRY... ENDTRY or CATCH SYSTEM-EXCEPTIONS (in older releases) to avoid short dumps if there is some problem with the file.&lt;br /&gt;2) Parameters include the file name (P_FILE) and the attachment title (P_DESC). The title is just sort of the attachment's name displayed in the attachment list, as on the picture here. The file name should look something like &lt;em&gt;\\sapcoredev01\nice_basis_guy_created_this_folder_for_me\test.doc&lt;/em&gt;. Note that the program derives the extension from the last 3 characters of the file name, but you may also pass the extension as a parameter. In this case P_FILE could be of type STRING, to make life easier.&lt;br /&gt;3) The field ls_obj_data-objlen is, naturally, the object's total length in characters. Since I'm using a 255-character field for the file contents, then I just multiply number of lines in the table by 255. On SDN you'll see many examples where people are trying to get the exact number and they calculate number of lines minus 1, then they add the number of characters in the last line, etc. This is completely unnecessary. &lt;br /&gt;&lt;br /&gt;So here is my homage, if you will, to Mr. Tiwari's blog:&lt;br /&gt;&lt;blockquote&gt;PARAMETERS: p_key  TYPE swo_typeid OBLIGATORY,&lt;br /&gt;            p_type TYPE swo_objtyp OBLIGATORY,&lt;br /&gt;            p_file TYPE c LENGTH 100 OBLIGATORY,&lt;br /&gt;            p_desc TYPE so_obj_des OBLIGATORY.&lt;br /&gt;&lt;br /&gt;DATA: ls_fol_id   TYPE soodk,&lt;br /&gt;      ls_obj_id   TYPE soodk,&lt;br /&gt;      ls_obj_data TYPE sood1,&lt;br /&gt;      ls_folmem_k TYPE sofmk,&lt;br /&gt;      ls_note     TYPE borident,&lt;br /&gt;      ls_object   TYPE borident,&lt;br /&gt;      lv_ep_note  TYPE borident-objkey,&lt;br /&gt;      lv_offset   TYPE i.&lt;br /&gt;&lt;br /&gt;DATA: it_objhead TYPE STANDARD TABLE OF soli,&lt;br /&gt;      it_content LIKE STANDARD TABLE OF soli,&lt;br /&gt;      wa_content LIKE soli.&lt;br /&gt;&lt;br /&gt;ls_object-objkey = p_key.&lt;br /&gt;ls_object-objtype = p_type.&lt;br /&gt;&lt;br /&gt;TRY.&lt;br /&gt;    OPEN DATASET p_file FOR INPUT IN BINARY MODE.&lt;br /&gt;    WHILE sy-subrc = 0.&lt;br /&gt;      READ DATASET p_file INTO wa_content.&lt;br /&gt;      APPEND wa_content TO it_content.&lt;br /&gt;    ENDWHILE.&lt;br /&gt;    CLOSE DATASET p_file.&lt;br /&gt;  CATCH cx_sy_file_access_error.&lt;br /&gt;    MESSAGE 'Error reading file' TYPE 'E'.&lt;br /&gt;ENDTRY.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_CONVERT_CONTENTS_BIN'&lt;br /&gt;  EXPORTING&lt;br /&gt;    it_contents_bin = it_content[]&lt;br /&gt;  IMPORTING&lt;br /&gt;    et_contents_bin = it_content[].&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'&lt;br /&gt;  EXPORTING&lt;br /&gt;    region    = 'B'&lt;br /&gt;  IMPORTING&lt;br /&gt;    folder_id = ls_fol_id&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    OTHERS    = 1.&lt;br /&gt;&lt;br /&gt;ls_obj_data-objsns = 'O'.&lt;br /&gt;ls_obj_data-objla  = sy-langu.&lt;br /&gt;ls_obj_data-objdes = p_desc.&lt;br /&gt;lv_offset = STRLEN( p_file ) - 3.&lt;br /&gt;ls_obj_data-file_ext = p_file+lv_offset(3).&lt;br /&gt;ls_obj_data-objlen = LINES( it_content ) * 255.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'SO_OBJECT_INSERT'&lt;br /&gt;  EXPORTING&lt;br /&gt;    folder_id             = ls_fol_id&lt;br /&gt;    object_type           = 'EXT'&lt;br /&gt;    object_hd_change      = ls_obj_data&lt;br /&gt;  IMPORTING&lt;br /&gt;    object_id             = ls_obj_id&lt;br /&gt;  TABLES&lt;br /&gt;    objhead               = it_objhead&lt;br /&gt;    objcont               = it_content&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    active_user_not_exist = 35&lt;br /&gt;    folder_not_exist      = 6&lt;br /&gt;    object_type_not_exist = 17&lt;br /&gt;    owner_not_exist       = 22&lt;br /&gt;    parameter_error       = 23&lt;br /&gt;    OTHERS                = 1000.&lt;br /&gt;&lt;br /&gt;IF sy-subrc = 0 AND ls_object-objkey IS NOT INITIAL.&lt;br /&gt;  ls_folmem_k-foltp = ls_fol_id-objtp.&lt;br /&gt;  ls_folmem_k-folyr = ls_fol_id-objyr.&lt;br /&gt;  ls_folmem_k-folno = ls_fol_id-objno.&lt;br /&gt;  ls_folmem_k-doctp = ls_obj_id-objtp.&lt;br /&gt;  ls_folmem_k-docyr = ls_obj_id-objyr.&lt;br /&gt;  ls_folmem_k-docno = ls_obj_id-objno.&lt;br /&gt;  lv_ep_note = ls_folmem_k.&lt;br /&gt;  ls_note-objtype = 'MESSAGE'.&lt;br /&gt;  ls_note-objkey  = lv_ep_note.&lt;br /&gt;  CALL FUNCTION 'BINARY_RELATION_CREATE_COMMIT'&lt;br /&gt;    EXPORTING&lt;br /&gt;      obj_rolea    = ls_object&lt;br /&gt;      obj_roleb    = ls_note&lt;br /&gt;      relationtype = 'ATTA'&lt;br /&gt;    EXCEPTIONS&lt;br /&gt;      OTHERS       = 1.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Not OK' TYPE 'I'.&lt;br /&gt;  RETURN.&lt;br /&gt;ENDIF.&lt;br /&gt;&lt;br /&gt;IF sy-subrc = 0.&lt;br /&gt;  MESSAGE 'OK' TYPE 'I'.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Not OK' TYPE 'I'.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;&lt;br /&gt;In case you're wondering, this code may also be put in an RFC-enabled function module if, for example, you'd like to create the attachments from outside of SAP. In this case instead of using the file name, you may just directly pass the file contents to the FM in a table. For that table, use type SOLIX_TAB, which is a RAW format and convert it to SOLI_TAB by using SO_SOLIXTAB_TO_SOLITAB. Naturally, you won't need OPEN DATASET anymore, just start with step # 2 above.&lt;br /&gt;&lt;br /&gt;And, of course, it's not just for the Word .DOC files, .PDF will work just fine (I checked!), as well as the other file formats that SAP can read and digest. I've heard that this technique could be used to add, for example, photos to the employee files (haven't tried it myself though).&lt;br /&gt;&lt;br /&gt;GOS has been available at least since the release 4.6, but it does require some setup in the system before you'll be able to see "the magic button". You'll find some information on this in SAP Help (see the link below), but I personally couldn't make much sense of it (please feel free to add a comment if you have more information on this). If you're having some issues with accessing GOS, I'd suggest to check with your Basis admin. &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;OSS notes of interest:&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;There is a rather scary number of the GOS-related OSS notes. Here are some notes that might be of interest to ABAP'ers:&lt;br /&gt;927407 - Determining the content of GOS and SAPoffice documents&lt;br /&gt;904711 - SAPoffice: Where are documents physically stored?&lt;br /&gt;916512 - SAPoffice: Directory for upload/download/display &lt;br /&gt;448074 - Calling generic object services from the dialog&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/wlg/2519"&gt;SDN Blog GOS in Background - Part I&lt;/a&gt;&lt;br /&gt;&lt;a href="https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/wlg/3399"&gt;SDN Blog GOS in Background - Part II&lt;/a&gt;&lt;br /&gt;&lt;a href="http://help.sap.com/saphelp_46c/helpdata/en/be/3fe63659241157e10000009b38f889/frameset.htm"&gt;GOS - SAP Help&lt;/a&gt;&lt;br /&gt;&lt;a href="https://www.sdn.sap.com/irj/sdn/go/portal/prtroot/docs/library/uuid/0e6b0d95-0a01-0010-4696-ca0a48de5fb3"&gt;How to Attach Documents to Any Custom Program Using GOS (SDN)&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-143490954071433762?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/143490954071433762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=143490954071433762' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/143490954071433762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/143490954071433762'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2008/07/oh-my-gos.html' title='Oh my GOS!'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_Eh4NJZiXByQ/SG-Or4D3TvI/AAAAAAAAAPg/IJUPqYKCV74/s72-c/GOS_1.bmp' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-117347206022628456</id><published>2007-03-09T15:23:00.000-05:00</published><updated>2007-03-09T15:27:40.236-05:00</updated><title type='text'>Execute in Background – your way</title><content type='html'>Isn’t it nice of SAP to provide us with the option to execute the programs in background? Just go to the menu Program -&gt; Execute in Background or, even easier, press [F9] button and – voila! – the program starts to run while you can get back to finding the next mine in the Minesweeper or something of not lesser importance. But, as Adrian Monk says, here is the thing: the users just can’t keep their paws off the Execute button and cannot be relied upon to execute the program only in the background.&lt;br /&gt;&lt;br /&gt;My recent task was to create a program that would run in the background but could be easily initiated by an average Joe User. One of the options would be to create an event-triggered background job and a transaction with the sole purpose of triggering the event. But this looked like too much maintenance to me: I would have to set up the job, create the event and, on top of that, make sure it’s all done in at least 3 environments for testing. Being a very lazy person (which, I think, is a positive quality for a programmer) I decided to find an easier option.&lt;br /&gt;&lt;br /&gt;After just a couple of hours of intensive search I stumbled upon a nice function module SUBST_START_REPORT_IN_BATCH. Turns out this beauty is easy to use and is pretty much foolproof. Here is the code that I used (it worked right from the first try, so there is no long story to tell today) to display the message to the user and run the program as a background job:&lt;br /&gt;&lt;blockquote&gt;PARAMETERS: p_vkorg TYPE tvko-vkorg OBLIGATORY MEMORY ID vko.&lt;br /&gt;&lt;br /&gt;DATA: rspar TYPE TABLE OF rsparams,&lt;br /&gt;      wa_rspar LIKE LINE OF rspar.&lt;br /&gt;&lt;br /&gt;START-OF-SELECTION.&lt;br /&gt;&lt;br /&gt;  IF sy-batch IS INITIAL.&lt;br /&gt;&lt;br /&gt;    wa_rspar-selname = 'P_VKORG'.&lt;br /&gt;    wa_rspar-kind = 'P'.&lt;br /&gt;    wa_rspar-low  = p_vkorg.&lt;br /&gt;    APPEND wa_rspar TO rspar.&lt;br /&gt;&lt;br /&gt;    CALL FUNCTION 'SUBST_START_REPORT_IN_BATCH'&lt;br /&gt;      EXPORTING&lt;br /&gt;        iv_repname                    = sy-repid&lt;br /&gt;      TABLES&lt;br /&gt;        tt_reportparam                = rspar&lt;br /&gt;      EXCEPTIONS&lt;br /&gt;        variant_exist_check_failed    = 1&lt;br /&gt;        variant_update_failed         = 2&lt;br /&gt;        variant_update_not_authorized = 3&lt;br /&gt;        variant_update_no_report      = 4&lt;br /&gt;        variant_update_no_variant     = 5&lt;br /&gt;        variant_update_variant_locked = 6&lt;br /&gt;        variant_insert_failed         = 7&lt;br /&gt;        variant_insert_not_authorized = 8&lt;br /&gt;        variant_insert_no_report      = 9&lt;br /&gt;        variant_insert_variant_exists = 10&lt;br /&gt;        variant_insert_variant_locked = 11&lt;br /&gt;        variant_write_failed          = 12&lt;br /&gt;        no_batch_service              = 13&lt;br /&gt;        no_server_list                = 14&lt;br /&gt;        batch_scheduling_failed       = 15&lt;br /&gt;        OTHERS                        = 16.&lt;br /&gt;&lt;br /&gt;    IF sy-subrc = 0.&lt;br /&gt;      MESSAGE 'Background job has been initiated successfully'&lt;br /&gt;          TYPE 'I'.&lt;br /&gt;    ELSE.&lt;br /&gt;      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno&lt;br /&gt;              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.&lt;br /&gt;    ENDIF.&lt;br /&gt;&lt;br /&gt;  ELSE.&lt;br /&gt;&lt;br /&gt;* place main program code here&lt;br /&gt;&lt;br /&gt;  ENDIF.&lt;/blockquote&gt;So what happens here – the user goes into transaction (I just created a Z... transaction for my report), hits Execute and the program goes into sy-batch IS INITIAL part. There it schedules itself in the background (I made the job name the same as the ABAP report name but it could be different), displays a message and stops. But when the background instance runs it goes into the ELSE condition where all the main code is executed.&lt;br /&gt;&lt;br /&gt;RSPAR parameter must be filled in with the report parameters (there are also some more parameters for the variant activities). Unfortunately it is not described in the FM documentation how exactly it should be filled in, but you can find all the details in the &lt;a href="http://help.sap.com/saphelp_nw04/helpdata/en/9f/dba51a35c111d1829f0000e829fbfe/content.htm"&gt;SAP Help&lt;/a&gt; on this subject.&lt;br /&gt;&lt;br /&gt;If you look into the function module’s code you’ll see that it is basically calling the FMs JOB_OPEN and JOB_CLOSE. Of course I could’ve also used them directly but I think that SUBST_START_REPORT_IN_BATCH FM is so much more convenient. While we are on it, here is a code fragment where I use the JOB_... function modules to schedule the output processing program RSNAST00 to run in the background for the specific documents (&lt;em&gt;i_objky&lt;/em&gt; is filled elsewhere in the code):&lt;br /&gt;&lt;blockquote&gt;PARAMETERS: p_vkorg TYPE tvko-vkorg OBLIGATORY MEMORY ID vko.&lt;br /&gt;&lt;br /&gt;RANGES i_objky FOR nast-objky.&lt;br /&gt;&lt;br /&gt;DATA:  jobname          TYPE tbtcjob-jobname,&lt;br /&gt;       jobcount         TYPE tbtcjob-jobcount.&lt;br /&gt;&lt;br /&gt;CONCATENATE p_vkorg '_PRINT_INVOICES' INTO jobname.&lt;br /&gt;CLEAR jobcount.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'JOB_OPEN'&lt;br /&gt;  EXPORTING&lt;br /&gt;    jobname  = jobname&lt;br /&gt;  IMPORTING&lt;br /&gt;    jobcount = jobcount.&lt;br /&gt;&lt;br /&gt;IF sy-subrc &lt;&gt; 0.&lt;br /&gt;  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno&lt;br /&gt;          WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.&lt;br /&gt;ENDIF.&lt;br /&gt;&lt;br /&gt;SUBMIT rsnast00&lt;br /&gt;        WITH p_again  = space&lt;br /&gt;        WITH p_print  = space&lt;br /&gt;        WITH p_sort   = 10&lt;br /&gt;        WITH p_suff2  = space&lt;br /&gt;        WITH s_kappl  = 'V3'&lt;br /&gt;        WITH s_nacha  = '1'&lt;br /&gt;        WITH s_objky  IN i_objky&lt;br /&gt;        VIA JOB jobname&lt;br /&gt;        NUMBER jobcount&lt;br /&gt; AND RETURN.&lt;br /&gt;&lt;br /&gt;* Close the job and run immediately&lt;br /&gt;CALL FUNCTION 'JOB_CLOSE'&lt;br /&gt;  EXPORTING&lt;br /&gt;    jobcount  = jobcount&lt;br /&gt;    jobname   = jobname&lt;br /&gt;    strtimmed = 'X'.            " Start immediately&lt;br /&gt;&lt;br /&gt;IF sy-subrc &lt;&gt; 0.&lt;br /&gt;  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno&lt;br /&gt;          WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;The key to success here is to keep the correct order (JOB_OPEN, SUBMIT... or JOB_SUBMIT, JOB_CLOSE) and not to forget jobcount / jobname everywhere. And in case you were wondering – the info on the background jobs is stored in the table &lt;strong&gt;TBTCO&lt;/strong&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-117347206022628456?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/117347206022628456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=117347206022628456' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/117347206022628456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/117347206022628456'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2007/03/execute-in-background-your-way.html' title='Execute in Background – your way'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-117211293619270363</id><published>2007-02-21T21:50:00.000-05:00</published><updated>2007-02-21T21:55:36.210-05:00</updated><title type='text'>SELECTed experiments</title><content type='html'>I have to apologize again for not writing for quite a while.  This time my work on the post was interrupted by, uhm, actual work. :) With the go-live date rapidly approaching there is, unfortunately, no time for my little experiments. &lt;br /&gt;&lt;br /&gt;Sooner or later we all have to face the performance issues in the ABAP programs. And so we learn, sometimes the hard way, to write the SELECT statements that use the database index, to avoid SELECTs in the loops and to avoid the temptation of SELECT... ENDSELECT, which is for some reason still taught in the BC400 class. But sometimes all the ‘Performance 101’ stuff is already in place, but we still have to scramble for more ways to make the program run faster. Since this is exactly the point where we are in the project right now, I am on a quest to find more techniques to speed up the data selection.&lt;br /&gt;&lt;br /&gt;First thing I’ve always been wondering about is whether it would make any difference if I write the JOIN condition in a different way. Would it matter if I replace ‘A join B’ with ‘B join A’, for example? &lt;br /&gt; &lt;br /&gt;Here is a simple code that I’ve tested:&lt;br /&gt;&lt;blockquote&gt;DATA: BEGIN OF i_materials OCCURS 0,&lt;br /&gt;        matnr TYPE vbap-matnr,&lt;br /&gt;      END OF i_materials.&lt;br /&gt;&lt;br /&gt;SELECT vbap~matnr&lt;br /&gt;FROM vbap JOIN vbup ON vbap~vbeln = vbup~vbeln AND&lt;br /&gt;                       vbap~posnr = vbup~posnr&lt;br /&gt;INTO TABLE i_materials&lt;br /&gt;WHERE ( vbup~lfsta = 'A' OR &lt;br /&gt;        vbup~lfsta = 'B' )&lt;br /&gt;AND vbap~werks = '2010'.&lt;/blockquote&gt;In the FROM... JOIN... part I’ve tried all possible combinations of VBAP and VBUP being in different order, like this, for example:&lt;br /&gt;&lt;blockquote&gt;FROM vbup JOIN vbap ON vbup~vbeln = vbap~vbeln AND&lt;br /&gt;                       vbup~posnr = vbap~posnr&lt;/blockquote&gt;None of the combinations performed better than others. However, when I switched VBELN and POSNR (VBELN is the first key, POSNR is second):&lt;br /&gt;&lt;blockquote&gt;FROM vbup JOIN vbap ON vbap~posnr = vbup~posnr&lt;br /&gt;AND vbap~vbeln = vbup~vbeln&lt;/blockquote&gt;&lt;br /&gt;performance dropped by 4.5-5%, which kind of makes sense.&lt;br /&gt;&lt;br /&gt;Another thing that SAP Help warns about is to avoid selecting too much data. The most outrageous case is, of course, when someone does SELECT * on a very wide table just to get 2 fields. However, I couldn’t find any statistics on how exactly selecting more data would affect the performance. Here is the same example with some modifications:&lt;br /&gt;&lt;blockquote&gt;DATA: BEGIN OF i_materials OCCURS 0,&lt;br /&gt;        matnr TYPE vbap-matnr,&lt;br /&gt;        vbeln type vbeln,&lt;br /&gt;        matkl type vbap-matkl,&lt;br /&gt;      END OF i_materials.&lt;br /&gt;&lt;br /&gt;SELECT vbap~matnr vbap~vbeln vbap~matkl&lt;br /&gt;FROM vbap JOIN vbup ON vbap~vbeln = vbup~vbeln AND&lt;br /&gt;                       vbap~posnr = vbup~posnr&lt;br /&gt;INTO TABLE i_materials&lt;br /&gt;WHERE ( vbup~besta = 'A' OR &lt;br /&gt;        vbup~besta = 'B' ).&lt;/blockquote&gt;The result: this runs about 5-10% slower than when only MATNR is selected. This number, of course, depends on the number of records and other factors, but it would make me think twice about bringing more data from the database than I really need.&lt;br /&gt;&lt;br /&gt;My third exercise was to find out the difference between using RANGES and OR conditions in the WHERE clause. Here is the sample code with RANGES:&lt;br /&gt;&lt;blockquote&gt;RANGES r_auart FOR vbak-auart.&lt;br /&gt;DATA: w_lines TYPE i,&lt;br /&gt;      w_runtime TYPE i.&lt;br /&gt;&lt;br /&gt;GET RUN TIME FIELD w_runtime.&lt;br /&gt;&lt;br /&gt;r_auart-sign = 'I'.&lt;br /&gt;r_auart-option = 'EQ'.&lt;br /&gt;r_auart-low = 'ZCA'.&lt;br /&gt;APPEND r_auart.&lt;br /&gt;r_auart-low = 'ZOR'.&lt;br /&gt;APPEND r_auart.&lt;br /&gt;&lt;br /&gt;DATA: BEGIN OF i_vbak OCCURS 0,&lt;br /&gt;        vbak-vbeln TYPE vbeln,&lt;br /&gt;      END OF i_vbak.&lt;br /&gt;&lt;br /&gt;SELECT vbeln&lt;br /&gt;FROM vbak&lt;br /&gt;INTO TABLE i_vbak&lt;br /&gt;WHERE auart IN r_auart.&lt;br /&gt;&lt;br /&gt;GET RUN TIME FIELD w_runtime.&lt;br /&gt;&lt;br /&gt;w_lines = LINES( i_vbak ).&lt;br /&gt;&lt;br /&gt;WRITE: 'Records found:' , w_lines , 'Runtime: ' , w_runtime.&lt;/blockquote&gt;And modified with OR:&lt;br /&gt;&lt;blockquote&gt;SELECT vbeln&lt;br /&gt;FROM vbak&lt;br /&gt;INTO TABLE i_vbak&lt;br /&gt;WHERE ( auart = 'ZCA' OR auart = 'ZOR' ).&lt;/blockquote&gt;&lt;br /&gt;When retrieving about 16,000 records the runtime of the first code (with RANGE) fluctuated between 45 and 46K ms but second code (with OR) - between 44 and 46K ms. Since difference is so insignificant, I’d probably choose between the RANGE or OR based solely on the readability and functional requirements.&lt;br /&gt;&lt;br /&gt;The last exercise was to prove my theory that SELECT-OPTIONS should be replaced by the PARAMETER when all the bells and whistles like ranges, etc. are not really necessary. Here is the code example:&lt;br /&gt;&lt;blockquote&gt;TABLES: vbak.&lt;br /&gt;SELECT-OPTIONS s_vkorg FOR vbak-vkorg.&lt;br /&gt;&lt;br /&gt;DATA: BEGIN OF i_vbak OCCURS 0,&lt;br /&gt;        vbak-vbeln TYPE vbeln,&lt;br /&gt;      END OF i_vbak.&lt;br /&gt;&lt;br /&gt;SELECT vbeln&lt;br /&gt;FROM vbak&lt;br /&gt;INTO TABLE i_vbak&lt;br /&gt;WHERE vkorg IN s_vkorg&lt;br /&gt;AND ( auart = 'ZCA' OR auart = 'ZOR' ).&lt;/blockquote&gt;When SELECT-OPTIONS is replaced by the single parameter P_VKORG and WHERE clause by “WHERE VKORG = P_VKORG”, the code runs about 10% faster.&lt;br /&gt;&lt;br /&gt;The examples above are pretty generic and maybe even lame and, of course, my conclusions can not be considered the ultimate truth. It always makes more sense to check the performance of the specific program in the specific environment than rely on somebody else’s (even mine :) ) assurances.&lt;br /&gt;&lt;br /&gt;Which brings us to another subject: how exactly do we track the program’s performance? There are some web pages that I’d like to suggest.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.thespot4sap.com/Articles/SAPABAPPerformanceTuning_Introduction.asp"&gt;ABAP Performance Tuning Tips &amp; Tricks&lt;/a&gt; – this is pure “Performance 101”, a good first stop for a beginner. There is also a fantastic weblog on SDN  - &lt;a href="https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/wlg/3518"&gt;The journey to tuning NetWeaver components&lt;/a&gt;. Unfortunately, it seems that the journey stopped with the part 1, but it is still very informative and to the point. &lt;br /&gt;&lt;br /&gt;If you are experimenting with small pieces of code, like me, there is one trick. In the ABAP editor, go to the menu Environment -&gt; Examples -&gt; Performance examples. (There are some very nice examples, by the way.) Then click on any example, which should open two windows with the code. To measure the runtime of your own code, just copy it over SAP’s code and click ‘Measure runtime’ button. Your code must have correct syntax and also this can be done only on the modifiable client (i.e. most likely you won’t be able to do it in production).&lt;br /&gt;&lt;br /&gt;The most suggested things for the performance trace are the Runtime Analysis (menu System -&gt; Utilities) and the SQL Trace (transaction ST05). Somehow I’ve never warmed up to the Runtime Analysis (long story), but ST05 is very useful, especially since most of the performance issues have something to do with the database access. ST05 is described quite well in the SAP Press book &lt;a href="http://www.sap-press.com/product.cfm?account=&amp;product=H951"&gt;SAP Performance Optimization Guide&lt;/a&gt;. It’s quite expensive, not that fantastic and is geared more towards the Basis guys, so I wouldn’t really suggest buying it. But if you get a chance to borrow it from your local library or your local Basis guy, parts of it are definitely worth reading.&lt;br /&gt;&lt;br /&gt;Final word of caution: always take more than one measurement! Most likely the first number that you'll get will be way off; you need at least 3 runs to get somewhat realistic results. For this post I've measured the examples at least 5 times.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-117211293619270363?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/117211293619270363/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=117211293619270363' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/117211293619270363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/117211293619270363'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2007/02/selected-experiments.html' title='SELECTed experiments'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116862952535721818</id><published>2007-01-12T14:07:00.000-05:00</published><updated>2008-02-11T10:47:01.523-05:00</updated><title type='text'>There is something about printer administration</title><content type='html'>Lately I had to spend several days trying to make an old matrix printer to print a SAPscript form on the continuous paper. Usually the Basis guys are the ones taking care of the printer setup, but where I work it’s like this: you get an assignment to work on a form - you have to deal with the printer setup. Or with the Basis guys. I personally prefer to deal with the printer directly. :) &lt;br /&gt;&lt;br /&gt;It is kind of outside of the ABAP scope but, having spent so much time on it, I might as well share the experience (and knowledge) with you. So let’s walk through the whole process, starting with adding a custom paper size and ending with the printer configuration to print on that paper. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/733791/11_01_SPAD.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/320/24614/11_01_SPAD.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Printer administration happens in the transaction SPAD. To add a new paper format you need to have access to the extended administration. SPAD might be confusing at times because instead of giving a “no authorization” message it just hides stuff from you. So if you don’t see any of the buttons or menu options mentioned here, it’s most likely a security issue. Check with your security administrator if you can get full access in the test system or, well, just watch.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/93295/11_02_Page_Formats.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/320/13037/11_02_Page_Formats.jpg" border="0" alt="" /&gt;&lt;/a&gt;First step would be to create a new Page Format. In SPAD, go to [Extended Admin.], Device Types tab and click on the [Page Formats] button. (You can also get almost anywhere in SPAD by drilling down, i.e. double-clicking, from the printer list but I’ll be giving the direct access path here for demonstration purposes.) After that you should see a list of the page formats available in the system. To create a new format or copy an existing one you’ll need to click the Change button (a pen) first, then your list will look like on the screenshot here. To copy an existing format, single-click on it and click the button 'Create using template' (F5). (For some reason this button is used in SPAD where really a 'Copy As...' button should be. Probably it’s just to keep the Basis guys alert.) The Page Format only holds the paper size and orientation (Portrait or Landscape). This format is then used in SAPscript or Smartform only to let SAP know how much space to allow on the form. As I’ve learned, for the matrix printer this size doesn’t mean a diddly squad.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/637956/11_03_Format_Type.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/320/423533/11_03_Format_Type.jpg" border="0" alt="" /&gt;&lt;/a&gt;After the new page format has been saved (name should start with Z, as usual, most likely you’ll be prompted for a transport number), the next step is to define the Format Type. Technology is very much the same – click on [Format Type] button, then on the 'pen' button to create or copy an existing one. At first this Format Type thing seems kind of redundant, but its purpose will become clear later. So, for the Format Type basically just pick a Page Format, orientation (again, for some reason) and add some meaningful description. If defining the format for SAPscript or Smartform, pick ‘Format type for SAPscript’ in the Type field. Save your creation.&lt;br /&gt;&lt;br /&gt;The next step is to either create a new Device Type or to assign your Format Type (not to be confused with the Page Format!) to an existing device type. Just for demonstration I'm going to create a new device type and a new printer in SAP, but let me take a moment to explain this concept. The actual printer, that sits somewhere in the office, is called &lt;em&gt;the output device&lt;/em&gt; and can be maintained on the Devices/Servers tab ([Output devices] button, naturally). Each printer, AKA output device, must be assigned some device type. For me it’s easier to think of the device type as of the printer’s model. Let’s say you have two identical laser printers sitting in two different offices. Since they are the same model and use the same driver, you would need to create one device type (or use an existing one, there are quite a few provided in SAP) and two output devices with this device type.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/970378/11_04_Device_Type.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/320/224113/11_04_Device_Type.jpg" border="0" alt="" /&gt;&lt;/a&gt;So, back to SPAD. Still in the Extended Admin area (Device Types tab), click on the [Device Types] button. There are many standard device types available, so usually you would only need to copy an existing type at most. If none of them fits, better leave it to the Basis guys to set up the printer. For my task I had to copy a standard ASCIIPRI device type (generic ASCII printer). On the Attributes tab you can select the printer driver and the character set (in case you need to print in different languages). Print Controls tab (as seen on the screenshot) is a bit more interesting. Print Controls allow sending some special codes to the printer to, well, control how to print stuff. Each print control (they have 5-character names. e.g. SC120) deals with one printing feature. For example, SPAGE contains a special code for the page break, SLINE – for the line break. If these codes are deleted (I tried!) the printer starts printing everything on one big line. SPAGE and SLINE controls came with the standard ASCIIPRI device type, but I had to add some code for SC120, otherwise the printer did not print the 12 pt font correctly. I honestly don’t know what does this code mean, I just copied it from the Epson printer settings and it somehow worked. If you are curious the description of all print controls can be found &lt;a href="http://help.sap.com/saphelp_nw04s/helpdata/en/b1/fd1042ea8711d18e310000e83dd9fc/content.htm"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/339892/11_05_Linking.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/200/673235/11_05_Linking.jpg" border="0" alt="" /&gt;&lt;/a&gt;OK, now is the time to finally link this new device type with all the page formatting that we did on the first two steps. To do so, on the same screen click the [Formats] button (the one with the blue thingy) or F6. This will bring up a list of the format types that are assigned to this device type. If you copied an existing device type you should see a bunch of standard formats there, like DINA4 or LETTER. Now this is again counter-intuitive but there is no button like ‘assign format type’, you need to use the ‘Create’ (blank page) button instead. When you click ‘Create’ button it opens a small window with the only field Format in it. Type in or select your format type (ZINCH8.5_INVOICE in this example) and hit [Enter]. This will open the screen ‘Maintain format ... for device type ...’. Most likely you won’t need to touch the Attributes tab, but the Formats tab (note how they use the word “format” in like third different context here) is quite interesting. Here you can enter the whole mini-program to send to the printer for different actions, e.g. ‘initialization’ or ‘end of page’. If an action has a green light next to it, it means that there is something programmed (although it might be just a comment line).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/91653/11_06_Format_Linked.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/200/210232/11_06_Format_Linked.jpg" border="0" alt="" /&gt;&lt;/a&gt;Programming here happens not in ABAP (unfortunately) but in a language that printer can understand. SAP suggests that you look into your printer documentation for more information. (Yeah, good luck with that!) Normally you wouldn’t need to do any programming here and/or should be able to use whatever comes with the standard formats. Just keep in mind that when you copy a standard format type, those mini-programs are not being copied automatically. To copy them, click the ‘Copy format’ button (F5). You can also just copy/paste the code into actions manually. In my case the standard device type ASCIIPRI came with no “program”, so I copied the following piece of code from the Epson printer:&lt;br /&gt;&lt;blockquote&gt;# reset&lt;br /&gt;\e\0x40&lt;br /&gt;# select US-ASCII character set&lt;br /&gt;# \e\0x52\0x00&lt;br /&gt;# set line spacing 6 LPI&lt;br /&gt;\e\0x32&lt;br /&gt;# set page length (33)&lt;br /&gt;\e\0x43\0x33&lt;br /&gt;# cancel bottom margin&lt;br /&gt;\e\0x4F&lt;br /&gt;# set left margin to 0&lt;br /&gt;\e\0x6C\0x00&lt;/blockquote&gt;&lt;br /&gt;Not sure if all of this was necessary but it worked, so I’m not touching it. The most important thing that I had to change was the page length: &lt;em&gt;\e\0x43\0x33&lt;/em&gt; line. This probably doesn’t matter for the laser printers which print on the single sheets of paper, but it turns out that this is the only place where the actual page length is set for the continuous paper (see OSS note 79288). Number 33 is hexadecimal for page length, which is equal to 51 lines (8.5 inch by 6 lines per inch) in decimal system. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/x/blogger/5103/717/1600/983842/11_08_Last%20step.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/x/blogger/5103/717/200/148795/11_08_Last%20step.jpg" border="0" alt="" /&gt;&lt;/a&gt;Save your new device type with all the bells and whistles. The last step is to create a printer, AKA output device, which is done, as mentioned above, on the Devices/Servers tab. As usual, you can create a brand new device or copy an existing one. This step is actually pretty simple: just pick your new device type from the list, assign a spool server, and enter the printer’s IP address on the ‘Access method’ tab. For the ‘Host Spool Access Method’ I’ve chosen ‘U’ for my test, but you might need to pick another option. It’s always a good idea to check the configuration on an existing printer that works and just copy as much as you can from there. On the Tray tab you can select which page formats are going to be used on each tray, but for the dot matrix printers I suggest to leave this tab blank. &lt;br /&gt;&lt;br /&gt;OK, I think now we can do the Basis support as well! Just kidding... More information on the subject of printing can be found in the &lt;a href="http://help.sap.com/saphelp_nw04s/helpdata/en/d9/4a8eb751ea11d189570000e829fbbd/frameset.htm"&gt;SAP Library&lt;/a&gt;, it’s pretty boring though and explained mostly in the SAP’s favorite stiff manner.&lt;br /&gt;&lt;br /&gt;In conclusion let’s just quickly summarize the basic concepts that we’ve learned today:&lt;br /&gt;- &lt;em&gt;page format&lt;/em&gt; holds the paper size and tells the SAPscript and Smartform how much space to allow;&lt;br /&gt;- &lt;em&gt;format type&lt;/em&gt; provides a longer description of the page format and links it to the device type&lt;br /&gt;- &lt;em&gt;device type&lt;/em&gt; is very much like a printer model, you can send the commands to the printer from here and you can tell which paper size can be used by linking it to the format type&lt;br /&gt;- &lt;em&gt;output device&lt;/em&gt; is a specific printer, which is linked to the device type; this is where you enter the printer’s IP address.&lt;br /&gt;&lt;br /&gt;Happy printing, everyone!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116862952535721818?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116862952535721818/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116862952535721818' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116862952535721818'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116862952535721818'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2007/01/there-is-something-about-printer.html' title='There is something about printer administration'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116614622244384278</id><published>2006-12-14T20:28:00.000-05:00</published><updated>2006-12-14T20:30:22.456-05:00</updated><title type='text'>Dangers of type N</title><content type='html'>Happy Holidays, everyone! First of all, sorry for a long absence, which was due to my [very much needed] vacation.  What could be nicer in December than to leave everyone in the middle of a boring QA testing and go to Florida where it’s sunny and +80 F? ;)&lt;br /&gt;&lt;br /&gt;You would think that I’ve already had enough fun with numbers while working on my “Fun with numbers” post, but turns out there is more fun left to have. Yesterday I was presented with the issue that instead of sending number 5 in the file we are sending 5000. Fortunately this came up in testing, so I had enough time to poke around.&lt;br /&gt;&lt;br /&gt;We have a lot of interfaces and most of them involve IDocs but, since we deal with many other companies and systems, we can not just send them a plain IDoc, so we have to create a file according to the standards set by those companies and systems. Not sure how familiar you guys are with the IDoc concept but, in a nutshell, IDoc is just a weirdly looking set of records in the database, which you can use to either post something in SAP from an external system or send some data out from SAP. All IDocs have a control record, which is sort of a header, and detail records, which are called segments. Segments, in turn, have the fields and the dangerous thing here is that the field names usually correspond with the fields in the major application tables (MARA or VBRK, for example), BUT the IDoc fields always are type CHAR. So when you see the field, let’s say, MENGE in the IDoc you might think that it’s a QUAN field or at least a numeric but, in fact, it’s not (check out the structure E1EDP01, for example).&lt;br /&gt;&lt;br /&gt;By the way, if you’d like to find out more about the IDocs, ALE, EDI and stuff, &lt;a href="http://www.logosworld.com/EDI/HTML/IDocBook.htm"&gt;here&lt;/a&gt; is a good web page to start with. It’s a bit dated but the concepts have not changed since. There is also a comprehensive &lt;a href="http://www.amazon.com/IDoc-Technologies-Prima-Techs-Book/dp/0761534318/sr=1-1/qid=1166113179/ref=sr_1_1/104-1116229-1163911?ie=UTF8&amp;s=books"&gt;book&lt;/a&gt; on the same subject which has the rave reviews on Amazon. In my opinion, the author did a great job exploring the different scenarios and trying to explain how stuff works (or at least supposed to work), but the book is a good reference if you know what you are looking for. If you haven’t had any experience with IDocs and ALE this book will most likely just add to the confusion. Also it goes waaaaay too deep into details – most of the time I don’t even want to know how it works and why. I don’t have time, users are freaking out, somebody please tell me what to do to make it work!!! But it’s the best book on IDocs so far.&lt;br /&gt;&lt;br /&gt;Anyways, back to the subject. So I looked at the IDoc and, sure thing, the MENGE field contained ‘5.000’. In my program I have to convert this field into a field of type N 7 (it mist always be the whole number) for the file. And what do you know – turns out that ‘5.000’ is converted not into ‘0000005’, as I hoped, but into ‘0005000’. Honestly, those character fields and the fact that type N is actually a character type, which can only contain numbers, are driving me nuts. I think that SAP could have done a bit better than this. But, since we have to live with it, I had to find out once and for all how this works and, most importantly, how to solve this problem.&lt;br /&gt;&lt;br /&gt;I put together a tiny test program to see how different strings would convert into the fields of type N and type I (since I is the real numeric type):&lt;br /&gt;&lt;blockquote&gt;DATA: qty_num   TYPE n LENGTH 10,&lt;br /&gt;      qty_i     TYPE i.&lt;br /&gt;&lt;br /&gt;PARAMETERS: p_char TYPE c LENGTH 10.&lt;br /&gt;&lt;br /&gt;qty_num = p_char.&lt;br /&gt;WRITE: / 'Type N = ', qty_num.&lt;br /&gt;&lt;br /&gt;qty_i = p_char.&lt;br /&gt;WRITE: / 'Type I = ', qty_i.&lt;/blockquote&gt;&lt;br /&gt;Here are the test results: ‘5,400.123’ converts into 5400 (type I) and ‘0005400123’ (type N). Explanation lies in the depths of SAP Library. If you go to the ABAP help, then drill down to Basic Statements -&gt; Process Data -&gt; Type Conversions, there will be a link to the Type Conversions topic in SAP Library (for some reason this link is missing in the &lt;a href="http://help.sap.com/saphelp_nw04/helpdata/en/fc/eb3427358411d1829f0000e829fbfe/content.htm"&gt;online version&lt;/a&gt;). From there click the hyperlink ‘Conversion Rules for Elementary Data Types’, then in the first table under N it reads:&lt;br /&gt;&lt;blockquote&gt;Only the digits in the source field are copied. The field is right-justified and filled with trailing zeros.&lt;/blockquote&gt;&lt;br /&gt;So basically SAP takes all the digits from the CHAR field and puts them into N field, then adds leading zeros and – voila! – you get 5000 instead of 5. Since conversion from CHAR to I is working fine, obvious solution would be to define a temporary variable of type I and convert it there and then convert I to type N. But I found that a primitive multiplication by 1 does the trick as well: &lt;br /&gt;&lt;blockquote&gt;qty_num = p_char * 1.&lt;/blockquote&gt;&lt;br /&gt;While I was on it, I’ve also tested how would rounding work: for example, ‘5.4’ (CHAR) will convert into 5 (I) but ‘5.5’ will convert into 6 (I). When multiplied by 1, conversion from CHAR to N works exactly like conversion from CHAR to I.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116614622244384278?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116614622244384278/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116614622244384278' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116614622244384278'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116614622244384278'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/12/dangers-of-type-n.html' title='Dangers of type N'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116364506273362826</id><published>2006-11-15T21:42:00.000-05:00</published><updated>2006-11-15T21:44:22.743-05:00</updated><title type='text'>Call View Maintenance to the rescue!</title><content type='html'>My latest project is a program that should verify whether there are any sales orders that are due for delivery but do not meet certain minimum criteria. Naturally, the minimum requirements have to be stored in some Z table, so that we don’t have to change the program should the requirements change. A functional consultant wrote the specification for me, which included development of a custom transaction to maintain that Z table. Even though it seemed quite complicated at first, after talking to the sales folks I figured that they basically wanted to maintain either a minimum amount (in the US dollars) or a minimum quantity (in the sales units) by material group (VBAP-MATKL). They did not need any fancy screens, so a generated maintenance screen would do. The only challenge was to ensure that th users have access only to the minimum requirements for their sales organization.&lt;br /&gt;&lt;br /&gt;After some unsuccessful online search, I resorted to debugging the SM30 transaction. This lead me to the function module VIEW_MAINTENANCE_CALL. (You might be laughing already, but only now I’ve noticed that SM30 is &lt;strong&gt;actually called &lt;/strong&gt;“Call View Maintenance”. Oh well...) So I checked out that function module and saw the table dba_sellist in the parameter list – jackpot! Just fill in VIEWFIELD, OPERATOR and VALUE and it will bring up the maintenance screen with only the selected values.&lt;br /&gt;&lt;br /&gt;The very cool thing is that it also restricts the maintenance to those values. E.g. if the user has selected sales org Z1 but then tries to add a record with Z2, it will not allow it (although the error message is kind of generic). Hence all that left for me to do was to write a very short report with just a selection screen and the FM call.&lt;br /&gt;&lt;br /&gt;My Z table has the following key fields:&lt;br /&gt;&lt;blockquote&gt;MANDT&lt;br /&gt;VKORG&lt;br /&gt;MATKL&lt;br /&gt;ZUNIT&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;ZUNIT is a CHAR field with the domain that limits the values to USD and our sales units. USD can be used to enter the minimum dollar amount and sales units can be used to enter the minimum quantity. One unfortunate thing that I found out about such domains is that SAP is adding a blank value to the list of the allowed values. This seems to be the standard functionality; I found no way to get rid of it in the Dictionary except for replacing single values with another table with a foreign key, which is a hassle (check out this &lt;a href="https://www.sdn.sap.com/irj/sdn/thread?threadID=145522"&gt;thread&lt;/a&gt; in the SDN forum). It’s not a big issue in this case, because I can just disallow blank units through my ABAP code, but it would be a major annoyance should anyone decide to use just SM30. Long story short, here is my report:&lt;br /&gt;&lt;blockquote&gt;REPORT  zsd_r_min_order_maintain.&lt;br /&gt;&lt;br /&gt;DATA: action.&lt;br /&gt;DATA: BEGIN OF i_sellist OCCURS 0.&lt;br /&gt;        INCLUDE STRUCTURE vimsellist.&lt;br /&gt;DATA: END OF i_sellist.&lt;br /&gt;&lt;br /&gt;TABLES sscrfields.&lt;br /&gt;&lt;br /&gt;PARAMETERS: p_vkorg TYPE tvko-vkorg OBLIGATORY,&lt;br /&gt;            p_usd   RADIOBUTTON GROUP lim, " Limit by USD&lt;br /&gt;            p_qty   RADIOBUTTON GROUP lim. " Limit by Quantity&lt;br /&gt;&lt;br /&gt;SELECTION-SCREEN SKIP 1.&lt;br /&gt;SELECTION-SCREEN PUSHBUTTON /1(15) disp USER-COMMAND disp.&lt;br /&gt;SELECTION-SCREEN PUSHBUTTON 20(15) maint USER-COMMAND maint.&lt;br /&gt;&lt;br /&gt;INITIALIZATION.&lt;br /&gt;  MOVE 'Display' TO disp.&lt;br /&gt;  MOVE 'Maintain' TO maint.&lt;br /&gt;&lt;br /&gt;AT SELECTION-SCREEN.&lt;br /&gt;* Authority check here&lt;br /&gt;  IF sscrfields-ucomm = 'MAINT'.&lt;br /&gt;    action = 'U'.&lt;br /&gt;  ELSE.&lt;br /&gt;    action = 'S'.&lt;br /&gt;  ENDIF.&lt;br /&gt;&lt;br /&gt;* Selection list&lt;br /&gt;  CLEAR: i_sellist.&lt;br /&gt;  REFRESH: i_sellist.&lt;br /&gt;  i_sellist-viewfield = 'VKORG'.&lt;br /&gt;  i_sellist-operator = 'EQ'.&lt;br /&gt;  i_sellist-value = p_vkorg.&lt;br /&gt;  i_sellist-and_or = 'AND'.&lt;br /&gt;  APPEND i_sellist.&lt;br /&gt;  IF p_usd = space.&lt;br /&gt;    i_sellist-operator = 'NE'.&lt;br /&gt;  ENDIF.&lt;br /&gt;  i_sellist-viewfield = 'ZUNIT'.&lt;br /&gt;  i_sellist-value = 'USD'.&lt;br /&gt;  APPEND i_sellist.&lt;br /&gt;* Disallow blank unit&lt;br /&gt;  i_sellist-viewfield = 'ZUNIT'.&lt;br /&gt;  i_sellist-operator = 'NE'.&lt;br /&gt;  i_sellist-value = space.&lt;br /&gt;  APPEND i_sellist.&lt;br /&gt;&lt;br /&gt;  CALL FUNCTION 'VIEW_MAINTENANCE_CALL'&lt;br /&gt;    EXPORTING&lt;br /&gt;      action                       = action&lt;br /&gt;      view_name                    = 'ZSD_MIN_ORDER'&lt;br /&gt;    TABLES&lt;br /&gt;      dba_sellist                  = i_sellist.&lt;/blockquote&gt;In a nutshell, this program provides a simple selection screen with VKORG, radiobutton for either dollar amount or quantity, and two pushbuttons: Display and Maintain. Then the program fills in the action field and the selection table (i_sellilst) according to the user’s selections. When the button is pushed, the screen goes to the same screen as SM30 (sans the first selection screen, of course) and brings up the values that fit the criteria. When the user clicks “back” (green arrow), he/she gets back to my selection screen and can make different selection. &lt;br /&gt;&lt;br /&gt;Like I said, we did not need anything fancy but, as a matter of fact, you can add more screens and built a very nice program that would look quite sophisticated by just correctly using this function module.&lt;br /&gt;&lt;br /&gt;Just one last thing. At first, I forgot to fill in the field I_SELLIST-AND_OR, which resulted in a short dump ‘The WHERE condition has an unexpected format’ (runtime error SAPSQL_WHERE_PARENTHESES, exception CX_SY_DYNAMIC_OSQL_SYNTAX). This exception should have been caught in the function module but I guess they’ve missed it, so keep this in mind.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116364506273362826?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116364506273362826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116364506273362826' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116364506273362826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116364506273362826'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/11/call-view-maintenance-to-rescue.html' title='Call View Maintenance to the rescue!'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116252113129158783</id><published>2006-11-02T21:29:00.000-05:00</published><updated>2006-11-02T21:38:14.796-05:00</updated><title type='text'>Search help</title><content type='html'>I was hoping to get more details on the subject but, unfortunately, had to work on something else. If I get a chance to explore this more, I’ll just comment on this post or write a “part II”, as seems to be very fashionable among the SAP webloggers.&lt;br /&gt;&lt;br /&gt;One of the sites that we support uses two names on the customer master (XD03). Our customers are only other companies and sometimes trade name and legal name are different. For example, a Joe’s Crab Shack restaurant might legally be an ACME Corporation. There are 2 lines for the name in XD03 (KNA1-NAME1 and KNA1-NAME2 respectively) and most of the sites enter the trade name (Joe’s Crab Shack) in NAME1 but legal name in NAME2. On the invoices (i.e. billing documents) the names are also printed in the same order: first NAME1, then NAME2. (This is just a preamble – hold on, we are getting there.) &lt;br /&gt;&lt;br /&gt;But one site, due to the legal requirements in their state, had to print legal name first and trade name second. To avoid changing the invoice form (which is standard for the whole enterprise) we just asked them to enter the names in their order on the Customer Master. Sure thing, pretty soon they started to complain that the customer search in the order entry screen (VA01) only works by NAME1 or by some mysterious “first name”, which we don’t use at all (by the way, when I double-clicked on it, it displayed “Name 2” in the description, but it does not search by NAME2 field).&lt;br /&gt;&lt;br /&gt;Honestly, so far I’ve never had anything to do with the search help but I gave it a try. Quick search in SAP Help (by the way, I usually simply use Google for searching – never really warmed up to that SAP help interface) provided some good starting points. First of all, I discovered that there is an elementary search help and a collective search help (collective is actually just a collection of the elementary ones). Then I learned that both are maintained in SE11. &lt;br /&gt;&lt;br /&gt;Obviously, next I had to find out which search help is VA01 using for the customer (or, as it’s called there ‘Sold-to Partner’ or ‘Ship-to Partner’). Search by description in SE11 was not very successful, so I did a little debugging and finally found the name SD_DEBI in MC_OBJ field in MV45AF0K_KUNDE_GET_F4HELP.&lt;br /&gt; &lt;br /&gt;Then in SE11 I discovered that SD_DEBI is a collective search help, consisting of 4 elementary ones (this might depend on the config, so don’t be surprised if it’s different in your system). Each elementary search help basically translates into a tab on the help screen with the tab names in the ‘Short text’ field. &lt;br /&gt;&lt;br /&gt;Much to my surprise, I was able to create my own elementary search help and simply add it to SD_DEBI without any requests for an access key and, in fact, without much hassle. Here are the steps that I have gone through.&lt;br /&gt;&lt;br /&gt;1. Create an elementary search help (Z_NAME2). This is quite intuitive: in SE11, select ‘Search help’, enter the name and click [Create] button. Then in the pop-up window select ‘Elementary’. I used the other search helps from SD_DEBI to fill in the details:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/08_zname2_1.1.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/08_zname2_1.1.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Did not quite figure the ‘Selection method’ thing. DEBIA, for example, had selection method M_DEBIA with the description ‘Generated View for Matchcode ID DEBI’. Not sure how they generated it but for now I just out the table name (KNA1) in there and it worked. I’ve chosen the ‘Dialog with value restriction’ option so that my new tab behaves the same way as the other ones. If you chose ‘Display values immediately’, it will display the whole list (which doesn’t make any sense if there are hundreds of items). The third option is somewhere in the middle and basically lets the system to choose between the two options based on the number of entries.&lt;br /&gt;&lt;br /&gt;IMP and EXP columns tell what needs to be passed to the search help from the input field (IMP) and back (EXP). If you check IMP then, if a user has entered some value and then clicked on help, that value will be passed to the help screen. If you check EXP, the value will be passed back to the input field. It’s a no-brainer that one field should have EXP checked (I checked both just for the heck of it).&lt;br /&gt;&lt;br /&gt;Numbers in the ‘LPos’ column tell in which order the fields will appear on the screen (I out name first). ‘Hot key’, as I figured from the documentation, seems more trouble than it’s worth, so I left it empty. Don’t forget to activate your new search help before leaving the screen.&lt;br /&gt;&lt;br /&gt;2. Add your new elementary search help to a collective one. Just to be clear: this is not a required step but I had to do it to add my search to SD_DEBI. Again, in SE11, select ‘Search help’, enter the name and click [Change] button. Then on the ‘Included search helps’ tab add your new help, like this:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/08_sd_debi.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/08_sd_debi.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;The last step is to select the line with your search help and to click on the ‘Parameter assignment’. There was an option to automatically propose the parameter, which I used. This parameter basically tells how the collective and elementary searches are linked together (in my case – by customer number KUNNR). And don’t forget to activate the collective search as well.&lt;br /&gt;&lt;br /&gt;After these steps I was able to see the new tab on the customer search help in VA01:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/08_search.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/08_search.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;The users, as usually is the case, were happy with my new screen only for the first hour or so and pretty soon came up with the new requirements. Now they wanted to add the Sales Organization (VKORG) field, so that the search is limited to only their customers. This meant that I could not use KNA1 as a search method because it does not include (VKORG). Luckily, there was a view already defined on KNA1 and KNVV (KNA1VV), which I was able to use. So I replaced KNA1 with KNA1VV, added VKORG to the field list and clicked ‘Activate’. &lt;br /&gt;&lt;br /&gt;This time the activation did not go as smoothly and I got a warning message ‘Dialog type C makes no sense for search help w/o selection popup window’. Not sure why it was qualified as a warning because the search help was not working until I took care of this problem. Surprisingly, the diagnosis for this error message was very specific and pointed out that all my fields had 0 in the SPos column. Here is the new Z_NAME2:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/08_zname2_2.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/08_zname2_2.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;You might notice that I’ve added the memory ID VKO to the VKORG field so that the users wouldn’t have to type in their sales org all the time. Also when I added VKORG field, it automatically put a check mark in the ‘Modified’ column. I tried to read the documentation for it but the only thing I understood was that I didn’t need that, so I unchecked it.&lt;br /&gt;&lt;br /&gt;I’ve also removed unnecessary IMP checkmark and figured out what’s the deal with LPos and SPos columns. Since the users want to search by Sales Org and Name 2, they don’t really care for the Customer [Number] field on the search help tab. But when they enter, say *Crab*, they want to see the list of possible customers with their names and numbers. I’ve achieved that by leaving SPos blank for KUNNR (it removes it from the tab) but setting LPos = 3 (which puts KUNNR on the list of the customers found by search help). Let’s say we have 2 customers: number 700001 (Joe’s Crab Shack) and number 700004 (Jane’s Crab Cakes). The list for *Crab* (Name 2 is case-sensitive for some reason) will displayed like this:&lt;br /&gt;&lt;br /&gt;Joe’s Crab Shack   700001&lt;br /&gt;Jane’s Crab Cakes  700004&lt;br /&gt;&lt;br /&gt;As you might have noticed, there is also an option to create a search help user exit, but I haven’t gone that far yet. &lt;br /&gt;&lt;br /&gt;Just one more thing. From version 4.6 SAP has introduced a central address area. All addresses have a unique ID and are linked to other master data by the field ADRNR. The main address table is ADRC, which also contains NAME1 and NAME2 fields. Fields in KNA1 have length of 35 while fields in ADRC are 40 characters long. If you need to use the whole 40, go with the ADRC table. DEBIX is the elementary search for KUNNR on ADRC, but, as I mentioned, it does not quite work the way we need.&lt;br /&gt;&lt;br /&gt;Some links that might be useful:&lt;br /&gt;1. &lt;a href="http://help.sap.com/saphelp_nw04s/helpdata/en/3d/e53642e2a3ab04e10000000a1550b0/frameset.htm"&gt;SAP Help article&lt;/a&gt;&lt;br /&gt;2. Some minimalistic &lt;a href="http://www.sapdevelopment.co.uk/dictionary/shelp/shelphome.htm"&gt;info&lt;/a&gt; on search help (good reference though)&lt;br /&gt;3. &lt;a href="http://forums.sdn.sap.com/thread.jspa?threadID=118638"&gt;SDN topic&lt;/a&gt; on the customer search help in VA01&lt;br /&gt;4. Attach a Search Help to the Screen Field (&lt;a href="http://sap-img.com/abap/attach-a-search-help-to-the-screen-field.htm"&gt;ABAP code&lt;/a&gt;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116252113129158783?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116252113129158783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116252113129158783' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116252113129158783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116252113129158783'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/11/search-help.html' title='Search help'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116174050010104340</id><published>2006-10-24T21:38:00.000-04:00</published><updated>2006-10-25T20:46:14.383-04:00</updated><title type='text'>Peeking inside a variant</title><content type='html'>Recently I had to work on a program, which should run, among other ABAP programs, in a background job. The challenge was that this program was supposed to figure out which dates were used by the program that is a preceding step in the same job. Since the programs run using the same variant name (content might be changed by the users but the name stays the same), the task here really is to figure out what’s inside the variant.&lt;br /&gt;&lt;br /&gt;It wasn’t quite difficult to find a function module that reads the variant - RS_VARIANT_CONTENTS (I found it on the always useful &lt;a href="http://www.erpgenie.com/abap/functions.htm"&gt;list&lt;/a&gt; on the ERPGenie website) but here are some more details on how this FM works.&lt;br /&gt;&lt;br /&gt;In the program in question the users can either save the dates “as is” in the variant or use the selection variable, in which case the dates would change for every run. Two internal tables can be received from the FM: L_SELOP and VALUTAB. L_SELOP contains the selections that were saved as variant and VALUTAB contains the values. If the selection variable has been used, VALUTAB contains the actual date values, calculated with that variable, which is very neat.&lt;br /&gt;&lt;br /&gt;Here is the test code that I used. The program name is RVV50R10C (delivery due list) and my variant was named TEST. You can, of course, use this FM with any program and variant.&lt;br /&gt;&lt;blockquote&gt;DATA: t_valutab LIKE rsparams OCCURS 0 WITH HEADER LINE,&lt;br /&gt;      t_selop LIKE vanz OCCURS 0 WITH HEADER LINE.&lt;br /&gt;&lt;br /&gt;CALL FUNCTION 'RS_VARIANT_CONTENTS'&lt;br /&gt;  EXPORTING&lt;br /&gt;    report               = 'RVV50R10C'&lt;br /&gt;    variant              = 'TEST'&lt;br /&gt;  TABLES&lt;br /&gt;    l_selop              = t_selop&lt;br /&gt;    valutab              = t_valutab&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    variant_non_existent = 1&lt;br /&gt;    variant_obsolete     = 2&lt;br /&gt;    OTHERS               = 3.&lt;br /&gt;&lt;br /&gt;IF sy-subrc &lt;&gt; 0.&lt;br /&gt;  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno&lt;br /&gt;          WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;In the TEST variant I’ve chosen the range of dates from [today – 10 days] till today using a selection variable. Here is the content of the t_selop table for the date field that I’m interested in (the table contains all the possible selection fields):&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/07_selection.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/320/07_selection.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;The technical field name (T_SELOP-NAME field) can be found either in the program body (look for SELECTION-OPTIONS or PARAMETER) or (the easy way), start the program and then hit [F1] on the field. In the pop-up window, click on [Technical info] – the field name will be in the 'Screen field':&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/07_field.1.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/320/07_field.1.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;T_SELOP doesn’t say what exactly was in the selection variable (bummer!), but at least we know that a variable was used: see field VNAME. If there is no selection variable, the field VNAME is empty.&lt;br /&gt;&lt;br /&gt;T_valutab is very similar to a regular range table. Here is the content of the t_valutab for the date field:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/07_value.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/320/07_value.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;I ran the program on 10/19, so you can see that it nicely calculated the date 10 days back (LOW field). Of course we could run into a problem if the program 1 starts before midnight but program 2 after midnight. Even though in our case it will not happen due to the business restrictions, there is a possible solution: use an FM to look up the job start time and, if it’s not equal to sy-datum, adjust the dates in t_valutab. Maybe I’ll explore this concept in one of the future posts. Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116174050010104340?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116174050010104340/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116174050010104340' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116174050010104340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116174050010104340'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/10/peeking-inside-variant.html' title='Peeking inside a variant'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116053027545226542</id><published>2006-10-10T21:27:00.000-04:00</published><updated>2006-10-10T21:34:15.823-04:00</updated><title type='text'>The pure and simple truth about BINARY SEARCH</title><content type='html'>&lt;em&gt;&lt;strong&gt;The pure and simple truth is rarely pure and never simple.&lt;/strong&gt; &lt;/em&gt;&lt;br /&gt;&lt;em&gt;Oscar Wilde &lt;/em&gt; &lt;br /&gt;&lt;br /&gt;Last week I got an email from a worried user that some information was missing on a sales report. After few hours of exhaustive debugging with a few time-outs in between, I realized that an obscure READ TABLE... command comes back with SY-SUBRC = 4. It was looking for a combination of material number and customer number in an internal table. Both numbers were right, leading zeroes and all. I had the whole table in front of me in the debugging window and the record, which READ was supposed to find, was indeed present in it. “What do you mean sy-subrc is 4?! Here is that record, right there, you dumbass!”, - almost yelled I at the poor innocent Dell monitor.&lt;br /&gt;&lt;br /&gt;Here I should probably mention that READ TABLE command had BINARY SEARCH addition. As I’ve learned from my very long programming (not ABAP) experience, sometimes if you just make things simpler it might actually solve the problem. So I’ve just commented out the BINARY SEARCH part and ran the program again. Now it worked like a charm. OK, now I had to get to the bottom of this.&lt;br /&gt;&lt;br /&gt;I set up a very simple test program:&lt;br /&gt;&lt;blockquote&gt;DATA: BEGIN OF i_test OCCURS 0,&lt;br /&gt;          key1,&lt;br /&gt;          key2,&lt;br /&gt;          non_key,&lt;br /&gt;      END OF i_test.&lt;br /&gt;&lt;br /&gt;PERFORM populate_table.&lt;br /&gt;&lt;br /&gt;SORT i_test BY key1.&lt;br /&gt;LOOP AT i_test.&lt;br /&gt;  WRITE: / i_test-key1, i_test-key2, i_test-non_key.&lt;br /&gt;ENDLOOP.&lt;br /&gt;&lt;br /&gt;READ TABLE i_test WITH KEY key1 = 'B' BINARY SEARCH.&lt;br /&gt;IF sy-subrc = 0.&lt;br /&gt;  WRITE: / 'Found:'.&lt;br /&gt;  WRITE: / i_test-key1, i_test-key2, i_test-non_key.&lt;br /&gt;ELSE.&lt;br /&gt;  WRITE: / 'Not found'.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;(I’ve omitted populate_table routine because it just populates i_test with some test data). Here is the output from the program:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/06_01.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/06_01.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;OK, so it found the very first record with B. So far so good. I changed the READ line to add the second key:&lt;br /&gt;&lt;blockquote&gt;READ TABLE i_test WITH KEY key1 = 'B' key2 = 'Z' BINARY SEARCH.&lt;/blockquote&gt;It did find B Z record. Alright, let’s make the things a bit more interesting:&lt;br /&gt;&lt;blockquote&gt;READ TABLE i_test WITH KEY key2 = 'Z' key1 = 'B' BINARY SEARCH.&lt;/blockquote&gt;Here comes the ‘Not found’ message! However, after I changed SORT from key1 to key2 it was able to find the B Z record again. Now let’s kick it up a notch. I changed SORT and READ commands as follows:&lt;br /&gt;&lt;blockquote&gt;SORT i_test BY key1 ASCENDING key2 DESCENDING.&lt;br /&gt;...&lt;br /&gt;READ TABLE i_test WITH KEY key1 = 'B' key2 = 'A' BINARY SEARCH&lt;/blockquote&gt;Here is the content of the i_test table after the sorting so that you guys could follow:&lt;br /&gt;&lt;blockquote&gt;A Z C&lt;br /&gt;A Z D&lt;br /&gt;A A A&lt;br /&gt;A A B&lt;br /&gt;B Z E&lt;br /&gt;B B C&lt;br /&gt;B A A&lt;br /&gt;B A B&lt;br /&gt;C C A&lt;br /&gt;Z B B&lt;br /&gt;Z A A&lt;/blockquote&gt;The results were as follows: the test above (B A) came back with ‘Not found’ (this time switching key1 and key2 in READ did not help). The things got even curioser when the A A record was found but Z A was not. &lt;br /&gt;&lt;br /&gt;So what’s the deal with this damn binary search? I really like the simple explanation that one guy gave in an &lt;a href="https://forums.sdn.sap.com/thread.jspa?threadID=231495"&gt;SDN post&lt;/a&gt;: ”Let’s say you have numbers 1..to ..100 in a table and you are searching for 34. It would read the 50th record and if it is say 50 next it would read the 25th record and if it is say 25 it would carry to read the 38th record and so on.” &lt;br /&gt;&lt;br /&gt;Back to my example. There were 11 entries in my test table. The binary search started by splitting the table in half and it got the middle record (B B C). “OK,”  thought the computer. “Since I’m looking for Z and A, let me look at the second part of the list (because Z &gt; B). Oh, now I see C C A, we are getting closer! Let’s look at what’s left after that.” Naturally, at this point the only records to search were only C C A, Z B B and Z A A. So it split the list in half again and got Z B B. “OMG, I went too far! Let me get back real quick. Hmm... I see C C A. C is less than Z, which means that there is no record with Z and A. Oh well... SY-SUBRC = 4. Buhbye!”. &lt;br /&gt;&lt;br /&gt;As I finally found out, the problem with the sales report was that the internal table was first sorted by one field, which would have worked fine with the READ, but then re-sorted by another field somewhere in the middle. It looks like a good idea to sort the table right before the binary search, which I will do in the future.&lt;br /&gt;&lt;br /&gt;Obviously, with BINARY SEARCH what you see is not always what you get. To get the right result, the table must be sorted by the right field and in ascending order. If this is not done properly, sometimes binary search might still work correctly, depending on what data is inside the table. But sometimes you might wish it didn’t work at all because it could make finding an error a major pain in the back.&lt;br /&gt;&lt;br /&gt;While I was on it, I also ran the runtime analysis a few times. With the small amount of data in my test program ordinary READ actually worked even faster than READ ... BINARY SEARCH. However, with thousands of records and about 10 fields (as in my sales report), BINARY SEARCH performs much better. I’m pretty sure that hashed table would be even more efficient (unfortunately, it can not be used in that specific report).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116053027545226542?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116053027545226542/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116053027545226542' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116053027545226542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116053027545226542'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/10/pure-and-simple-truth-about-binary.html' title='The pure and simple truth about BINARY SEARCH'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-116009690831928039</id><published>2006-10-05T21:01:00.000-04:00</published><updated>2006-10-05T21:12:25.513-04:00</updated><title type='text'>Fun with numbers</title><content type='html'>It seems that ABAP is one of the few languages that does not have an operator to validate whether the field is numeric or not. In the system where most of the key fields (VBELN, MATNR, etc.) are CHAR but usually contain only numbers an IS NUMERIC operator would come in handy, don’t you think?&lt;br /&gt;&lt;br /&gt;Oh well, as &lt;a href="http://www.imdb.com/title/tt0119116/"&gt;Mr. Zorg&lt;/a&gt; used to say: "You want something done, do it yourself!". And so I started this quest with a simple task to find a way to check whether the field is a number. I’m not going to lie to you – the idea to define a constant with numbers only and to use IF ... CO... was stolen from one of the SAP programs. Here is my first test program:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;CONSTANTS: numbers(10) VALUE '1234567890'.&lt;br /&gt;DATA: test(10).&lt;br /&gt;&lt;br /&gt;test = '123ABC'.&lt;br /&gt;PERFORM test_check USING test.&lt;br /&gt;&lt;br /&gt;test = '123'.&lt;br /&gt;PERFORM test_check USING test.&lt;br /&gt;&lt;br /&gt;FORM test_check  USING    p_test.&lt;br /&gt;  IF p_test CO numbers.&lt;br /&gt;    WRITE: / p_test , ' contains only numbers'.&lt;br /&gt;  ELSE.&lt;br /&gt;    WRITE: / p_test , ' contains alpha characters'.&lt;br /&gt;  ENDIF.&lt;br /&gt;ENDFORM.                    " test_check&lt;/blockquote&gt;But the result surprised me:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;123ABC      contains alpha characters&lt;br /&gt;123         contains alpha characters&lt;/blockquote&gt;What?! Since when 123 is not a number?! Well, obviously, sometimes &lt;a href="http://www.lynchnet.com/tp/tpcard53.html"&gt;the owls are not what they seem&lt;/a&gt;. Here is what the documentation says:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;CO (Contains Only): &lt;br /&gt;&lt;br /&gt;c1 contains only characters from the string c2. &lt;br /&gt;If c1 or c2 is of type C, the comparison takes into account the full length of the field, including blanks at the end.&lt;/blockquote&gt; Doh! Damn SAP with their blanks... OK, I can work around this. Not sure if there is a better way to do this but I found my own method to pad a number with leading zeroes by using SHIFT and TRANSLATE operators. Here is my test number two (I changed only the test_check routine:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;FORM test_check USING VALUE(p_test).&lt;br /&gt;&lt;br /&gt;  SHIFT p_test RIGHT DELETING TRAILING ' '.&lt;br /&gt;  TRANSLATE p_test USING ' 0'.&lt;br /&gt;&lt;br /&gt;  IF p_test CO numbers.&lt;br /&gt;    WRITE: / test , ' contains only numbers'.&lt;br /&gt;  ELSE.&lt;br /&gt;    WRITE: / test , ' contains alpha characters'.&lt;br /&gt;  ENDIF.&lt;br /&gt;ENDFORM.                    " test_check&lt;/blockquote&gt;The result:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;123ABC      contains alpha characters&lt;br /&gt;123         contains only numbers  &lt;/blockquote&gt;Tadah! Note that if you don’t use FORM ... USING VALUE... then the variable test will be converted to ‘0000000123’. (Boy, I feel so smart right now. :) )&lt;br /&gt;&lt;br /&gt;OK. But this piece of magic has actually very limited application. What if the field contains characters like ‘+’, ‘,’ or ‘.’, which can also be a part of the number? Coincidentally, on one of the SAP forums someone has posted a question how to convert a string (for example '107,400.99') into an integer. Since I was already on this numeric quest, I continued in this new direction. Here is what I came up with:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;DATA: input_string TYPE string,&lt;br /&gt;      output_integer TYPE i.&lt;br /&gt;&lt;br /&gt;input_string = '+107,400.99'.&lt;br /&gt;&lt;br /&gt;CATCH SYSTEM-EXCEPTIONS&lt;br /&gt;             arithmetic_errors = 1&lt;br /&gt;             conversion_errors = 2.&lt;br /&gt;  TRANSLATE input_string USING ', '.&lt;br /&gt;  CONDENSE input_string NO-GAPS.&lt;br /&gt;  output_integer = input_string.&lt;br /&gt;ENDCATCH.&lt;br /&gt;&lt;br /&gt;IF sy-subrc = 0.&lt;br /&gt;  WRITE: output_integer.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Not a number' TYPE 'E'.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;The result: 107,401 (since its type I the decimals have been rounded). This piece of code also works when input_string is type CHAR and output can actually be any numeric type. It works with NUMC, P (with or without DECIMALS) and currency types equally well. Also the input string can have plus and minus sign and it can be upfront or at the end of the number – it will still work. The standard ABAP type conversion will take care of converting decimals and the sign, so we only have to remove the thousand separator (‘,’ in this case). CATCH clause will catch an exception if the string contains any other characters (e.g. letters).&lt;br /&gt;&lt;br /&gt;Of course, there are also some related functional modules available:&lt;br /&gt;&lt;strong&gt;MOVE_CHAR_TO_NUM&lt;/strong&gt; – This is a good all-purpose FM (works with CHAR but not with STRING though). Its major advantage is that the thousands separator is not limited to a comma, like in my example above.&lt;br /&gt;&lt;strong&gt;HRCM_STRING_TO_AMOUNT_CONVERT&lt;/strong&gt; – this FM can be used for the conversion of amounts since it takes the currency into account. For the simple string to number conversion it is a bit too bulky, in my opinion.&lt;br /&gt;&lt;strong&gt;CATS_NUMERIC_INPUT_CHECK&lt;/strong&gt; – this one has a very limited application IMHO. It does not convert character fields to numeric fields. Basically all it does it checks if the field is numeric, removes the thousands separator and, if there is a negative sign, moves it to the end. It could not handle the number '+107,400.99' and threw a "not numeric" exception.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-116009690831928039?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/116009690831928039/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=116009690831928039' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116009690831928039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/116009690831928039'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/10/fun-with-numbers.html' title='Fun with numbers'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-115992981585419351</id><published>2006-10-03T22:40:00.000-04:00</published><updated>2006-10-03T22:43:35.860-04:00</updated><title type='text'>User parameters (PARAMETER ... MEMORY ID)</title><content type='html'>The company that I currently work for has just recently implemented SAP at one of the locations and now we are working on the implementation at another location. We have several custom transactions where users must enter an ID (Sales Organization, Plant, etc.) of their location. Naturally, we started thinking what we could do so that the users wouldn’t need to type those IDs again and again. (In case you are wondering – we were simply too busy to think about it during the first implementation.)&lt;br /&gt;&lt;br /&gt;So last week I went to talk to our security administrator and she told me that she’s been entering all the applicable organizational IDs on the user profiles. There are other transactions for the user profile maintenance (e.g. SU01), which most likely you will not be authorized to use, but she showed me this one, SU3 with the Parameters tab:&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/04_SU3.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/04_SU3.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;At first I thought that we will have to find some function module and get those parameters somehow by user ID. But then I realized that those parameters can actually be used in the MEMORY ID addition to the PARAMETER command. If, for example, a user has the parameter VKO = ZZZ in SU3, then p_vkorg will be populated with ZZZ by default when the user runs the program below. Here I also check if the user has authorization to display the data for this sales organization:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;PARAMETER: p_vkorg TYPE vkorg MEMORY ID VKO.&lt;br /&gt;&lt;br /&gt;  AUTHORITY-CHECK OBJECT 'V_VBRK_VKO'&lt;br /&gt;           ID 'VKORG' FIELD p_vkorg&lt;br /&gt;           ID 'ACTVT' FIELD ‘03’.&lt;br /&gt;&lt;br /&gt;  IF sy-subrc &lt;&gt; 0.&lt;br /&gt;* display error message here.&lt;br /&gt;  ENDIF.&lt;/blockquote&gt;&lt;br /&gt;Here are some other useful memory IDs:&lt;br /&gt;&lt;br /&gt;BUK  - Company code (BUKRS)&lt;br /&gt;EKO  - Purchasing Org (EKORG)&lt;br /&gt;LAG  - Storage Location (LGORT)&lt;br /&gt;LGN  - Warehouse Number (LGNUM)&lt;br /&gt;WRK  - Plant (WERKS)&lt;br /&gt;&lt;br /&gt;The memory IDs are stored in the TPARA table, there are like thousands of them. Not sure if those parameters have any other use and if the custom parameters could be maintained and how... I’ll let you know if and when I find out.&lt;br /&gt;&lt;br /&gt;By the way, while I was playing around with this, I found that WERKS is actually a structure. While it’s OK to define the parameters with TYPE VKORG, it is better not to define them with TYPE WERKS. Use TYPE WERKS_D instead.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-115992981585419351?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/115992981585419351/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=115992981585419351' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115992981585419351'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115992981585419351'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/10/user-parameters-parameter-memory-id.html' title='User parameters (PARAMETER ... MEMORY ID)'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-115983620716127223</id><published>2006-10-02T20:42:00.000-04:00</published><updated>2006-10-02T20:45:43.336-04:00</updated><title type='text'>Trouble with hashed tables</title><content type='html'>Today I learned the hard way that hashed internal tables work not exactly as I was expecting. What we (or at least I) learn from, say, BC400 class or the ABAP reference is that there are standard, sorted and hashed internal tables. Standard tables are kind of all-purpose, the sorted ones are better for LOOP AT ... WHERE and hashed tables are good if you need to do READ TABLE with unique key. And for some reason I was assuming that if I read some data that has duplicates into a hashed table it will be nicely populated and duplicates will simply be skipped. Well, assumption is mother of all screw-ups, as they say. Very true.&lt;br /&gt;&lt;br /&gt;In my defense, I actually went through my BC400 materials and ABAP reference and could not find any clues on this, so here is some info on how this actually works. Let’s say you’re trying to get a list of all the deliveries and material numbers and you want the unique numbers only. Here is a bad idea example:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;TYPES: BEGIN OF deliveries,&lt;br /&gt;         vbeln TYPE vbeln,&lt;br /&gt;         matnr TYPE matnr,&lt;br /&gt;       END OF deliveries.&lt;br /&gt;DATA: i_deliveries TYPE HASHED TABLE OF deliveries&lt;br /&gt;               WITH UNIQUE KEY vbeln matnr.&lt;br /&gt;&lt;br /&gt;SELECT vbeln matnr&lt;br /&gt;  INTO TABLE i_deliveries&lt;br /&gt;  FROM lips.&lt;/blockquote&gt;&lt;br /&gt;This program will end with a dump if there is any VBELN with more than one record with the same MATNR. However, this disaster can be easily avoided by changing SELECT to SELECT DISTINCT. Another option (depending on your task) would be to SELECT into a standard table, then do SORT, DELETE ADJACENT DUPLICATES and copy the content to a hashed table. This seems a bit redundant (most likely SELECT DISTINCT is going to work faster) but might be necessary sometimes, you never know.&lt;br /&gt;&lt;br /&gt;Also be careful when doing, for example,&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;i_deliveries_hashed[] = i_deliveries[].&lt;/blockquote&gt;&lt;br /&gt;(Here i_deliveries is a standard table and i_deliveries_hashed is a hashed table.) If there are records in i_deliveries with duplicates (based on the hashed table key), this will also fall into a short dump. Good old SORT and DELETE ADJACENT DUPLICATES will help here as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-115983620716127223?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/115983620716127223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=115983620716127223' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115983620716127223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115983620716127223'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/10/trouble-with-hashed-tables.html' title='Trouble with hashed tables'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-115958272036299936</id><published>2006-09-29T22:14:00.000-04:00</published><updated>2006-09-29T22:22:04.276-04:00</updated><title type='text'>Can field definitions affect performance?</title><content type='html'>I’ve been always curious if the field definitions (DATA) could affect the performance, especially in the subroutines. In some languages the field definition actually compiles into 2 operations: definition itself and field initialization. It is obvious that in this case the less field definition the better, but I wasn’t completely sure about ABAP.&lt;br /&gt;&lt;br /&gt;Today I had to make a change in the program where about 20 fields were defined inside a routine, which was called in a loop, so I finally decided to find out whether it would make any difference if I moved the field definitions outside of the loop.&lt;br /&gt;&lt;br /&gt;For a clean test, I wrote a simple program:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;DO 100 TIMES.&lt;br /&gt;  PERFORM routine.&lt;br /&gt;ENDDO.&lt;br /&gt;&lt;br /&gt;FORM routine.&lt;br /&gt;&lt;br /&gt;  DATA: w_matnr TYPE matnr,&lt;br /&gt;        w_posnr TYPE posnr.&lt;br /&gt;  WRITE: w_matnr.&lt;br /&gt;&lt;br /&gt;ENDFORM.&lt;/blockquote&gt;&lt;br /&gt;Then I changed it like this:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;DATA: w_matnr TYPE matnr,&lt;br /&gt;      w_posnr TYPE posnr.&lt;br /&gt;&lt;br /&gt;DO 100 TIMES.&lt;br /&gt;  PERFORM routine.&lt;br /&gt;ENDDO.&lt;br /&gt;&lt;br /&gt;FORM routine.&lt;br /&gt;&lt;br /&gt;  WRITE: w_matnr.&lt;br /&gt;&lt;br /&gt;ENDFORM.&lt;/blockquote&gt;&lt;br /&gt;I ran Runtime Analysis on both several times to get more accurate results (that really drives me crazy that you get different results almost every time) and here is the average “before” picture&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/Routine_before.0.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/Routine_before.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;and “after” picture.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://photos1.blogger.com/blogger/5103/717/1600/Routine_after.0.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/5103/717/400/Routine_after.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Well, it’s not huge but now I know that it does make a difference. What’s interesting though – the runtime did not increase much when I changed the DO cycle to 1000 times. Hmm... If anyone can explain this, please let me know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-115958272036299936?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/115958272036299936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=115958272036299936' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115958272036299936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115958272036299936'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/09/can-field-definitions-affect.html' title='Can field definitions affect performance?'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34883048.post-115949247548367333</id><published>2006-09-28T21:13:00.000-04:00</published><updated>2006-09-28T21:17:26.510-04:00</updated><title type='text'>Sending emails from SAP</title><content type='html'>Today I was trying to set up email notification in one of my programs. There was a requirement to send out an email to a designated user if any errors occurred in the program. Fortunately, I already knew which function module to use (SO_NEW_DOCUMENT_SEND_API1), so I thought it would be a piece of cake.&lt;br /&gt;&lt;br /&gt;In the documentation for that function module there is even a piece of code that illustrates how this FM should be used. So I just copy-pasted that code in my temporary program, added my email address and ran it. It did successfully send a message to my SAP Inbox but the email was not sent. After some checking around it turned out that we simply can not send emails from the test client (apparently the Basis guys were too lazy to set it up there). OK, no big deal – I ran my program in the right environment. And – hurrah! – got a message ‘...sent successfully’.&lt;br /&gt;&lt;br /&gt;After 2 cups of coffee I suddenly realized that I should have gotten an email by now. But there was none... Hmm... More googling and more asking around brought the following results: if there was an email waiting to be sent I would have seen it in the transaction SCOT. Also the emails are actually sent out from SAP by the program RSCONN01, which is usually scheduled as a background job to run every N minutes. My program ran successfully and the job was running OK but SCOT was showing only bunch of zeroes. What the heck is going on?! So I went to check my program again, maybe I was missing something. Indeed I was! Turns out that the bast... , I mean nice person who wrote a code example in the FM documentation forgot about a little tiny detail – it needs COMMIT WORK, otherwise nothing will happen! It will give you all the “success” messages and sy-subrc = 0 but good luck waiting for that email to arrive.&lt;br /&gt;&lt;br /&gt;In the process (I spent like an hour on this!) I’ve also managed to strip down the code to the bare minimum: email address, subject line and email body. Here is what it boiled down to:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;DATA: objcont LIKE solisti1 OCCURS 0 WITH HEADER LINE.&lt;br /&gt;DATA: reclist LIKE somlreci1 OCCURS 0 WITH HEADER LINE.&lt;br /&gt;DATA: doc_chng LIKE sodocchgi1.&lt;br /&gt;&lt;br /&gt;* Email subject line&lt;br /&gt;doc_chng-obj_descr = 'Email subject'.&lt;br /&gt;&lt;br /&gt;* Email body&lt;br /&gt;objcont = 'This is a line to be sent in the email body'.&lt;br /&gt;APPEND objcont.&lt;br /&gt;&lt;br /&gt;* Receiver list&lt;br /&gt;reclist-receiver = 'your email address here'.&lt;br /&gt;reclist-rec_type = 'U'.&lt;br /&gt;APPEND reclist.&lt;br /&gt;&lt;br /&gt;* Send the document&lt;br /&gt;CALL FUNCTION 'SO_NEW_DOCUMENT_SEND_API1'&lt;br /&gt;  EXPORTING&lt;br /&gt;    document_data              = doc_chng&lt;br /&gt;    commit_work                = 'X'&lt;br /&gt;  TABLES&lt;br /&gt;    object_content             = objcont&lt;br /&gt;    receivers                  = reclist&lt;br /&gt;  EXCEPTIONS&lt;br /&gt;    too_many_receivers         = 1&lt;br /&gt;    document_not_sent          = 2&lt;br /&gt;    operation_no_authorization = 4&lt;br /&gt;    OTHERS                     = 99.&lt;br /&gt;&lt;br /&gt;IF sy-subrc &lt;&gt; 0.&lt;br /&gt;  MESSAGE 'Email could not be sent' TYPE 'I'.&lt;br /&gt;ELSE.&lt;br /&gt;  MESSAGE 'Email was sent successfully' TYPE 'I'.&lt;br /&gt;ENDIF.&lt;/blockquote&gt;&lt;br /&gt;A few pointers:&lt;br /&gt;- Email address is not case-sensitive. &lt;br /&gt;- Since I have only one recipient, I check only for sy-subrc after the function module. If you have a list of recipients, use the code from the FM documentation, which checks the status of each recipient. &lt;br /&gt;- Objcont here is an internal table. In my program I populate a table i_errors with the error messages and then copy the content to objcont.&lt;br /&gt;&lt;br /&gt;You can also send attachments using this function module. As far as I understand, the attachment should be written into a file and the file name should be submitted to the FM.&lt;br /&gt;&lt;br /&gt;I’m pretty sure you could get an email address from a user profile (user profile display is in transaction SU3) but we don’t store emails there, so we chose to use our own Z table instead.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34883048-115949247548367333?l=friendlyabaper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://friendlyabaper.blogspot.com/feeds/115949247548367333/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34883048&amp;postID=115949247548367333' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115949247548367333'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34883048/posts/default/115949247548367333'/><link rel='alternate' type='text/html' href='http://friendlyabaper.blogspot.com/2006/09/sending-emails-from-sap.html' title='Sending emails from SAP'/><author><name>Jelena</name><uri>http://www.blogger.com/profile/14435835526289950029</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry></feed>
