Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
cancel
Showing results for 
Search instead for 
Did you mean: 
JohnMeadows
Product and Topic Expert
Product and Topic Expert
2,782
Dear Colleagues and Partners,

One of the many requests I have seen come through during both customer facing demos and in RFI/RFP documents that is the ability to see physical on-hand stock during the order entry process.

As you are aware Business ByDesign is configured to maximise the utilisation of inventory by prioritising need. ByDesign ensures that customers are served by requirement - not necessarily on First Come, First Served basis.

This allows ByD to manage stock requirements efficiently and without too much intervention by users.

However quite often in an organisation actually knowing the physical inventory and requirements can be useful to optimise the sales engagement, either by switch selling to alternative products or ensuring that we only "sell what we have" maybe for products reaching end of life?

What did we achieve?

By researching the business objects available in the Repository Explorer in the SAP Cloud Application Studio I found that this object:

Inventory.AggregationOverview

and

MaterialSupplyAndDemandView

Together with some query and code work, we were able to enrich the sales order screen to look like this


Let me tell you how I achieved this:

Step 1:


Create a new Business Object in ByD to receive the results of the queries made on the two objects mentioned above.

Business Object Definition:
import AP.Common.GDT as apCommonGDT;
import AP.LogisticsExecution.Global;

businessobject BO_SOItemAvail {

[Transient] element SOrder_ItemUUID: UUID;
[Transient] element SOrder_ItemPrdUUID: UUID;

action RetrieveOnHand;

node onHandStock{
[Transient][Label("Product")] element ProductID: ProductID;
[Transient][Label("Warehouse")] element LocationID: LocationID;
[Transient][Label("Warehouse Name")] element LocationDesc: SHORT_Description;
[Transient][Label("Storage location ID")] element StorageLocID: ID;
[Transient][Label("Storage Location Name")] element StorageLocDesc: SHORT_Description;
[Transient][Label("Quantity")] element OnHandQty: Quantity;
}
node materialPlan{
[Transient][Label("Planning Area ID")] element planAreaID: ID;
[Transient][Label("Planning Area")] element planArea: SHORT_Description;
[Transient][Label("Total Requirement")] element prdReq: Quantity;
[Transient][Label("Total OnHand Stock")] element prdOH: Quantity;
[Transient][Label("Total Net Available")] element prdNet: Quantity;

}
}

You may notice that I use the annotation "Transient" this means that the data only exists when the object is "opened". We do not need to save this data for future reference and so this ensures the db size does not get bloated.

Step 2:


Create an Embedded Component

This is going to get quite complicated , if you have never used embedded components, I suggest you watch this video presentation first!

ensure the new Embedded Component has a Data Structure and Data Field Called "Inport" added via the data model tab. You can then edit the host Sales Order Screen and bind our EC to the Public Outport of the Sales Order Item UUID.

We need to open the Sales Order OIF screen in the UI Designer of SAP Cloud Application Studio

Using the Configuration Explorer tab of the UI Designer navigate to SAP_BYD_APPLICATION_UI/crm/soc/salesorderoifeco.OIF.uicomponent double click to open.

Navigate to the Items Tab and highlight the Details Box in the bottom half of the screen.


