A client requested what appeared to be a simple customization. Here I sit after week and can’t get across the finish line.
They use Projects and Change Orders. They use Inventory Items on their budget lines. They want to see the Unit Price, Ext Price and Total in their change order cost budget tab (PM308000)
I have added these as calculated (PXDecimal) fields in DAC extensions. I have pretty much all working in the grid/detail with all the interaction but I cannot for the life of me figure out a way to total. I’ve tried every combination of PXFormula/PXUnboundFormula I can think of. I’m looking for:
- Am I even on the right track or am I approaching this in a completely backwards way?
- If I’m NOT on the right track, recommendations for a different approach (ie. persistent vs. non-persistent, etc.) If I need to persist, what is the best approach for filling unitprice on existing PMChangeOrderBudget rows?
- If I’m on the right track, what is the magic combination of events/attributes I’m missing to get the total to work?
Here is a screenshot showing I have the UnitPrice, ext populating when data is read, during user interaction (add/update). InventoryID, Qty and Unit Price all have CommitChanges in the ASPX set t true.
Update: I should mention that I looked at the T100 course material for doing total calculations and while I understand I may need to add RowInserted, RowUpdated, RowDeleted to increment/decrement my total - this still leaves me with the problem of how to set the total when the data is being retrieved from the database.
Here is my code:
PMChangeOrderExt class (order total):
public class PMChangeOrderExt : PXCacheExtension<PX.Objects.PM.PMChangeOrder>
{
#region UsrTotalPrice
aPXDecimal(2)]
mPXUIField(DisplayName = "Total Price", Enabled = false)]
public virtual decimal? UsrTotalPrice { get; set; }
public abstract class usrTotalPrice : PX.Data.BQL.BqlDecimal.Field<usrTotalPrice> { }
#endregion
#region ReleaseInProcess
sPXHidden]
public abstract class releaseInProcess : IBqlField { }
ePXBool]
public virtual bool? ReleaseInProcess { get; set; }
#endregion
public static bool IsActive() => true;
}
PMChangeOrderBudgetExt class (detail lines)
public class PMChangeOrderBudgetExt : PXCacheExtension<PX.Objects.PM.PMChangeOrderBudget>
{
#region UsrUnitPrice
iPXDecimal(2)]
mPXUIField(DisplayName="Unit Price", Enabled = false)]
public virtual decimal? UsrUnitPrice { get; set; }
public abstract class usrUnitPrice : PX.Data.BQL.BqlDecimal.Field<usrUnitPrice> { }
#endregion
#region UsrUnitPriceExt
rPXDecimal(2)]
mPXUIField(DisplayName = "Price Ext.", Enabled = false)]
fPXFormula(typeof(Mult<PMChangeOrderBudget.qty, usrUnitPrice>))]
public virtual decimal? UsrUnitPriceExt { get; set; }
public abstract class usrUnitPriceExt : PX.Data.BQL.BqlDecimal.Field<usrUnitPriceExt> { }
#endregion
public static bool IsActive() => true;
}
Graph extension (ChangeOrderEntry_Extension)
public class ChangeOrderEntry_Extension : PXGraphExtension<PX.Objects.PM.ChangeOrderEntry>
{
public PXAction<PMChangeOrder> release;
ePXUIField(DisplayName = "Release")]
ePXProcessButton]
public virtual IEnumerable Release(PXAdapter adapter)
{
PMChangeOrderExt changeOrderExt = PXCache<PMChangeOrder>.GetExtension<PMChangeOrderExt>(Base.Document.Current);
changeOrderExt.ReleaseInProcess = true;
PXGraph.InstanceCreated.AddHandler<ChangeOrderEntry>((graph) =>
{
graph.RowPersisted.AddHandler<PMChangeOrder>((cache, args) =>
{
if (args.TranStatus == PXTranStatus.Completed)
{
changeOrderExt.ReleaseInProcess = false;
}
else if (args.TranStatus == PXTranStatus.Aborted)
{
changeOrderExt.ReleaseInProcess = false;
}
});
});
return Base.Release(adapter);
}
private InventoryItem GetInventoryItem(int? inventoryID)
{
InventoryItem item = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(Base, inventoryID);
return item;
}
#region Event Handlers
protected virtual void _(Events.FieldUpdated<PMChangeOrderCostBudget, PMChangeOrderCostBudget.inventoryID> e, PXFieldUpdated baseMethod)
{
if (e.Row == null) return;
baseMethod(e.Cache, e.Args);
if (e.Row.InventoryID != null && e.Row.InventoryID != PMInventorySelectorAttribute.EmptyInventoryID)
{
InventoryItem item = GetInventoryItem(e.Row.InventoryID);
if (item != null)
{
// set unit price on ext
decimal? unitPrice = Base.RateService.CalculateUnitPrice(e.Cache, e.Row.ProjectID, e.Row.ProjectTaskID, e.Row.InventoryID, e.Row.UOM, e.Row.Qty, Base.Project.Current.StartDate, Base.Project.Current.CuryInfoID);
ext.UsrUnitPrice = unitPrice;
}
}
else
{
ext.UsrUnitPrice = 0.0M;
}
}
protected virtual void _(Events.RowSelecting<PMChangeOrderCostBudget> e, PXRowSelecting baseMethod)
{
if (e.Row == null) return;
PMChangeOrder changeOrder = Base.Document.Current;
PMChangeOrderExt changeOrderExt = Base.Cachesrtypeof(PMChangeOrder)].GetExtension<PMChangeOrderExt>(changeOrder);
if (changeOrderExt.ReleaseInProcess ?? false) return;
InventoryItem item = null;
// This interferes with the release process. Add a flag to PMChangeOrderExt?
using (new PXConnectionScope())
{
if (e.Row.InventoryID != null && e.Row.InventoryID != PMInventorySelectorAttribute.EmptyInventoryID)
{
item = GetInventoryItem(e.Row.InventoryID);
}
}
if (item != null)
{
// set unit price on ext
PMChangeOrderBudgetExt ext = e.Row.GetExtension<PMChangeOrderBudgetExt>();
decimal? unitPrice = Base.RateService.CalculateUnitPrice(e.Cache, e.Row.ProjectID, e.Row.ProjectTaskID, e.Row.InventoryID, e.Row.UOM, e.Row.Qty, Base.Project.Current.StartDate, Base.Project.Current.CuryInfoID);
// Note: this seems to fire PXFormula OK
ext.UsrUnitPrice = unitPrice;
}
baseMethod(e.Cache, e.Args);
}
#endregion
public static bool IsActive() => true;
}