Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Jelena_Perfiljeva
Active Contributor
1,869

Folks,

I have a theory: there aren’t many blog posts in the ABAP space about SAP EWM because it’s such a colossal pain in the back that no one wants to risk triggering their PTSD by revisiting the experience. It’s certainly that way for me, but this time, I’m bravely posting through the pain.

There is practically no information about creating an outbound delivery order (ODO) in SAP EWM. The SAP guides and the great SAP Press book include examples how to update ODOs but not how to create one.

Now, to be fair, this might be an odd requirement because EWM is kind of directed by The Big Kahuna Core ERP on what to deliver. But it is not something unheard of and is feasible in SAP GUI transaction /SCWM/PRDO.

The main source of information on ODO creation so far has been this blog post from 2020. It is still valid and huge "thank you" to the author for filling the vacuum! This post was one of my starting points, but it leaves very many gaps in the story. Since the remakes are all the rage these days, I bring you the 2025 edition of ODO creation, now with the executable code and more explanations.

Full executable program code is available on GitHub. It was tested in a post-2020 standalone EWM system but is likely to work in other settings. The program can create an ODO for an HU. If it’s not what you need, then make adjustments to use other aspects. All the steps are explained below and should be sufficient to make adjustment using the code as an example. But even if everything works for you “out of the box”, this code is not meant for productive use.

Overview

If you’ve been working with EWM for a while, you must have already gone through at least some DABDA stages. Otherwise, brace yourself.

In the core SAP ERP (ECC or S/4HANA) we know and love, the typical BAPI-based process of creating a document goes like this: you fill in a bunch of information, then call the BAPI function module and keep your fingers crossed that the BAPI gods smile upon you. From a coding perspective, it’s a rather straightforward “here is my data, do your thing,” though.

Now, the evil genius minds behind SAP EWM looked at that and thought, "Well, that’s just too convenient, and we don’t want that." Instead, in the EWM world, you’ll constantly find yourself assembling code like some macabre Lego™ set from various bits and pieces. Not that I pine for the function module comeback, but, ladies and gentlemen of the jury, in this post you will see that this way is, beyond a reasonable doubt, bonkers.

In this example we will be using different classes and methods. The overall flow is like this.

  1. We start with some boilerplate code that sets up a “service provider” and “transaction manager” instance.
  2. We call the service provider’s method INSERT with basic header-level data, such as document type. This step provides the document ID, even though no data is saved in DB yet.
  3. Then, we use that ID and keep calling method INSERT to add all other pieces: parties, HU, etc.  
  4. If no errors happened, we call SAVE method, followed by the ubiquitous “clean up”.

This sounds simple on paper, and this is where I suspect the SAP folks think they’ve created an awesome framework - while in reality, working with it is rather painful. At least with BAPI, we could clearly see all the parameters exposed, and it was obvious what’s what. But since, in this case, it’s the same INSERT method but with different data, we’re sort of flying blind.

What data are we providing? That depends on something called an aspect. What the heck is this aspect? That’s a whole separate subject. For the purposes of this example, though, it’s enough to understand that the aspect corresponds to the data structure. It’ll become a bit clearer when you look at the code, so no worries if you feel confused right now.

Boilerplate prep stuff

Service provider class is /SCDL/CL_SP_PRD_OUT.  We create an instance of that class and pass a “message box” (/SCDL/CL_SP_MESSAGE_BOX) reference to it, as well as “classic update” flag. No idea what it does but it works. Then, we “initialize transaction manager”.

  DATA(message_box) = NEW /scdl/cl_sp_message_box( ).
  DATA(delivery_sp) = NEW /scdl/cl_sp_prd_out(
    io_message_box = message_box
    iv_doccat      = /scdl/if_dl_doc_c=>sc_doccat_out_prd   " Outbound = PDO
    iv_mode        = /scdl/cl_sp=>sc_mode_classic ).        " No idea what this is for

* Important! This sort of starts LUW
  /scwm/cl_tm=>set_lgnum( p_lgnum ).

I used /scwm/cl_tm just because that’s what I copy-pasted from somewhere, it worked, and I’m too tired to change anything. A more recent recommendation is to use this code instead:

lo_tm ?= /scwm/cl_tm_factory=>get_service( /scwm/cl_tm_factory=>sc_manager ).
lo_tm->set_whno( p_whno ).

The OG 2020 blog post started with the suggestion to create a subclass to manipulate the values in MS_DEFAULT class attribute. There wasn’t a full code example for it, and I’m still puzzled how the author used it exactly. But my example works without it and instead feeds this data using the “parties”.

