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;
}
}