In the right hand window open the extensibility explorer (Or using the menu item View>Extensibility Explorer.

If you have the correct section of the screen highlighted in the Explorer you will see this


Click the box "Add Tab with embedded component" and fill in the title. click button with ellipsis to navigate to your Embedded Component.

when selected, the Bind Option is available and here you select the "PublicOutPortECSalesOrderItem>Parameter" and bind to "Inport>Inport" on your Embedded component.

Save and close these screens and test the binding works (as in the video) by adding the inport field to the UI. Test by opening an existing sales order, and navigate to the items tab and view your new tab, here the UI should show the UUID of the SalesOrderItem, If this is successful we can move on to configure the actions needed.

Step 3:


Now that we have our embedded component, we need to open it again in the form designer. With it open we need to add some data to the controller tab to ensure the EC works in concert with the action scripts on the Business Object.

So first, select the controller tab (bottom left hand of the form designer) and create a new Event Handler. Mine is called Intialise_EventHandler.

First add a Business Object Operation as below


Secondly, add a Data Operation. This is step to assign the incoming data (product UUID) to the data structure in the UI.


Thirdly, we need a BOAction to fire the action script on the business object.


Highlight the action script in the Business Object browser in the lower window and click the "bind" button.

Finally, ensure that on the inPort the event is triggered in the "On Fire" event in the InPort property explorer (right hand side of the screen)


This is all the set up we need to ensure the data is property is transferred from the Sales Order host screen to our embedded component.

Step 4:


moving to the designer tab of the form editor, select the object "Advanced List Pane" from the toolbox and drag it into an empty panel on your form.


Now select the BO Browser tab from the bottom right and ensure your custom BO is visible in the top window.


you can now drag the data fields to the list pane to populate the screen with whatever data you want the user to see.


Finally we need a query to retrieve the data and populate the business object.

Step 5:


Back in the studio now, we can open the action script "retrieve on hand quantity"

This script serves 2 functions.

The queries are run to interrogate the inventory aggregate object and the physical inventory overview.

This data is then pushed into the business object and then displayed in our form.

In header part of my script we need to import some namespaces, since Sales Orders are in the CRM deployment unit and we need to access the objects in other deployment units.
import AP.LogisticsExecution.Global;
import AP.FO.ProductDataMaintenance.Global;
import AP.SupplyChainControl.Global;
var elInventory: elementsof BO_SOItemAvail;
var elInventoryOnHand: elementsof BO_SOItemAvail.onHandStock;
var elInventoryPlan: elementsof BO_SOItemAvail.materialPlan;
var SO_Item = SalesOrder.Item.Retrieve(this.SOrder_ItemUUID);

We then set some variables to be elements of our custom BO  -  this is a good technique to know for creating objects generally in ByDesign. Finally we retrieve the item using the UUID and assign it to the variable.
if(SO_Item.IsSet()){
var orderPrd = Material.Retrieve(SO_Item.ItemProduct.ProductUUID);
if(orderPrd.IsSet()){
//qry Inventory Aggregate View first
var qryAgView = Inventory.AggregationOverview.QueryByElements;
var agViewParam = qryAgView.CreateSelectionParams();
agViewParam.Add(qryAgView.CompanyID,"I","EQ", SO_Item.ToParent.SalesAndServiceBusinessArea.SalesOrganisation.CurrentSuperordinateCompany.ID);
agViewParam.Add(qryAgView.MainInventorySeparatingValues.MaterialKey.ProductID.content,"I","EQ",SO_Item.ItemProduct.ProductInternalID.content);

var agView = qryAgView.Execute(agViewParam);
if(agView.Count()>0){
foreach(var location in agView){
elInventoryOnHand.LocationID = location.InventoryManagedLocation.ID;
elInventoryOnHand.LocationDesc = location.InventoryManagedLocation.Description.GetFirst().Description;
if(location.LogisticsArea.IsSet()){
elInventoryOnHand.StorageLocID = location.LogisticsArea.ID;
elInventoryOnHand.StorageLocDesc= location.LogisticsArea.Description.GetFirst().Description;
}
elInventoryOnHand.OnHandQty = location.Quantity;
this.onHandStock.Create(elInventoryOnHand);
}
}

I use an "If" statement to ensure the data is properly "set" as ByD calls it. This clause ensures that the rest of the script is only executed when there is a product assigned to the Sales Order Item, and that we have retrieved the masterdata for that product. Generally these clauses should be used as often as possible in ByD to reduce the possibility of dumps, due to attempts to write or read null values.

You can see the query is built around demand for the company selling the products, and the product ID. Again, another if clause is used so that we don't try to write null values to the business object.

So so long as we have more than one record we assign the values, using a "For" statement to loop through all the retrieved records.

The final statement creates the business object with the data.

We then do the same again for the inventory overview
var qryPlanView		= MaterialSupplyAndDemandView.QueryByElements;
var planViewParam = qryPlanView.CreateSelectionParams();
planViewParam.Add(qryPlanView.MaterialKey.ProductID.content,"I","EQ",SO_Item.ItemProduct.ProductInternalID.content);
var rawPlanView = qryPlanView.Execute(planViewParam);
var planView = rawPlanView.OrderBy(n=>n.SupplyPlanningAreaUUID.content);
if(planView.Count()>0){
foreach(var plan in planView){
var netInv = plan.KeyFigures.InventoryTotalQuantity.content;
var netReq = plan.KeyFigures.NetRequirementTotalQuantity.content;
elInventoryPlan.planAreaID = plan.SupplyPlanningArea.ID.content;
elInventoryPlan.planArea = plan.SupplyPlanningArea.Description.GetFirst().Description;
elInventoryPlan.prdReq.content = netReq;
elInventoryPlan.prdReq.unitCode = plan.KeyFigures.NetRequirementTotalQuantity.unitCode;
elInventoryPlan.prdOH.content = netInv;
elInventoryPlan.prdOH.unitCode = plan.KeyFigures.InventoryTotalQuantity.unitCode;
elInventoryPlan.prdNet.content = (netInv + netReq);
elInventoryPlan.prdNet.unitCode = plan.KeyFigures.NetRequirementTotalQuantity.unitCode;

this.materialPlan.Create(elInventoryPlan);
}

With this script properly activated (no errors!!) you should be able to test the entire solution.

Step 6:


Create a new Sales Order

Add a material as a Sales Order Item

Click "View All"

navigate to your new tab, which should be in the lower half of your screen to see your new list

Please reach out if you have any questions!

 

John

4 Comments