Skip to main content
Question

Automatically Create Sales Order Line Record On RowUpdated Event Handler

  • December 3, 2025
  • 2 replies
  • 21 views

Forum|alt.badge.img+2

Hi I have a working customization on the sales order screen which automatically creates a new line with item “NON-RETURN FEE” if the item class of the previous line is “CYLINDERS”. This then also populates the quantity of the line above to the NON-RETURN FEE line and also copies a UDF value from the given item to this auto generated line. This functionality works but the records gets auto-populated only on pressing the save button

How can I immediately get the “NON-RETURN FEE” line populated? For example straight after the quantity field of the previous line gets updated? I tried a fieldupdated event handler but this did not work either. Please let me know the best approach, thank you!

Example below of what the generation looks like on sales order line:

namespace PX.Objects.SO
{
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
#region Event Handlers

protected void SOLine_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e, PXRowUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);


var row = (SOLine)e.Row;
if (row == null)
{
PXTrace.WriteInformation("Row is null - exiting");
return;
}

PXTrace.WriteInformation($"Processing Line {row.LineNbr}, InventoryID: {row.InventoryID}, Qty: {row.OrderQty}");

// Only process if it's a CYLINDER item
if (IsCylinderItem(row.InventoryID))
{
PXTrace.WriteInformation("Item is a CYLINDER");

// Get the Core Non-Return Fee
decimal? coreFee = GetCoreNonReturnFee(row.InventoryID);
PXTrace.WriteInformation($"Core Fee retrieved: {coreFee}");

if (coreFee != null && coreFee > 0)
{
PXTrace.WriteInformation($"Core Fee is valid ({coreFee}) - calling HandleNonReturnFeeLine");
// Find or create the NON-RETURN FEE line
HandleNonReturnFeeLine(row, coreFee.Value);
}
else
{
if (coreFee == null)
PXTrace.WriteWarning("Core Fee is NULL");
else if (coreFee <= 0)
PXTrace.WriteWarning($"Core Fee is {coreFee} (must be > 0)");
}
}
else
{
PXTrace.WriteInformation("Item is NOT a CYLINDER - skipping");
}


}

private void HandleNonReturnFeeLine(SOLine cylinderLine, decimal coreFee)
{
PXTrace.WriteInformation("=== HandleNonReturnFeeLine START ===");
PXTrace.WriteInformation($"Cylinder Line: {cylinderLine.LineNbr}, Qty: {cylinderLine.OrderQty}, Fee: {coreFee}");

// Get NON-RETURN FEE item
PXTrace.WriteInformation("Looking for NON-RETURN FEE inventory item...");
InventoryItem feeItem = PXSelect<InventoryItem,
Where<InventoryItem.inventoryCD, Equal<Required<InventoryItem.inventoryCD>>>>
.Select(Base, "NON-RETURN FEE");

if (feeItem == null)
{
PXTrace.WriteError("NON-RETURN FEE item not found in inventory!");
PXTrace.WriteError("Please ensure 'NON-RETURN FEE' exists as a Non-Stock item");
return;
}

PXTrace.WriteInformation($"NON-RETURN FEE item found: ID={feeItem.InventoryID}, CD={feeItem.InventoryCD}");

// Check if NON-RETURN FEE line already exists for this cylinder line
SOLine existingFeeLine = null;
int lineCount = 0;

PXTrace.WriteInformation($"Searching for existing NON-RETURN FEE line after line {cylinderLine.LineNbr}...");

foreach (SOLine line in Base.Transactions.Select())
{
lineCount++;
PXTrace.WriteInformation($"Checking Line {line.LineNbr}: InventoryID={line.InventoryID}, Qty={line.OrderQty}");

if (line.LineNbr > cylinderLine.LineNbr &&
line.InventoryID == feeItem.InventoryID)
{
existingFeeLine = line;
PXTrace.WriteInformation($"Found existing NON-RETURN FEE line at Line {line.LineNbr}");
break;
}
}

PXTrace.WriteInformation($"Total lines checked: {lineCount}");

if (existingFeeLine != null)
{
// UPDATE existing line
PXTrace.WriteInformation($"Updating existing NON-RETURN FEE line...");
PXTrace.WriteInformation($"Old Qty: {existingFeeLine.OrderQty} -> New Qty: {cylinderLine.OrderQty}");
PXTrace.WriteInformation($"Old Price: {existingFeeLine.CuryUnitPrice} -> New Price: {coreFee}");

existingFeeLine.OrderQty = cylinderLine.OrderQty;
existingFeeLine.CuryUnitPrice = coreFee;
Base.Transactions.Update(existingFeeLine);

PXTrace.WriteInformation("Line updated successfully");
}
else
{
// CREATE new line
PXTrace.WriteInformation("No existing NON-RETURN FEE line found - creating new one");

try
{
SOLine newLine = new SOLine();
PXTrace.WriteInformation("New SOLine object created");

newLine.InventoryID = feeItem.InventoryID;
newLine.OrderQty = cylinderLine.OrderQty;
newLine.CuryUnitPrice = coreFee;
newLine.ManualPrice = true;

PXTrace.WriteInformation("New line details:");
PXTrace.WriteInformation($" InventoryID: {newLine.InventoryID}");
PXTrace.WriteInformation($" OrderQty: {newLine.OrderQty}");
PXTrace.WriteInformation($" CuryUnitPrice: {newLine.CuryUnitPrice}");
PXTrace.WriteInformation($" ManualPrice: {newLine.ManualPrice}");

// Insert after the cylinder line
Base.Transactions.Insert(newLine);
PXTrace.WriteInformation("New NON-RETURN FEE line inserted");
}
catch (System.Exception ex)
{
PXTrace.WriteError($"Error inserting NON-RETURN FEE line: {ex.Message}");
PXTrace.WriteError($"Stack Trace: {ex.StackTrace}");
}
}

PXTrace.WriteInformation("=== HandleNonReturnFeeLine END ===\n");
}

