Solved

PM Change Order Customization - Thought this would be easy :(


Userlevel 3
Badge +1

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:

  1. Am I even on the right track or am I approaching this in a completely backwards way?
  2. 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?
  3. 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
[PXDecimal(2)]
[PXUIField(DisplayName = "Total Price", Enabled = false)]
public virtual decimal? UsrTotalPrice { get; set; }
public abstract class usrTotalPrice : PX.Data.BQL.BqlDecimal.Field<usrTotalPrice> { }
#endregion

#region ReleaseInProcess

[PXHidden]
public abstract class releaseInProcess : IBqlField { }
[PXBool]
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
[PXDecimal(2)]
[PXUIField(DisplayName="Unit Price", Enabled = false)]
public virtual decimal? UsrUnitPrice { get; set; }
public abstract class usrUnitPrice : PX.Data.BQL.BqlDecimal.Field<usrUnitPrice> { }
#endregion

#region UsrUnitPriceExt
[PXDecimal(2)]
[PXUIField(DisplayName = "Price Ext.", Enabled = false)]
[PXFormula(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;
[PXUIField(DisplayName = "Release")]
[PXProcessButton]
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.Caches[typeof(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;
}

 

icon

Best answer by rjean09 16 May 2022, 20:42

View original

2 replies

Userlevel 7
Badge

Hi @rjean09  - Thank you for sharing your solution with the community!

Userlevel 3
Badge +1

I think I finally have this working.  Below are the main points:

Make sure CommitChanges = true on UsrUnitPriceExt  (thanks to Troy Var suggesting to check all my commit settings)

Added PXUnboundFormula to parent DAC extension: UsrTotalPrice:

        #region UsrTotalPrice
[PXDecimal(2)]
[PXUIField(DisplayName = "Total Price", Enabled = false)]
[PXUnboundFormula(null, typeof(SumCalc<PMChangeOrderBudgetExt.usrUnitPriceExt>))]
public virtual decimal? UsrTotalPrice { get; set; }
public abstract class usrTotalPrice : PX.Data.BQL.BqlDecimal.Field<usrTotalPrice> { }
#endregion

Add PXFormula AND PXUnboundFormula to child DAC extension: UsrUnitPriceExt 

        #region UsrUnitPriceExt
[PXDecimal(2)]
[PXUIField(DisplayName = "Price Ext.", Enabled = false)]
[PXFormula(typeof(Mult<PMChangeOrderBudget.qty, usrUnitPrice>))]
[PXUnboundFormula(typeof(Mult<PMChangeOrderBudget.qty, usrUnitPrice>), typeof(SumCalc<PMChangeOrderExt.usrTotalPrice>))]
public virtual decimal? UsrUnitPriceExt { get; set; }
public abstract class usrUnitPriceExt : PX.Data.BQL.BqlDecimal.Field<usrUnitPriceExt> { }
#endregion

Add FieldSelecting event handler to PMChangeOrderExt.usrTotalPrice:

        public virtual void _(Events.FieldSelecting<PMChangeOrderExt.usrTotalPrice> e, PXFieldSelecting baseMethod)
{
e.ReturnValue = CalculateUnitPriceTotal();
}

public virtual decimal? CalculateUnitPriceTotal()
{
decimal? total = 0M;

PMChangeOrder order = (PMChangeOrder)Base.Document.Current;
if (order == null) return 0.0M;
PMChangeOrderExt orderExt = Base.Caches[typeof(PMChangeOrder)].GetExtension<PMChangeOrderExt>(order);
if (orderExt.ReleaseInProcess ?? false) return 0.0M;

foreach (PMChangeOrderCostBudget line in Base.CostBudget.Select())
{
PMChangeOrderBudgetExt ext = PXCache<PMChangeOrderBudget>.GetExtension<PMChangeOrderBudgetExt>(line);
total += (ext.UsrUnitPriceExt != null ? ext.UsrUnitPriceExt : 0M);
}
return total;

}

Now total is getting set when a change order is read from the database and also recalculated on line insert/update/delete, inventoryID and Qty changes, etc.

It was just a matter of getting all the right pieces of the puzzle (Acumatica picked an appropriate icon for the Customization menu).  Not sure if this is the ideal solution but it’s finally working.  Any feedback appreciated.  

Got ideas for things from documentation, StackOverflow, peer help but getting all the pieces working together was challenging.  Hope this helps someone else trying to do something  similar.

Reply


About Acumatica ERP system
Acumatica Cloud ERP provides the best business management solution for transforming your company to thrive in the new digital economy. Built on a future-proof platform with open architecture for rapid integrations, scalability, and ease of use, Acumatica delivers unparalleled value to small and midmarket organizations. Connected Business. Delivered.
© 2008 — 2024  Acumatica, Inc. All rights reserved