How do I add an Inventory field to a Shipment form?

We would like to add something like PartWhse.OnHand or PartQty.OnHand to our Customer Shipment Entry form. Just a read-only field to give us an idea if we have inventory in stock so that we know to ship from inventory vs ship from job.

I’ve worked my way through both the Embedded Customization and Advanced Embedded Customization Self-Paced Courses, but they do not provide a workshop for this exact need, although I feel like they talk about it with Assembly References, adapters, contracts, etc. I just don’t know how to bring it all together. How can add one of the tables.fields and bind it to the form?

Would anyone be able to provide a working example they use or a reference to documentation that I could read through?

I had something similar where I needed information from OrderRel on the customer shipment entry screen and got some help in this thread.

It wont be an exact match but maybe the concept can help guide you if no one else can.

Thank you for sharing this. I had hoped to configure this with just customization, but reading through this helps me see more of how BPM’s can help with customizations.

This also leads me to think that the code from the BPM could be translated somehow into the Script Editor and perform the same methods. That’s part of what I’m still trying to learn.

Unless your company is firmly going to stay in classic I would do as little as possible in the script editor. Bpms and functions will convert up, but customization code will be gone in a few years.

This is good to know. I’ll need to customize things in Script Editor for now since our companies are still using Classic. However, I’m also trying to plan forward. Is this an example of where we would customize using App Studio?

I’ve browsed through App Studio just a little, but want to work my way through the Epicor Learning materials. I’m assuming this is the new customization for Kinetic forms, is that right?

If you have functions then that is the future. Calling a function to do the work server side. Screens will just present the data.

I am still in need of help on this. From my research across the web, I think I am close in the CallPartBinSearchAdapterGetPartBinByLotMethod(), but I don’t understand how to call my method.

In the code below, I began adding Form Events and just inserting a MessageBox per method and running back through the Customer Shipment form to see when each event’s method would be called.

Here are the steps I took:

  • Create New Pack
  • Enter Order Number
  • Select Lines tab
  • Select Actions>Mass Shipment (only one line to ship)
  • The form populates with the part number from the SO, and it’s at this point that I want to understand and build from.

The goal is to grab the populated part number and use that in the PartBin search to find OnHandQty. The only method that fired at this point was the EpiViewNotification, although it fired numerous times throughout the process, so I’m still trying to learn about this.

I guess my main question at this point is, what form event (if any) should I use to trigger my method once the ShipDtl.PartNum field populates but before any other data changes?

// **************************************************
// Custom code for CustShipForm
// Created: 2/27/2024 12:31:08 PM
// **************************************************

extern alias Erp_Contracts_BO_CustShip;
extern alias Erp_Contracts_BO_PackOutSearch;
extern alias Erp_Contracts_BO_PickedOrders;
extern alias Erp_Contracts_BO_MaterialQueue;
extern alias Erp_Contracts_BO_Company;
extern alias Erp_Contracts_BO_Warehse;
extern alias Erp_Contracts_BO_Part;

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Erp.Adapters;
using Erp.UI;
using Ice.Lib;
using Ice.Adapters;
using Ice.Lib.Customization;
using Ice.Lib.ExtendedProps;
using Ice.Lib.Framework;
using Ice.Lib.Searches;
using Ice.UI.FormFunctions;

public class Script
{
	// ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
	// Begin Wizard Added Module Level Variables **

	private EpiBaseAdapter oTrans_partBinSearchAdapter;
	private EpiDataView edvShipDtl;
	// End Wizard Added Module Level Variables **

	// Add Custom Module Level Variables Here **

	public void InitializeCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
		// Begin Wizard Added Variable Initialization

