<body><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d34883048\x26blogName\x3dYour+Friendly+ABAPer\x26publishMode\x3dPUBLISH_MODE_BLOGSPOT\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttps://friendlyabaper.blogspot.com/search\x26blogLocale\x3den_US\x26v\x3d2\x26homepageUrl\x3dhttp://friendlyabaper.blogspot.com/\x26vt\x3d-8534208955155839123', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe", messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, messageHandlers: { 'blogger-ping': function() {} } }); } }); </script>

Oh my GOS!

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.

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.

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).

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!

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.

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.

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.
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.
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.
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.

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.

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.

On the list you will most likely see something like this (do Ctrl-F and type in BUS to search for this line):
SELECT WHERE "MANDT" = '100' AND "SAP_OBJECT" = 'BUS2037' AND "OBJECT_ID" LIKE '0090052466%'

See? Now not only we know what the type is, but also what the object key should look like.

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!

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.

Other adjustments that I made in the original code (apart from the general clean-up):
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.
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 \\sapcoredev01\nice_basis_guy_created_this_folder_for_me\test.doc. 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.
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.

So here is my homage, if you will, to Mr. Tiwari's blog:
PARAMETERS: p_key TYPE swo_typeid OBLIGATORY,
p_type TYPE swo_objtyp OBLIGATORY,
p_file TYPE c LENGTH 100 OBLIGATORY,
p_desc TYPE so_obj_des OBLIGATORY.

DATA: ls_fol_id TYPE soodk,
ls_obj_id TYPE soodk,
ls_obj_data TYPE sood1,
ls_folmem_k TYPE sofmk,
ls_note TYPE borident,
ls_object TYPE borident,
lv_ep_note TYPE borident-objkey,
lv_offset TYPE i.

DATA: it_objhead TYPE STANDARD TABLE OF soli,
it_content LIKE STANDARD TABLE OF soli,
wa_content LIKE soli.

ls_object-objkey = p_key.
ls_object-objtype = p_type.

TRY.
OPEN DATASET p_file FOR INPUT IN BINARY MODE.
WHILE sy-subrc = 0.
READ DATASET p_file INTO wa_content.
APPEND wa_content TO it_content.
ENDWHILE.
CLOSE DATASET p_file.
CATCH cx_sy_file_access_error.
MESSAGE 'Error reading file' TYPE 'E'.
ENDTRY.

CALL FUNCTION 'SO_CONVERT_CONTENTS_BIN'
EXPORTING
it_contents_bin = it_content[]
IMPORTING
et_contents_bin = it_content[].

CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'
EXPORTING
region = 'B'
IMPORTING
folder_id = ls_fol_id
EXCEPTIONS
OTHERS = 1.

ls_obj_data-objsns = 'O'.
ls_obj_data-objla = sy-langu.
ls_obj_data-objdes = p_desc.
lv_offset = STRLEN( p_file ) - 3.
ls_obj_data-file_ext = p_file+lv_offset(3).
ls_obj_data-objlen = LINES( it_content ) * 255.

CALL FUNCTION 'SO_OBJECT_INSERT'
EXPORTING
folder_id = ls_fol_id
object_type = 'EXT'
object_hd_change = ls_obj_data
IMPORTING
object_id = ls_obj_id
TABLES
objhead = it_objhead
objcont = it_content
EXCEPTIONS
active_user_not_exist = 35
folder_not_exist = 6
object_type_not_exist = 17
owner_not_exist = 22
parameter_error = 23
OTHERS = 1000.

IF sy-subrc = 0 AND ls_object-objkey IS NOT INITIAL.
ls_folmem_k-foltp = ls_fol_id-objtp.
ls_folmem_k-folyr = ls_fol_id-objyr.
ls_folmem_k-folno = ls_fol_id-objno.
ls_folmem_k-doctp = ls_obj_id-objtp.
ls_folmem_k-docyr = ls_obj_id-objyr.
ls_folmem_k-docno = ls_obj_id-objno.
lv_ep_note = ls_folmem_k.
ls_note-objtype = 'MESSAGE'.
ls_note-objkey = lv_ep_note.
CALL FUNCTION 'BINARY_RELATION_CREATE_COMMIT'
EXPORTING
obj_rolea = ls_object
obj_roleb = ls_note
relationtype = 'ATTA'
EXCEPTIONS
OTHERS = 1.
ELSE.
MESSAGE 'Not OK' TYPE 'I'.
RETURN.
ENDIF.

IF sy-subrc = 0.
MESSAGE 'OK' TYPE 'I'.
ELSE.
MESSAGE 'Not OK' TYPE 'I'.
ENDIF.

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.

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).

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.

OSS notes of interest:

There is a rather scary number of the GOS-related OSS notes. Here are some notes that might be of interest to ABAP'ers:
927407 - Determining the content of GOS and SAPoffice documents
904711 - SAPoffice: Where are documents physically stored?
916512 - SAPoffice: Directory for upload/download/display
448074 - Calling generic object services from the dialog

Links:

SDN Blog GOS in Background - Part I
SDN Blog GOS in Background - Part II
GOS - SAP Help
How to Attach Documents to Any Custom Program Using GOS (SDN)

posted by Your Friendly ABAPer @ 11:08,
Direct link to this post