Skip to main content

Integrating OptiPack into SAP EWM

Introduction

Below is a schematic overview of how the cartonization process is handled today in SAP (on the left) and how it would look like after integrating with our OptiPack API (on the right).

SAP_Pack_Flow.svg

SAP EWM supports HU (Handling Unit) creation, including CAP (Cartonization Planning) and PSHU (planned shipping HUs). In practice, many sites run static or template-based rules:

  • Dimensions from material/packaging master exist but are underused; combining multiple items into the most efficient HU layout is often not optimized.
  • There's no spatial optimization (3D packing, allowed orientations, weight constraints, etc.).

This is where OptiPack comes in. The OptiPack API can be called right after Warehouse Task Creation, when all materials and their dimensions are known but before SAP decides on how to pack them into Handling Units. We recommend also running the SAP standard HU creation rules, but not persisting them. This could serve as a fallback in the unlikely event something goes wrong, making OptiPack non-intrusive. Your operations can just continue normally while you gain efficiency improvements.

In the remainder of this guide, we demonstrate how we could generate a request (JSON format) that could be sent to our API. It is important to note that, in order to keep this guide as general as possible, many assumptions and simplifications were made. Your company might have some specific details that would require some modifications to the examples below.

Required API data

Below is a minimal JSON for the /pack endpoint:

1{ 2 "orders": [ 3 { 4 "id": "order_1", 5 "items": [ 6 { 7 "id": "item_1", 8 "width": 1, 9 "height": 1, 10 "depth": 1, 11 "quantity": 4 12 } 13 ] 14 } 15 ], 16 "bins": [ 17 { 18 "id": "bin_1", 19 "width": 1, 20 "height": 1, 21 "depth": 1 22 } 23 ] 24}

It consists of the following pieces of informations:

  • orders: a list of the orders that must be packed; items of different orders will never be packed onto the same bin.
  • bins: the different bins (HUs) available in the warehouse, with their dimensional information. Many other attributes & constraints are supported as well, for the full list, we refer to our API documentation.

For every order a list of items has to be provided, this contains information such as:

  • width, height, depth: the three dimensions of every item
  • quantity: how many of this item were ordered
  • many other possible constraints & attributes such as its weight, the weight it can carry, allowed orientations, sequence values and many many more. See our API documentation for a complete list.

SAP Data tables

In the following subsections, we’ll have a look at each of the relevant data tables that correspond to the components of the schematic overview above which will allow us to construct a JSON that serves as a request for our API. We’ll look at the data both in the SAP GUI and by making a query in the ABAP (Advanced Business Application Programming) language.