		this.ShipDtl_Column.ColumnChanged += new DataColumnChangeEventHandler(this.ShipDtl_AfterFieldChange);
		this.ShipDtl_Row.EpiRowChanged += new EpiRowChanged(this.ShipDtl_AfterRowChange);
		this.ShipDtl_Column.ColumnChanging += new DataColumnChangeEventHandler(this.ShipDtl_BeforeFieldChange);
		this.oTrans_partBinSearchAdapter = ((EpiBaseAdapter)(this.csm.TransAdaptersHT["oTrans_partBinSearchAdapter"]));
		this.oTrans_partBinSearchAdapter.AfterAdapterMethod += new AfterAdapterMethod(this.oTrans_partBinSearchAdapter_AfterAdapterMethod);
		this.oTrans_partBinSearchAdapter.BeforeAdapterMethod += new BeforeAdapterMethod(this.oTrans_partBinSearchAdapter_BeforeAdapterMethod);
		this.edvShipDtl = ((EpiDataView)(this.oTrans.EpiDataViews["ShipDtl"]));
		this.edvShipDtl.EpiViewNotification += new EpiViewNotification(this.edvShipDtl_EpiViewNotification);
		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls
		MessageBox.Show("InitializeCustomCode");
		// End Wizard Added Custom Method Calls
	}

	public void DestroyCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
		// Begin Wizard Added Object Disposal

		this.ShipDtl_Column.ColumnChanged -= new DataColumnChangeEventHandler(this.ShipDtl_AfterFieldChange);
		this.ShipDtl_Row.EpiRowChanged -= new EpiRowChanged(this.ShipDtl_AfterRowChange);
		this.ShipDtl_Column.ColumnChanging -= new DataColumnChangeEventHandler(this.ShipDtl_BeforeFieldChange);
		this.oTrans_partBinSearchAdapter.AfterAdapterMethod -= new AfterAdapterMethod(this.oTrans_partBinSearchAdapter_AfterAdapterMethod);
		this.oTrans_partBinSearchAdapter = null;
		this.oTrans_partBinSearchAdapter.BeforeAdapterMethod -= new BeforeAdapterMethod(this.oTrans_partBinSearchAdapter_BeforeAdapterMethod);
		this.edvShipDtl.EpiViewNotification -= new EpiViewNotification(this.edvShipDtl_EpiViewNotification);
		this.edvShipDtl = null;
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal

		// End Custom Code Disposal
	}

	private void CallPartBinSearchAdapterGetPartBinByLotMethod()
	{
		MessageBox.Show("CallPartBinSearchAdapterGetPartBinByLotMethod");
		try
		{
			// Declare and Initialize EpiDataView Variables
			EpiDataView edvShipDtl = ((EpiDataView)(this.oTrans.EpiDataViews["ShipDtl"]));

			// Check if valid EpiDataView Row(s) are selected
			if ((edvShipDtl.Row < 0))
			{
				return;
			}

			// Declare and create an instance of the Adapter.
			PartBinSearchAdapter adapterPartBinSearch = new PartBinSearchAdapter(this.oTrans);
			adapterPartBinSearch.BOConnect();

			// Declare and Initialize Variables
			// TODO: You may need to replace the default initialization with valid values as required for the BL method call.
			string partNum = ((string)(edvShipDtl.dataView[edvShipDtl.Row]["PartNum"]));
			string lotNum = ((string)(edvShipDtl.dataView[edvShipDtl.Row]["LotNum"]));
			int attributeSetID = ((int)(edvShipDtl.dataView[edvShipDtl.Row]["AttributeSetID"]));

			// Call Adapter method
			Erp.BO.PartBinSearchDataSet dsPartBinSearch = adapterPartBinSearch.GetPartBinByLot(partNum, lotNum, attributeSetID);
	
			//DataTable dt = dsPartBinSearch.Tables["PartBinSearch"];

			string qtyOnHand = dsPartBinSearch.Tables["PartBinSearch"].Rows[0]["QtyOnHand"].ToString();

			// Debugging help
			MessageBox.Show("qtyOnHand= " + qtyOnHand);

			// Cleanup Adapter Reference
			adapterPartBinSearch.Dispose();

		} catch (System.Exception ex)
		{
			ExceptionBox.Show(ex);
		}
	}

	private void ShipDtl_AfterFieldChange(object sender, DataColumnChangeEventArgs args)
	{
		// ** Argument Properties and Uses **
		// args.Row["FieldName"]
		// args.Column, args.ProposedValue, args.Row
		// Add Event Handler Code
	
		MessageBox.Show("AfterFieldChange");
		switch (args.Column.ColumnName)
		{
			case "PartNum":
				CallPartBinSearchAdapterGetPartBinByLotMethod();
				break;
		}
	}


	private void ShipDtl_AfterRowChange(EpiRowChangedArgs args)
	{
		// ** Argument Properties and Uses **
		// args.CurrentView.dataView[args.CurrentRow]["FieldName"]
		// args.LastRow, args.CurrentRow, args.CurrentView
		// Add Event Handler Code
		MessageBox.Show("AfterRowChange");
	}


	private void ShipDtl_BeforeFieldChange(object sender, DataColumnChangeEventArgs args)
	{
		// ** Argument Properties and Uses **
		// args.Row["FieldName"]
		// args.Column, args.ProposedValue, args.Row
		// Add Event Handler Code
	
		MessageBox.Show("BeforeFIeldChange");
		switch (args.Column.ColumnName)
		{
			case "PartNum":
				break;
		}
	}


	private void oTrans_partBinSearchAdapter_AfterAdapterMethod(object sender, AfterAdapterMethodArgs args)
	{
		// ** Argument Properties and Uses **
		// ** args.MethodName **
		// ** Add Event Handler Code **
	
		// ** Use MessageBox to find adapter method name
		// EpiMessageBox.Show(args.MethodName)
	
		MessageBox.Show("Afteradaptermethod");
		switch (args.MethodName)
		{
			case "Update":
				break;
		}
	
	}


	private void oTrans_partBinSearchAdapter_BeforeAdapterMethod(object sender, BeforeAdapterMethodArgs args)
	{
		// ** Argument Properties and Uses **
		// ** args.MethodName **
		// ** Add Event Handler Code **
	
		// ** Use MessageBox to find adapter method name
		// EpiMessageBox.Show(args.MethodName)
	
		MessageBox.Show("BeforeadapterMethod");
		switch (args.MethodName)
		{
			case "Update":
				// DialogResult dialogResult = EpiMessageBox.Show("Cancel Update?", "Cancel", MessageBoxButtons.YesNo);
				// if ((dialogResult == DialogResult.Yes))
				// {
				// 	args.Cancel = true;
				// } else
				// {
				// 	DoSomethingElse();
				// }
				break;
		}
	
	}


	private void edvShipDtl_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
	{
		// ** Argument Properties and Uses **
		// view.dataView[args.Row]["FieldName"]
		// args.Row, args.Column, args.Sender, args.NotifyType
		// NotifyType.Initialize, NotifyType.AddRow, NotifyType.DeleteRow, NotifyType.InitLastView, NotifyType.InitAndResetTreeNodes

		MessageBox.Show("EpiViewNotification");
		if ((args.NotifyType == EpiTransaction.NotifyType.AddRow))
		{
			if ((args.Row > 0))
			{
				CallPartBinSearchAdapterGetPartBinByLotMethod();
			}
		}
	}
}

