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.
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.
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.
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.
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?
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…
INSERT method returns the REJECTED value ‘X’ if your “submishinz” were rejected.
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.
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”.
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.
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 ) ).
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?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
9 | |
7 | |
7 | |
5 | |
5 | |
4 | |
4 | |
4 | |
4 | |
3 |