0
votes

Unable to setup unit price updating gross profit percent and back.

I have successfully created a new field for the sales order line to display gross profit percent, but now the user wants a way to adjust the percentage and update the unit price as well. All my attempts so far have apparently caused an infinite loop.

Attribute added to [UnitPrice] field in the DAC:

[PXFormula(typeof(Switch<Case<Where<SOLineExt.usrGPPercent, Equal<decimal0>>, decimal0>, Mult<SOLineExt.usrGPPercent, SOLine.curyUnitCost>>))]

[UsrGPPercent] field attributes:

[PXDBDecimal]
[PXUIField(DisplayName="GPPercent", Visible = false)]
[PXFormula(typeof(Switch<Case<Where<SOLine.curyLineAmt, Equal<decimal0>>, decimal0>, Div<SOLineExt.usrTotalProfit, SOLine.curyLineAmt>>))]
[PXDefault(TypeCode.Decimal, "0.0")]

[UsrGPPct] Field attributes:

[PXUIField(DisplayName = "GP %", Enabled = true)] 
[PXFormula(typeof(Mult<SOLineExt.usrGPPercent, decimal100>))]
[PXDefault(TypeCode.Decimal, "0.0")]

The above all works perfectly and update the GP% as expected.

Attempt #1, added the following attribute to [UsrGCP] (I realize the math is incomplete, just trying to proof-of-concept at this point).

[PXFormula(typeof(Switch<Case<Where<SOLine.curyLineAmt, Equal<decimal0>>, decimal0>, Div<SOLineExt.usrTotalProfit, SOLine.curyLineAmt>>))]

Attempt #2: FieldUpdated handler:

protected void SOLine_UsrGPPct_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
  if(InvokeBaseHandler != null)
    InvokeBaseHandler(cache, e);
  var row = (SOLine)e.Row;

  PX.Objects.SO.SOLineExt soLineExt = PXCache<SOLine>.GetExtension<PX.Objects.SO.SOLineExt>(row);

  if (row.OrderType == "SO")
  {
      if (soLineExt.UsrGPPct > 0)
      {
        row.CuryUnitPrice = row.CuryUnitCost + (soLineExt.UsrGPPct * row.CuryUnitCost);
      }
  }

}

Both methods apparently resulted in an infinite loop (guessing since the debugger was triggered and IIS had to be reset). The goal is to only update once when either field is updated by the user and ignore updates made by the system. Any ideas?

Based on HB_Acumatica's response I updated the code above to:

protected void SOLine_UsrGPPct_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
  if(InvokeBaseHandler != null)
    InvokeBaseHandler(cache, e);
  var row = (SOLine)e.Row;

  PX.Objects.SO.SOLineExt soLineExt = PXCache<SOLine>.GetExtension<PX.Objects.SO.SOLineExt>(row);

  if (e.ExternalCall)
  {
      if (soLineExt.UsrGPPct > 0)
      {
        if (row.OrderType == "SO")
        {
          decimal NewUnitPrice;
          decimal GPPercent = soLineExt.UsrGPPct ?? 0;
          decimal UnitCost = row.CuryUnitCost ?? 0;
          NewUnitPrice = UnitCost + ((GPPercent  / (decimal)100) * UnitCost);
          row.CuryUnitPrice = NewUnitPrice;
          row.UnitPrice = NewUnitPrice;
        }
      }
  }
}

This almost works. Nothing happens on the screen, but when saved the unit price does indeed update and save properly. Almost there, is there some sort of update step I'm missing?

2

2 Answers

3
votes

This is usually fixed with e.ExternalCall in the field updated event handler to prevent recursion:

protected void SOLine_UsrGPPct_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
   if (e.ExternalCall)
   {
        // Make sure it gets in here only once when value change in UI
   }
}

ExternalCall Property (PXRowUpdatingEventArgs)

Gets the value indicating, if it equals true, that the update of the DAC object has been initiated from the UI or through the Web Service API