@DaShMa To be sure I am a lazy coder and do not do more than needed. While I am sure you could get this done with a customization I spent a few minutes to do this in a bpm. You would have to add a field to ShipDtl or just use callContext to display the value. I did ShipComment so I could verify it worked without adding a field.

The reason onHand is a double is because sql returns a double if null

Thank you so much for sharing that. I was finally successful with my customization last night, but I am keeping what you’ve shared in my toolbox for future use, especially knowing that the Script Editor will be deprecated.

Posting my code here (minus template comments) to help others.

public class Script
{
	private EpiBaseAdapter oTrans_partBinSearchAdapter;
	private EpiDataView edvShipDtl;

	public void InitializeCustomCode()
	{
		this.edvShipDtl = ((EpiDataView)(this.oTrans.EpiDataViews["ShipDtl"]));
		this.edvShipDtl.EpiViewNotification += new EpiViewNotification(this.edvShipDtl_EpiViewNotification);
	}

	public void DestroyCustomCode()
	{

		this.edvShipDtl.EpiViewNotification -= new EpiViewNotification(this.edvShipDtl_EpiViewNotification);
		this.edvShipDtl = null;
	}

	private void CallPartBinSearchAdapterGetPartBinByLotMethod()
	{
		try
		{
			// Declare and Initialize EpiDataView Variables
			EpiDataView edvShipDtl = ((EpiDataView)(this.oTrans.EpiDataViews["ShipDtl"]));

			// Check if valid EpiDataView Row(s) are selected
			if ((edvShipDtl.Row < 0))
			{
				return;
			}

			// Declare and create an instance of the Adapter.
			PartBinSearchAdapter adapterPartBinSearch = new PartBinSearchAdapter(this.oTrans);
			adapterPartBinSearch.BOConnect();

			// Declare and Initialize Variables
			string partNum = ((string)(edvShipDtl.dataView[edvShipDtl.Row]["PartNum"]));
			string lotNum = ((string)(edvShipDtl.dataView[edvShipDtl.Row]["LotNum"]));
			int attributeSetID = ((int)(edvShipDtl.dataView[edvShipDtl.Row]["AttributeSetID"]));

			// Call Adapter method
			Erp.BO.PartBinSearchDataSet dsPartBinSearch = adapterPartBinSearch.GetPartBinByLot(partNum, lotNum, attributeSetID);

            // Assign QtyOnHand and populate form field
			string qtyOnHand = dsPartBinSearch.Tables["PartBinSearch"].Rows[0]["QtyOnHand"].ToString();
			epiTextBoxC1.Text = qtyOnHand;

			// Debugging help
			//MessageBox.Show("qtyOnHand= " + qtyOnHand);

			// Cleanup Adapter Reference
			adapterPartBinSearch.Dispose();

		} catch (System.Exception ex)
		{
			ExceptionBox.Show(ex);
		}
	}

	private void edvShipDtl_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
	{
		if ((args.NotifyType == EpiTransaction.NotifyType.Initialize))
		{
	        // Check if the PartNum field has updated
	        if (args.Row > -1 && view.dataView[args.Row]["PartNum"] != null)
	        {
					CallPartBinSearchAdapterGetPartBinByLotMethod();
			}
		}
	}
1 Like