Outbound Deliveries Orders (/SCWM/PRDO, /SCDL/*)

To populate the order list of our request, we query the delivery documents in our system and which items + quantities they contain. In the GUI, we can view detailed information for each ODO in /n/SCWM/PRDO.

image.png

General Material Data (MM03 / MARA)

There’s two ways to inspect the material master data (both for SKUs and packaging material / handling units). Either the MM03 transaction to inspect each material individually, or the MARA (or MARM more specifically) table through the table viewer (/nSE16)

image.png

The most important, and mandatory information, are the dimensions of each product. These can be found in Additional Data > Units of Measure

image.png

image.png

To view all information in 1 table view, we can use the /nSE16 command and navigate to the MARA table.

image.png

Below is a view of both SKUs and HUs where the dimensions have been filled in for:

image.png

We can now write individual queries to get both the items of the ODOs in our system, as well as the different packaging types in which we can cartonize:

Querying the ordered items with dimensions

1REPORT z_list_procio_item_dims. 2 3"--- Result row (DOCNO instead of VBELN because we're in EWM) 4TYPES: BEGIN OF ty_row, 5 docno TYPE /scdl/dl_docno_int, 6 matnr TYPE matnr, 7 width TYPE mara-breit, 8 height TYPE mara-hoehe, 9 depth TYPE mara-laeng, 10 quantity TYPE p DECIMALS 3, 11 END OF ty_row. 12TYPES tt_row TYPE STANDARD TABLE OF ty_row WITH EMPTY KEY. 13 14"--- Temp read from /SCDL/DB_PROCI_O 15TYPES: BEGIN OF ty_pi, 16 docno TYPE /scdl/dl_docno_int, 17 itemno TYPE /scdl/dl_itemno, 18 productno TYPE /scdl/dl_productno, 19 qty TYPE p DECIMALS 3, 20 END OF ty_pi. 21DATA lt_pi TYPE STANDARD TABLE OF ty_pi. 22 23START-OF-SELECTION. 24 25" 1) Read ODO items (warehouse 1710, doccat PDO) 26SELECT docno, itemno, productno 27 FROM /scdl/db_proci_o 28 INTO TABLE @DATA(lt_raw) 29 WHERE /scwm/whno = '1710' 30 AND doccat = 'PDO'. 31 32IF lt_raw IS INITIAL. 33 WRITE: / 'No /SCDL/DB_PROCI_O items for LGNUM 1710.'. 34 LEAVE PROGRAM. 35ENDIF. 36 37" Resolve quantity via common field names (QTY, QTY_REQ, REQ_QTY, QREQ) 38FIELD-SYMBOLS: <r> TYPE any, <q> TYPE any. 39DATA ls_pi TYPE ty_pi. 40LOOP AT lt_raw ASSIGNING <r>. 41 CLEAR ls_pi. 42 ASSIGN COMPONENT 'DOCNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<d>). IF sy-subrc = 0. ls_pi-docno = <d>. ENDIF. 43 ASSIGN COMPONENT 'ITEMNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<i>). IF sy-subrc = 0. ls_pi-itemno = <i>. ENDIF. 44 ASSIGN COMPONENT 'PRODUCTNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<p>). IF sy-subrc = 0. ls_pi-productno = <p>. ENDIF. 45 46 ASSIGN COMPONENT 'QTY' OF STRUCTURE <r> TO <q>. 47 IF sy-subrc <> 0. ASSIGN COMPONENT 'QTY_REQ' OF STRUCTURE <r> TO <q>. ENDIF. 48 IF sy-subrc <> 0. ASSIGN COMPONENT 'REQ_QTY' OF STRUCTURE <r> TO <q>. ENDIF. 49 IF sy-subrc <> 0. ASSIGN COMPONENT 'QREQ' OF STRUCTURE <r> TO <q>. ENDIF. 50 IF sy-subrc = 0. ls_pi-qty = <q>. ELSE. ls_pi-qty = 1. ENDIF. 51 52 APPEND ls_pi TO lt_pi. 53ENDLOOP. 54 55" 2) Collect distinct MATNRs and read MARA dimensions (non-zero only) 56TYPES: BEGIN OF ty_mkey, matnr TYPE matnr, END OF ty_mkey. 57DATA lt_mkeys TYPE SORTED TABLE OF ty_mkey WITH UNIQUE KEY matnr. 58 59FIELD-SYMBOLS <pi> LIKE LINE OF lt_pi. 60LOOP AT lt_pi ASSIGNING <pi>. 61 DATA lv_m TYPE matnr. 62 lv_m = <pi>-productno. 63 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT' 64 EXPORTING input = lv_m 65 IMPORTING output = lv_m. 66 INSERT VALUE ty_mkey( matnr = lv_m ) INTO TABLE lt_mkeys. 67ENDLOOP. 68 69SELECT matnr, laeng, breit, hoehe 70 FROM mara 71 INTO TABLE @DATA(lt_mdim) 72 FOR ALL ENTRIES IN @lt_mkeys 73 WHERE matnr = @lt_mkeys-matnr 74 AND laeng > 0 AND breit > 0 AND hoehe > 0. 75 76" hashed map MATNR -> dims 77TYPES: BEGIN OF ty_m2d, 78 matnr TYPE matnr, 79 laeng TYPE mara-laeng, 80 breit TYPE mara-breit, 81 hoehe TYPE mara-hoehe, 82 END OF ty_m2d. 83DATA lt_m2d TYPE HASHED TABLE OF ty_m2d WITH UNIQUE KEY matnr. 84 85FIELD-SYMBOLS <md> LIKE LINE OF lt_mdim. 86LOOP AT lt_mdim ASSIGNING <md>. 87 INSERT VALUE ty_m2d( matnr = <md>-matnr 88 laeng = <md>-laeng 89 breit = <md>-breit 90 hoehe = <md>-hoehe ) INTO TABLE lt_m2d. 91ENDLOOP. 92 93" 3) Build result rows; drop items without maintained (non-zero) dims 94DATA lt_rows TYPE tt_row. 95FIELD-SYMBOLS <m2d> LIKE LINE OF lt_m2d. 96DATA ls_row TYPE ty_row. 97 98LOOP AT lt_pi ASSIGNING <pi>. 99 DATA lv_m_int TYPE matnr. 100 lv_m_int = <pi>-productno. 101 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT' 102 EXPORTING input = lv_m_int 103 IMPORTING output = lv_m_int. 104 105 READ TABLE lt_m2d ASSIGNING <m2d> WITH TABLE KEY matnr = lv_m_int. 106 IF sy-subrc <> 0. 107 CONTINUE. 108 ENDIF. 109 110 CLEAR ls_row. 111 ls_row-docno = <pi>-docno. 112 ls_row-quantity = <pi>-qty. 113 ls_row-width = <m2d>-breit. 114 ls_row-height = <m2d>-hoehe. 115 ls_row-depth = <m2d>-laeng. 116 117 " pretty MATNR for display 118 ls_row-matnr = lv_m_int. 119 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT' 120 EXPORTING input = ls_row-matnr 121 IMPORTING output = ls_row-matnr. 122 123 APPEND ls_row TO lt_rows. 124ENDLOOP. 125 126cl_demo_output=>display( lt_rows ).

image.png

Querying the packaging types with dimensions

1REPORT z_list_hu_bins_min. 2 3" Output: VERP material id + maintained dims (non-zero only) 4TYPES: BEGIN OF ty_bin, 5 id TYPE matnr, 6 width TYPE mara-breit, 7 height TYPE mara-hoehe, 8 depth TYPE mara-laeng, 9 END OF ty_bin. 10DATA lt_bins TYPE STANDARD TABLE OF ty_bin WITH EMPTY KEY. 11 12" Read VERP materials where all three dims are maintained (>0) 13SELECT matnr AS id, 14 breit AS width, 15 hoehe AS height, 16 laeng AS depth 17 FROM mara 18 INTO TABLE @lt_bins 19 WHERE mtart = 'VERP' 20 AND laeng > 0 AND breit > 0 AND hoehe > 0. 21 22" Optional: show MATNR in external format 23FIELD-SYMBOLS <b> LIKE LINE OF lt_bins. 24LOOP AT lt_bins ASSIGNING <b>. 25 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT' 26 EXPORTING input = <b>-id 27 IMPORTING output = <b>-id. 28ENDLOOP. 29 30IF lt_bins IS INITIAL. 31 WRITE: / 'No VERP materials with non-zero dimensions found.'. 32 LEAVE PROGRAM. 33ENDIF. 34 35cl_demo_output=>display( lt_bins ).

image.png

Setting up a connection with OptiPack

We need to allow our SAP system to make a connection the OptiPack API. This can be configured in /nSTRUST and /nSM59. For this, we need to download the Certificate of our OptiPack API to upload it here. The instructions are very similar to how a connection to our OptiPick API has to be set up, so the screenshots below are focused on that. The certificate can be downloaded from the browser (https://optiapp.api.optioryx.com). Below are screenshots to do it in Google Chrome:

image.png

image.png

image.png

Most export formats will work, so choose your favourite. Afterwards, go to /nSTRUST in SAP and upload the certificate here. You should see it added in the Certificate List (middle panel).

image.png

After adding the certificate, we add an extra connection using /nSM59.

image.png

In here, create a new “HTTP Connection to External Server”.Then fill in the following information in the “Technical Settings” and “Logon & Security” tabs.

image.png

image.png

When configured, you can run a “Connection Test” from here:

image.png

We get Error 405 from our API, but this makes sense as the Connection Test just sends a simple GET request (while our API expects a POST). This confirms our connection works! Within ABAP we can use this with the http_client as follows:

1DATA: lo_http TYPE REF TO if_http_client. 2 3cl_http_client=>create_by_destination( 4 EXPORTING destination = 'OptiPack' 5 IMPORTING client = lo_http ).

ABAP script to generate request and visualise

We now have all necessary information to send a first request to the OptiPack API. We first query the ODOs with their items and quantities, we then join this with the MARA table to get the necessary dimension information. We filter away order lines where one or more of the dimensions are undefined or equal to 0. Afterwards, we get the packaging materials (type = VERP). We then construct the JSON to send to our API. In the end, we first visualise the response in JSON format that we get back from the API. After that, we also take the first order from the response to call a second endpoint that returns a HTML with an interactive visualisation of that order.

1REPORT zopti_pack_call_procio. 2 3"============================= 4" tiny JSON helper 5"============================= 6CLASS lcl_json DEFINITION FINAL. 7 PUBLIC SECTION. 8 CLASS-METHODS ser IMPORTING data TYPE any RETURNING VALUE(json) TYPE string. 9ENDCLASS. 10CLASS lcl_json IMPLEMENTATION. 11 METHOD ser. 12 json = /ui2/cl_json=>serialize( 13 data = data 14 pretty_name = /ui2/cl_json=>pretty_mode-low_case ). 15 ENDMETHOD. 16ENDCLASS. 17 18"============================= 19" parameter(s) 20"============================= 21PARAMETERS: p_apkey TYPE string LOWER CASE OBLIGATORY 22 DEFAULT '<CENSORED>'. 23 24"============================= 25" simple types for payload 26"============================= 27TYPES: BEGIN OF ty_item_json, 28 id TYPE string, 29 width TYPE p DECIMALS 3, 30 height TYPE p DECIMALS 3, 31 depth TYPE p DECIMALS 3, 32 quantity TYPE p DECIMALS 3, 33 END OF ty_item_json. 34TYPES tt_item_json TYPE STANDARD TABLE OF ty_item_json WITH EMPTY KEY. 35 36TYPES: BEGIN OF ty_order_json, 37 id TYPE string, 38 items TYPE tt_item_json, 39 END OF ty_order_json. 40TYPES tt_order_json TYPE STANDARD TABLE OF ty_order_json WITH EMPTY KEY. 41 42TYPES: BEGIN OF ty_bin_json, 43 id TYPE string, 44 width TYPE p DECIMALS 3, 45 height TYPE p DECIMALS 3, 46 depth TYPE p DECIMALS 3, 47 END OF ty_bin_json. 48TYPES tt_bin_json TYPE STANDARD TABLE OF ty_bin_json WITH EMPTY KEY. 49 50TYPES: BEGIN OF ty_payload, 51 scenario_id TYPE string, 52 orders TYPE tt_order_json, 53 bins TYPE tt_bin_json, 54 END OF ty_payload. 55 56"============================= 57" 1) Read ODO items from /SCDL/DB_PROCI_O 58"============================= 59TYPES: BEGIN OF ty_proci_min, 60 docno TYPE /scdl/dl_docno_int, " internal ODO number 61 itemno TYPE /scdl/dl_itemno, " item number 62 productno TYPE /scdl/dl_productno, " material 63 qty TYPE p DECIMALS 3, " best-effort quantity 64 END OF ty_proci_min. 65DATA lt_proci TYPE STANDARD TABLE OF ty_proci_min. 66 67START-OF-SELECTION. 68 69" Raw read (without qty yet) 70SELECT docno, itemno, productno 71 FROM /scdl/db_proci_o 72 INTO TABLE @DATA(lt_raw) 73 WHERE /scwm/whno = '1710' 74 AND doccat = 'PDO'. 75 76IF lt_raw IS INITIAL. 77 WRITE: / 'No /SCDL/DB_PROCI_O items for LGNUM 1710.'. 78 LEAVE PROGRAM. 79ENDIF. 80 81" Copy + resolve quantity via dynamic component names (QTY variants) 82FIELD-SYMBOLS: <r> TYPE any, 83 <q> TYPE any. 84DATA ls_min TYPE ty_proci_min. 85 86LOOP AT lt_raw ASSIGNING <r>. 87 CLEAR: ls_min, ls_min-qty. 88 ASSIGN COMPONENT 'DOCNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<d>). IF sy-subrc = 0. ls_min-docno = <d>. ENDIF. 89 ASSIGN COMPONENT 'ITEMNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<i>). IF sy-subrc = 0. ls_min-itemno = <i>. ENDIF. 90 ASSIGN COMPONENT 'PRODUCTNO' OF STRUCTURE <r> TO FIELD-SYMBOL(<p>). IF sy-subrc = 0. ls_min-productno = <p>. ENDIF. 91 92 " try common quantity field names: 93 IF sy-subrc = 0. 94 ASSIGN COMPONENT 'QTY' OF STRUCTURE <r> TO <q>. 95 IF sy-subrc <> 0. ASSIGN COMPONENT 'QTY_REQ' OF STRUCTURE <r> TO <q>. ENDIF. 96 IF sy-subrc <> 0. ASSIGN COMPONENT 'REQ_QTY' OF STRUCTURE <r> TO <q>. ENDIF. 97 IF sy-subrc <> 0. ASSIGN COMPONENT 'QREQ' OF STRUCTURE <r> TO <q>. ENDIF. 98 99 IF sy-subrc = 0. 100 ls_min-qty = <q>. 101 ELSE. 102 ls_min-qty = 1. " fallback so we always have something 103 ENDIF. 104 ENDIF. 105 106 APPEND ls_min TO lt_proci. 107ENDLOOP. 108 109"============================= 110" 2) Read MARA dimensions (non-zero only) for those materials 111"============================= 112TYPES: BEGIN OF ty_m2d, 113 matnr TYPE matnr, 114 laeng TYPE mara-laeng, 115 breit TYPE mara-breit, 116 hoehe TYPE mara-hoehe, 117 END OF ty_m2d. 118DATA lt_m2d TYPE HASHED TABLE OF ty_m2d WITH UNIQUE KEY matnr. 119 120" collect distinct MATNR (internal) 121TYPES: BEGIN OF ty_mkey, matnr TYPE matnr, END OF ty_mkey. 122DATA lt_mkeys TYPE SORTED TABLE OF ty_mkey WITH UNIQUE KEY matnr. 123 124FIELD-SYMBOLS <x> LIKE LINE OF lt_proci. 125LOOP AT lt_proci ASSIGNING <x>. 126 DATA lv_m_int TYPE matnr. 127 lv_m_int = <x>-productno. 128 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT' 129 EXPORTING input = lv_m_int 130 IMPORTING output = lv_m_int. 131 INSERT VALUE ty_mkey( matnr = lv_m_int ) INTO TABLE lt_mkeys. 132ENDLOOP. 133 134IF lt_mkeys IS INITIAL. 135 WRITE: / 'No materials to read in MARA.'. 136 LEAVE PROGRAM. 137ENDIF. 138 139SELECT matnr, laeng, breit, hoehe 140 FROM mara 141 INTO TABLE @DATA(lt_mara) 142 FOR ALL ENTRIES IN @lt_mkeys 143 WHERE matnr = @lt_mkeys-matnr 144 AND laeng > 0 AND breit > 0 AND hoehe > 0. 145 146FIELD-SYMBOLS <mrow> LIKE LINE OF lt_mara. 147LOOP AT lt_mara ASSIGNING <mrow>. 148 INSERT VALUE ty_m2d( 149 matnr = <mrow>-matnr 150 laeng = <mrow>-laeng 151 breit = <mrow>-breit 152 hoehe = <mrow>-hoehe ) INTO TABLE lt_m2d. 153ENDLOOP. 154 155"============================= 156" 3) Keep only items whose material has non-zero dims, aggregate per DOCNO+MATNR 157"============================= 158TYPES: BEGIN OF ty_agg_row, 159 docno TYPE /scdl/dl_docno_int, 160 matnr TYPE matnr, 161 qty TYPE p DECIMALS 3, 162 END OF ty_agg_row. 163DATA lt_agg TYPE HASHED TABLE OF ty_agg_row WITH UNIQUE KEY docno matnr. 164 165FIELD-SYMBOLS: <a> LIKE LINE OF lt_agg. 166 167LOOP AT lt_proci ASSIGNING <x>. 168 DATA lv_m_int2 TYPE matnr. 169 lv_m_int2 = <x>-productno. 170 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT' 171 EXPORTING input = lv_m_int2 172 IMPORTING output = lv_m_int2. 173 174 " keep only if we have dims 175 READ TABLE lt_m2d WITH TABLE KEY matnr = lv_m_int2 TRANSPORTING NO FIELDS. 176 IF sy-subrc <> 0. 177 CONTINUE. 178 ENDIF. 179 180 READ TABLE lt_agg ASSIGNING <a> WITH TABLE KEY docno = <x>-docno matnr = lv_m_int2. 181 IF sy-subrc = 0. 182 <a>-qty = <a>-qty + <x>-qty. 183 ELSE. 184 INSERT VALUE ty_agg_row( docno = <x>-docno matnr = lv_m_int2 qty = <x>-qty ) INTO TABLE lt_agg. 185 ENDIF. 186ENDLOOP. 187 188IF lt_agg IS INITIAL. 189 WRITE: / 'Nothing to send (no PROCI_O items with non-zero dimensions).'. 190 LEAVE PROGRAM. 191ENDIF. 192 193"============================= 194" 4) Build orders[] from lt_agg (order id = DOCNO pretty-printed) 195"============================= 196DATA lt_orders TYPE tt_order_json. 197DATA lt_items TYPE tt_item_json. 198DATA ls_order TYPE ty_order_json. 199DATA ls_item TYPE ty_item_json. 200 201" collect distinct DOCNO 202TYPES: BEGIN OF ty_dkey, docno TYPE /scdl/dl_docno_int, END OF ty_dkey. 203DATA lt_docnos TYPE SORTED TABLE OF ty_dkey WITH UNIQUE KEY docno. 204 205FIELD-SYMBOLS <ar> LIKE LINE OF lt_agg. 206LOOP AT lt_agg ASSIGNING <ar>. 207 INSERT VALUE ty_dkey( docno = <ar>-docno ) INTO TABLE lt_docnos. 208ENDLOOP. 209 210FIELD-SYMBOLS: <dk> LIKE LINE OF lt_docnos, 211 <m2d> LIKE LINE OF lt_m2d. 212 213LOOP AT lt_docnos ASSIGNING <dk>. 214 CLEAR lt_items. 215 LOOP AT lt_agg ASSIGNING <ar> WHERE docno = <dk>-docno. 216 READ TABLE lt_m2d ASSIGNING <m2d> WITH TABLE KEY matnr = <ar>-matnr. 217 IF sy-subrc <> 0. CONTINUE. ENDIF. 218 219 CLEAR ls_item. 220 ls_item-quantity = <ar>-qty. 221 ls_item-width = <m2d>-breit. 222 ls_item-height = <m2d>-hoehe. 223 ls_item-depth = <m2d>-laeng. 224 225 DATA lv_m_out TYPE matnr. lv_m_out = <ar>-matnr. 226 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT' 227 EXPORTING input = lv_m_out 228 IMPORTING output = lv_m_out. 229 ls_item-id = lv_m_out. 230 231 APPEND ls_item TO lt_items. 232 ENDLOOP. 233 234 IF lt_items IS INITIAL. 235 CONTINUE. 236 ENDIF. 237 238 " DOCNO is an internal number; still use as order id (string) 239 CLEAR ls_order. 240 ls_order-id = |{ <dk>-docno }|. 241 ls_order-items = lt_items. 242 APPEND ls_order TO lt_orders. 243ENDLOOP. 244 245"============================= 246" 5) Bins = HU types (VERP) with non-zero dims 247"============================= 248DATA lt_bins TYPE tt_bin_json. 249SELECT matnr AS id, breit AS width, hoehe AS height, laeng AS depth 250 FROM mara 251 INTO TABLE @lt_bins 252 WHERE mtart = 'VERP' 253 AND laeng > 0 AND breit > 0 AND hoehe > 0. 254 255FIELD-SYMBOLS <b> LIKE LINE OF lt_bins. 256LOOP AT lt_bins ASSIGNING <b>. 257 CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT' 258 EXPORTING input = <b>-id 259 IMPORTING output = <b>-id. 260ENDLOOP. 261 262IF lt_bins IS INITIAL. 263 WRITE: / 'No VERP bins with non-zero dimensions.'. 264 LEAVE PROGRAM. 265ENDIF. 266 267"============================= 268" 6) Build payload and POST to OptiPack 269"============================= 270DATA ls_payload TYPE ty_payload. 271ls_payload-scenario_id = 'scenario_1'. 272ls_payload-orders = lt_orders. 273ls_payload-bins = lt_bins. 274 275DATA lv_json_req TYPE string. 276lv_json_req = lcl_json=>ser( ls_payload ). 277 278DATA lo_http TYPE REF TO if_http_client. 279cl_http_client=>create_by_destination( 280 EXPORTING destination = 'OptiPack' 281 IMPORTING client = lo_http ). 282 283lo_http->request->set_method( 'POST' ). 284lo_http->request->set_header_field( name = 'Content-Type' value = 'application/json' ). 285lo_http->request->set_header_field( name = 'accept' value = 'application/json' ). 286lo_http->request->set_header_field( name = 'x-api-key' value = p_apkey ). 287lo_http->request->set_cdata( lv_json_req ). 288 289lo_http->send( ). 290lo_http->receive( ). 291 292DATA lv_status TYPE i. 293DATA lv_reason TYPE string. 294DATA lv_json_res TYPE string. 295 296lo_http->response->get_status( IMPORTING code = lv_status reason = lv_reason ). 297lv_json_res = lo_http->response->get_cdata( ). 298 299" Pretty-print request & response 300CALL TRANSFORMATION sjson2html 301 SOURCE XML lv_json_req 302 RESULT XML DATA(html_req). 303cl_demo_output=>display_html( cl_abap_codepage=>convert_from( html_req ) ). 304 305CALL TRANSFORMATION sjson2html 306 SOURCE XML lv_json_res 307 RESULT XML DATA(html_res). 308cl_demo_output=>display_html( cl_abap_codepage=>convert_from( html_res ) ). 309 310lo_http->close( ). 311 312"============================= 313" 7) Extract _id and open interactive visualization 314"============================= 315DATA lv_scn_id TYPE string. 316 317TYPES: BEGIN OF ty_pack_res, _id TYPE string, END OF ty_pack_res. 318DATA ls_pack_res TYPE ty_pack_res. 319 320TRY. 321 /ui2/cl_json=>deserialize( 322 EXPORTING json = lv_json_res 323 CHANGING data = ls_pack_res ). 324 lv_scn_id = ls_pack_res-_id. 325 CATCH cx_root. 326 CLEAR lv_scn_id. 327ENDTRY. 328 329IF lv_scn_id IS INITIAL. 330 FIND REGEX '"_id"s*:s*"([A-Za-z0-9]+)"' IN lv_json_res SUBMATCHES lv_scn_id. 331ENDIF. 332 333IF lv_scn_id IS INITIAL. 334 WRITE: / 'No _id found in OptiPack response – cannot open visualization.'. 335 RETURN. 336ENDIF. 337 338DATA lv_url TYPE string. 339DATA lv_html TYPE string. 340DATA lo_vis TYPE REF TO if_http_client. 341 342lv_url = |https://optiapp.api.optioryx.com/visualisation/interactive/{ lv_scn_id }/0/0?offline=false|. 343 344cl_http_client=>create_by_url( 345 EXPORTING url = lv_url 346 IMPORTING client = lo_vis ). 347 348lo_vis->request->set_method( 'GET' ). 349lo_vis->request->set_header_field( name = 'accept' value = 'text/html' ). 350lo_vis->request->set_header_field( name = 'x-api-key' value = p_apkey ). 351 352lo_vis->send( ). 353lo_vis->receive( ). 354 355lv_html = lo_vis->response->get_cdata( ). 356lo_vis->close( ). 357 358cl_demo_output=>display_html( lv_html ).

The results of executing this script:

image.png

image.png

image.png

image.png

Field mapping (recap)

OptiPack JSONSAP EWM source
orders[].idDOCNO from /SCDL/DB_PROCI_O (or ERP VBELN via /SCDL/DB_PROCH_O)
orders[].items[].idPRODUCTNO (pretty-printed MATNR)
orders[].items[].quantityQTY or QTY_REQ / REQ_QTY / QREQ in /SCDL/DB_PROCI_O
items[].width/height/depthMARM preferred, fallback MARA
bins[].idMATNR of packaging material (VERP)
bins[].width/height/depthMARM (preferred) or MARA of packaging material

Where to call OptiPack in SAP EWM?

PPF Action on ODO (recommended)

Trigger after WT creation (e.g., at wave release / ODO status). Run OptiPack; if success → store plan; if fail → use SAP CAP/standard. Easy to make idempotent.

Work Center (Packing)

User click → call OptiPack and propose HU assignment interactively.

Background job

Periodic job that picks up ready ODOs, posts plans in batches.

Fallback strategy: always have SAP CAP/standard precomputed but not persisted; persist only if OptiPack fails or is disabled. This keeps the integration non-intrusive.

Setup checklist

  • Master data: make sure SKUs and Packaging (VERP) materials have Length/Width/Height maintained (preferably in MARM).
  • Warehouse filters: /SCWM/WHNO, DOCCAT match target scope.
  • Connectivity: STRUST SSL chain; SM59 destination; proxy/egress allowlist.
  • Authorizations: program runs with access to /SCDL/*, MARA/MARM, STRUST, SM59.
  • Feature toggle: TVARVC (Z_OPTIPACK_ENABLED) or similar.

Additional Tips & Tricks

Units & conversions (important)

  • Prefer MARM, normalize to a single unit (e.g., CM).
  • Document orientation mapping: LAENG → depth, BREIT → width, HOEHE → height (as in examples).
  • If you adopt MARM, convert with MD_CONVERT_MATERIAL_UNIT or material UoM conversion logic.

Error handling & monitoring

  • Wrap HTTP with timeouts; for 429/5xx do retry with backoff.
  • Write to Application Log (SLG1) using CL_BAL_LOG (store ODO id(s), request hash, HTTP code, _id).
  • Keep calls idempotent: use scenario_id + upsert=true.

Security

  • Do not hardcode API keys. Store in SSFS or a protected Z-table; restrict maintenance via auth objects.
  • Ensure outbound proxy/firewall allowlist for optiapp.api.optioryx.com.

Performance & scaling

  • One API call per order scales much better than bundling orders in a single call.
  • Use FOR ALL ENTRIES reads (as you do) and normalize MATNR once.
  • For very large volumes, consider a CDS view joining PROCI_OMARM/MARA so HANA can push down filters.

Result application (if persisting the plan)

  • Persist as PSHU or shipping HUs on the ODO; create VEPO assignments accordingly.
  • In pick-to-cart flows, you can stamp pick-HUs and then consolidate to ship-HUs per the OptiPack plan.

Governance & transports

  • STRUST/SM59 + Z-programs go via DEV→QAS→PRD transports.
  • Gate rollout with the feature toggle per warehouse.