Besides that, the other case is when two fields attributes are inter-dependent and need each other to compute their value. You can't have field 1 with a formula that depends on field 2 along with field 1 that also has a formula referencing field 2.

With that kind of setup, when asking for field 1 it will first try to evaluate the dependent field 2 in the field 1 formula and since field 2 also depends on field 1 it will start an infinite recursion loop.

There's no way out of this one that I know, you would have to re-design the DAC fields so none are mutually inter-dependent. Or just leave out the DAC attributes and perform the computation in RowSelected events (this has some disadvantages because it requires graph to work).


EDIT:

About fields values not changing in UI. This might happen when some fields need some events to be triggered when their value change (generally FieldUpdated event).

Instead of assigning values like this which doesn't raise any event:

row.Field = value;

You can use SetValueExt instead which raises events like FieldUpdated:

sender.SetValueExt<DAC.field>(row, value);

Sender needs to be cache of DAC type, in your case it should be otherwise it's:

Base.Caches[typeof(DAC)].SetValueExt<DAC.field>(row, value);

Note that a field that has a PXFormula attribute will get it's value re-computed at some point so you shouldn't manually set the value of such fields.

If you still have such an attribute on UnitPrice field you shouldn't manually update the field value:

[PXFormula(typeof(Switch<Case<Where<SOLineExt.usrGPPercent, Equal<decimal0>>, decimal0>, Mult<SOLineExt.usrGPPercent, SOLine.curyUnitCost>>))]

Maybe all you need to do is force the formula to be refreshed when UsrGPPercent changes. If that's the case, instead of setting UnitPrice value try using RaiseFieldDefaulting method to have the formula recalculated: object dummy;

sender.RaiseFieldDefaulting<SOLine.unitPrice>(e.Row, out dummy);
1
votes

Thanks so much to HB_ACUMATICA!

Final code is posted below for anyone trying to do something similar. Also cleaned up.

Two fields added to the DAC (and SalesOrder screen):

UsrGPPercent

Attributes:

[PXDBDecimal()]
[PXUIField(DisplayName = "GP %", Enabled = true, Visible = true)] 
[PXFormula(typeof(Mult<Switch<Case<Where<SOLine.curyExtCost, Equal<decimal0>>, decimal0>, Div<SOLineExt.usrTotalProfit, SOLine.curyExtCost>>, decimal100>))]
[PXDefault(TypeCode.Decimal, "0.0")]

Code Extension:

    #region Event Handlers
    protected void SOLine_UsrGPPercent_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
    {
      if(InvokeBaseHandler != null)
        InvokeBaseHandler(cache, e);
      var row = (SOLine)e.Row;

      PX.Objects.SO.SOLineExt soLineExt = PXCache<SOLine>.GetExtension<PX.Objects.SO.SOLineExt>(row);

      if (e.ExternalCall)
      {
          if (soLineExt.UsrGPPercent > 0)
          {
            if (row.OrderType == "SO")
            {
              decimal NewUnitPrice;
              decimal GPPercent = soLineExt.UsrGPPercent ?? 0;
              decimal UnitCost = row.CuryUnitCost ?? 0;
              decimal QtyOrdered = row.OrderQty ?? 0;
              NewUnitPrice = (UnitCost + ((GPPercent  / (decimal)100) * UnitCost));

              soLineExt.UsrTotalProfit = (NewUnitPrice * QtyOrdered) - (UnitCost * QtyOrdered);
              row.CuryUnitPrice = NewUnitPrice;
              row.UnitPrice = NewUnitPrice;
            }
          }
      }
    }
    #endregion

UsrTotalProfit

Attributes:

[PXDBCurrency(typeof(SOLine.curyInfoID), typeof(SOLineExt.usrTotalProfit))] 
[PXUIField(DisplayName = "Total Profit", Enabled = false)]               
[PXFormula(typeof(Sub<SOLine.curyLineAmt, SOLine.curyExtCost>))]    
[PXDefault(TypeCode.Decimal, "0.0")]