If you try to create an ODO manually in /SCWM/PRDO transaction, you might get a pop-up asking for these “default” values. I suspect this might be somehow related, but in general this remains a mystery.

Meet The Aspects

We use service provider’s INSERT method and header aspect (a constant from /SCDL/IF_SP_C interface). Based on the aspect name, INSERT method knows what to do, I guess.

  DATA(headers_in) = VALUE /scdl/t_sp_a_head( ( doctype    = p_doctyp
                                                doccat     = /scdl/if_dl_doc_c=>sc_doccat_out_prd "Might nor be needed?
                                                manual     = abap_true ) ).

  delivery_sp->insert( EXPORTING inrecords    = headers_in
                                 aspect       = /scdl/if_sp_c=>sc_asp_head
                       IMPORTING outrecords   = headers_out
                                 rejected     = rejected
                                 return_codes = return_codes ).

The million-dollar question here is: where did this /scdl/if_sp_c=>sc_asp_head and /scdl/t_sp_a_head come from?

Side rant

I found this information in the SAP Press book. It mentions SAP Note 1414179 that has some documents attached. Later I was able to track down a publicly available PDF that currently sits here.

You might wonder how does one find such useful PDF and I’ll tell you. The link to this PDF is… in another PDF. Which is linked on the SAP EWM page in SAP Help document area (not to be confused with the actual SAP Help content). Welcome to SAP Taco Town!

Without the book, how is anyone supposed to know to even search for this? I really don’t know. And even when you find these guides, they read like an acid trip. So yeah…

Error Handling

INSERT method returns the REJECTED value ‘X’ if your “submishinz” were rejected.

Moderator Kitteh.jpg

In my experiments, RETURN_CODES came out blank, but I kept it just in case. The code examples in “how-to guide” have this line to check for errors:

READ TABLE lt_return_codes TRANSPORTING NO FIELDS WITH KEY failed = abap_true.

In this example, to see the actual errors, we need to use the “message box” reference. There are two methods to get the messages in a table form: get_messages and get_messages_with_no. I prefer the latter because the result looks slightly better.

Side Rant Number 2

If you are used to dealing with BAPIRETURN table, you’re probably thinking: this is great, we get a nice table to see the errors. HA! HA! HA! Unlike the OG BAPIRETURN, this “message box” does not contain the actual message text. At first, I thought I must have used the wrong method and went to check the table definition. Nope, whole bunch of fields but no text.

Is there another method to get readable messages? Maybe, but not in the same class. The PDF guide just ends the code examples with an upbeat comment “now for example, messages could be displayed”.

Thanks for the tip.jpg

Adding Parties

This is where I initially ran into the odd errors complaining about the unknown warehouse. The errors were raised at the later SAVE stage, but I solved it by making changes in what party data is passed to INSERT method.

Even though warehouse ID was already provided before, it started working only after I included the warehouse location information. This might as well be related to the specific system configuration, I really don’t know. But calling FM /SCWM/LGNUM_LOCID_READ for the warehouse and using data it provides saved the day. I think I found it in one of the SCN questions but don't remember.

Reference Documents

This is another oddity. We don’t need to enter any references to create an ODO in transaction. But if this part is not called, I found that you’ll end up with an error message. I do suspect this might be related to the fact that this was in a decentralized EWM, but can't confirm.

  /scwm/cl_mq_services=>get_erp_systems( EXPORTING iv_whno    = p_lgnum
                                         IMPORTING et_systems = DATA(system_data) ).

[...]

  DATA(headers_refdoc_in) = VALUE /scdl/t_sp_a_head_refdoc( ( refdoccat = refdoc_cat_erp
                                                              refbskey  = system_data[ 1 ]-bskey ) ).

The Rest

Thankfully, at least HU part was straightforward, no land mines. Just the regular find the aspect, find the table type, and call INSERT.

If you need any additional data for your use case, throw it in somewhere before the SAVE method. After that, just check the familiar REJECTED flag and commit or rollback. At the end, calling /scwm/cl_tm=>cleanup( ) is another EWM ceremony that is like “well, everyone is doing it”.

Hopefully, you get successfully to the last WRITE that will output the human-readable ODO number. I got it from the DB table, like real cowboys, but I bet it can be made more complicated by using the service provider and query method.

Now, where is that Men in Black gizmo?

Jelena_Perfiljeva_0-1747371425080.png

2 Comments