private bool IsCylinderItem(int? inventoryID)
{
PXTrace.WriteInformation("=== IsCylinderItem START ===");
PXTrace.WriteInformation($"Checking InventoryID: {inventoryID}");

if (inventoryID == null)
{
PXTrace.WriteInformation("InventoryID is null -> FALSE");
return false;
}

PXTrace.WriteInformation("Looking up InventoryItem...");
InventoryItem item = PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>
.Select(Base, inventoryID);

if (item == null)
{
PXTrace.WriteError($"InventoryItem not found for ID {inventoryID} -> FALSE");
return false;
}

PXTrace.WriteInformation($"InventoryItem found: {item.InventoryCD}");
PXTrace.WriteInformation($"ItemClassID: {item.ItemClassID}");

PXTrace.WriteInformation("Looking up INItemClass...");
INItemClass itemClass = PXSelect<INItemClass,
Where<INItemClass.itemClassID, Equal<Required<INItemClass.itemClassID>>>>
.Select(Base, item.ItemClassID);

if (itemClass == null)
{
PXTrace.WriteError($"INItemClass not found for ID {item.ItemClassID} -> FALSE");
return false;
}

string itemClassCD = itemClass.ItemClassCD?.Trim().ToUpper() ?? "";
bool isCylinder = itemClassCD == "CYLINDERS";

PXTrace.WriteInformation($"ItemClass found: {itemClass.ItemClassCD}");
PXTrace.WriteInformation($"ItemClassCD (trimmed/uppercase): '{itemClassCD}'");
PXTrace.WriteInformation($"Is 'CYLINDERS'? {isCylinder}");

PXTrace.WriteInformation("=== IsCylinderItem END ===");
return isCylinder;
}

private decimal? GetCoreNonReturnFee(int? inventoryID)
{
PXTrace.WriteInformation("=== GetCoreNonReturnFee START ===");
PXTrace.WriteInformation($"InventoryID: {inventoryID}");

if (inventoryID == null)
{
PXTrace.WriteError("InventoryID is null -> NULL");
return null;
}

try
{
PXTrace.WriteInformation("Querying InventoryItemCurySettings...");
InventoryItemCurySettings settings = PXSelect<InventoryItemCurySettings,
Where<InventoryItemCurySettings.inventoryID, Equal<Required<InventoryItemCurySettings.inventoryID>>>>
.Select(Base, inventoryID);

if (settings == null)
{
PXTrace.WriteError($"No InventoryItemCurySettings record found for InventoryID {inventoryID}");
PXTrace.WriteError("Check if the item has currency settings configured");
return null;
}

PXTrace.WriteInformation($"InventoryItemCurySettings record found");

PXTrace.WriteInformation("Getting extension InventoryItemCurySettingsExt...");
var settingsExt = settings.GetExtension<InventoryItemCurySettingsExt>();

if (settingsExt == null)
{
PXTrace.WriteError("InventoryItemCurySettingsExt extension not found");
PXTrace.WriteError("Check if the custom field is properly deployed");
return null;
}

decimal? fee = settingsExt.UsrMAINCoreNonReturnFee;
PXTrace.WriteInformation($"Custom field UsrMAINCoreNonReturnFee value: {fee}");

PXTrace.WriteInformation("=== GetCoreNonReturnFee END ===");
return fee;
}
catch (System.Exception ex)
{
PXTrace.WriteError($"ERROR in GetCoreNonReturnFee: {ex.Message}");
PXTrace.WriteError($"Stack Trace: {ex.StackTrace}");
return null;
}
}

 

2 replies

Forum|alt.badge.img+2

Hello ​@TharidhiP You can try event signature like this :

protected void _(Events.RowUpdated<SOLine> e)

probably your old handler with (PXRowUpdated InvokeBaseHandler) only works when overriding base graph events, not DAC-level events.

 

I hope it helps.


Forum|alt.badge.img+8
  • Captain II
  • December 3, 2025

Hello ​@TharidhiP You can try event signature like this :

protected void _(Events.RowUpdated<SOLine> e)

probably your old handler with (PXRowUpdated InvokeBaseHandler) only works when overriding base graph events, not DAC-level events.

 

I hope it helps.

@Abhishek Niikam For extending existing functionality, you should invoke the base method to ensure that you do not make an unwanted change, that is just a strongly typed handler and overrides all existing functionality in the original RowUpdated handler. To preserve existing functionality should declare it like this:

protected void _(Events.RowUpdated<SOLine> e, PXRowUpdated b)

{

b?.Invoke(e.Cache, e.Args);

//Rest of your code here

}

 

@TharidhiP 

You could try RowInserted and use an if statement to determine whether the InventoryID is ‘Non-Return Fee’ (you will have to use the DB identity value rather than the CD value displayed) then add in the value here, based on the last used InventoryID.

A way to track the last InventoryID is to add a custom field to SOOrder which holds the value of the newest SOLine.InventoryID, and update this in the SOLine RowInserted handler.

 

Hope this helps!