PXFormula attribute isn't calculating correctl

2019-07-27 10:08发布

问题:

I have a customization to the SalesOrder (SO301000) screen as follows:

  • I've created a User field on the header section (SOOrder) called 'Total Revenue' (SOOrderExt.UsrTotalRevenue):

  • I've added a PXFormula attribute to the Ext. Price field in the grid (SOLine.CuryLineAmt) as follows:

This calculates correctly, until one tries to copy an order to create another order. When this happens, the value is doubled - as if it's summing the original order PLUS the copied order. At that point, even if you delete the line item(s) in the copied order, it maintains the summary value in the header. I have no idea where it would be getting this value. Have I missed a step? Is there some type of refresh that I need to add somewhere?

回答1:

Per our discussion with Peter earlier today, originally there were 2 separate scenarios causing custom aggregate field value to double:

  1. When Copy-Paste buttons are used from the Clipboard menu (addressed in the answer above):

  2. The Copy Order action form the Actions drop-down button:

By exploring source code of the SOOrderentry.CopyOrderProc method, you should notice a bunch of fields with assigned 0 value during the Copy Order function (among those are all aggregated fields originally found on the Sales Orders screen):

    public class SOOrderEntry : PXGraph<SOOrderEntry, SOOrder>, PXImportAttribute.IPXPrepareItems
    {
        ...

        public virtual void CopyOrderProc(SOOrder order, CopyParamFilter copyFilter)
        {
            ...
            foreach (PXResult<SOOrder, CurrencyInfo> res in PXSelectJoin<SOOrder, InnerJoin<CurrencyInfo, On<CurrencyInfo.curyInfoID, Equal<SOOrder.curyInfoID>>>, Where<SOOrder.orderType, Equal<Required<SOOrder.orderType>>, And<SOOrder.orderNbr, Equal<Required<SOOrder.orderNbr>>>>>.Select(this, order.OrderType, order.OrderNbr))
            {
                ...
                SOOrder copyorder = PXCache<SOOrder>.CreateCopy(quoteorder);
                ...
                copyorder.ShipmentCntr = 0;
                copyorder.OpenShipmentCntr = 0;
                copyorder.OpenLineCntr = 0;
                copyorder.ReleasedCntr = 0;
                copyorder.BilledCntr = 0;
                copyorder.OrderQty = 0m;
                copyorder.OrderWeight = 0m;
                copyorder.OrderVolume = 0m;
                copyorder.OpenOrderQty = 0m;
                copyorder.UnbilledOrderQty = 0m;
                copyorder.CuryInfoID = neworder.CuryInfoID;
                copyorder.Status = neworder.Status;
                copyorder.Hold = neworder.Hold;
                copyorder.Completed = neworder.Completed;
                copyorder.Cancelled = neworder.Cancelled;
                copyorder.InclCustOpenOrders = neworder.InclCustOpenOrders;
                copyorder.OrderDate = neworder.OrderDate;
                copyorder.RequestDate = neworder.RequestDate;
                copyorder.ShipDate = neworder.ShipDate;
                copyorder.CuryMiscTot = 0m;
                copyorder.CuryUnbilledMiscTot = 0m;
                copyorder.CuryLineTotal = 0m;
                copyorder.CuryOpenLineTotal = 0m;
                copyorder.CuryUnbilledLineTotal = 0m;
                copyorder.CuryVatExemptTotal = 0m;
                copyorder.CuryVatTaxableTotal = 0m;
                copyorder.CuryTaxTotal = 0m;
                copyorder.CuryOrderTotal = 0m;
                copyorder.CuryOpenOrderTotal = 0m;
                copyorder.CuryOpenTaxTotal = 0m;
                copyorder.CuryUnbilledOrderTotal = 0m;
                copyorder.CuryUnbilledTaxTotal = 0m;
                ...
            }
            ...
        }
        ...
    }

To resolve the issue with doubled custom aggregated field value in the Copy Order action, you should simply override the CopyOrderProc method in the SOOrderEntry BLC extension and inside the method subscribe to RowSelecting handler to assign 0 value to custom aggregated fields following the base product flow:

public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
    public delegate void CopyOrderProcDel(SOOrder order, CopyParamFilter copyFilter);

    [PXOverride]
    public void CopyOrderProc(SOOrder order, CopyParamFilter copyFilter, CopyOrderProcDel del)
    {
        Base.RowSelecting.AddHandler<SOOrder>((sender, e) =>
        {
            if (e.Row == null) return;

            SOOrderExt orderExt = sender.GetExtension<SOOrderExt>(e.Row);
            orderExt.UsrTotalRevenue = 0m;
        });
        del(order, copyFilter);
    }
}


回答2:

Solution suggested by Brendan will definitely work, however it doesn’t seem to be bullet-proof agains possible changes in base SOOrderEntry BLC and can possible increase maintenance cost in future.

On the Sales Orders screen, none of the original aggregated values get duplicated thanks to SOOrder_RowSelected handler implemented in the BLC - it makes sure to always set Enabled property of the PXUIFieldAttribute to False for all base aggregated fields. This is the only additional required step because of PXUIFieldAttribute.SetEnabled(cache, doc, true); method invoked in the middle of the SOOrder_RowSelected handler:

public class SOOrderEntry : PXGraph<SOOrderEntry, SOOrder>, PXImportAttribute.IPXPrepareItems
{
    ...

    protected virtual void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
    {
        SOOrder doc = e.Row as SOOrder;

        if (doc == null)
        {
            return;
        }

        ...

        bool allowAllocation = soordertype.Current != null && soordertype.Current.RequireAllocation != true
            || PXAccess.FeatureInstalled<FeaturesSet.warehouseLocation>()
            || PXAccess.FeatureInstalled<FeaturesSet.lotSerialTracking>()
            || PXAccess.FeatureInstalled<FeaturesSet.subItem>()
            || PXAccess.FeatureInstalled<FeaturesSet.replenishment>()
            || PXAccess.FeatureInstalled<FeaturesSet.sOToPOLink>();

        if (doc == null || doc.Completed == true || doc.Cancelled == true || !allowAllocation)
        {
            PXUIFieldAttribute.SetEnabled(cache, doc, false);
            cache.AllowDelete = false;
            cache.AllowUpdate = allowAllocation;

            ...
        }
        else
        {
            ...

            PXUIFieldAttribute.SetEnabled(cache, doc, true);
            PXUIFieldAttribute.SetEnabled<SOOrder.refTranExtNbr>(cache, doc, (doc.CreatePMInstance == true || doc.PMInstanceID.HasValue) && isCCPayment && isCashReturn && !isCCRefunded);
            PXUIFieldAttribute.SetEnabled<SOOrder.status>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.orderQty>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.orderWeight>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.orderVolume>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.packageWeight>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyOrderTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyUnpaidBalance>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyLineTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyMiscTot>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyFreightCost>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.freightCostIsValid>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyFreightAmt>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyTaxTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.openOrderQty>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyOpenOrderTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyOpenLineTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyOpenTaxTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.unbilledOrderQty>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyUnbilledOrderTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyUnbilledLineTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyUnbilledTaxTotal>(cache, doc, false);

            PXUIFieldAttribute.SetEnabled<SOOrder.curyPaymentTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyID>(cache, doc, curyenabled);
            PXUIFieldAttribute.SetEnabled<SOOrder.preAuthTranNumber>(cache, doc, enableCCAuthEntering);
            PXUIFieldAttribute.SetEnabled<SOOrder.cCPaymentStateDescr>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.cCAuthExpirationDate>(cache, doc, enableCCAuthEntering && String.IsNullOrEmpty(doc.PreAuthTranNumber) == false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyCCPreAuthAmount>(cache, doc, enableCCAuthEntering && String.IsNullOrEmpty(doc.PreAuthTranNumber) == false);
            PXUIFieldAttribute.SetEnabled<SOOrder.pCResponseReasonText>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.captureTranNumber>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyCCCapturedAmt>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.origOrderType>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.origOrderNbr>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyVatExemptTotal>(cache, doc, false);
            PXUIFieldAttribute.SetEnabled<SOOrder.curyVatTaxableTotal>(cache, doc, false);

            ...
        }

        ...
    }

    ...
}

With that said, to resolve the issue with doubled custom aggregated field value, you should follow 2 steps listed below. Those are necessary to exclude custom aggregated field from template used when pasting the document.

  1. Implement RowSelected handler for the SOOrder DAC in the SOOrderEntry BLC extension to also set Enabled property of the PXUIFieldAttribute to False for your custom field:

    public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
    {
        public void SOOrder_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
        {
            var doc = e.Row as SOOrder;
            if (doc == null) return;
    
            if (doc != null && doc.Completed != true && doc.Cancelled != true && sender.AllowUpdate)
            {
                PXUIFieldAttribute.SetEnabled<SOOrderExt.usrTotalRevenue>(sender, doc, false);
            }
        }
    }
    
  2. For custom aggregated field input control, set Enabled property to False in Aspx page:

    <px:PXNumberEdit DataField=“UsrTotalRevenue” Enabled="False" runat="server" ID="CstPXNumberEdit19" />
    

Regarding concerns shared by @Dmitry Kasatsky: on Friday evening Peter sent me his customization via email, which I used to verify the 2-step approach described above. Based on reported scenario, there were NO issues identified with the PXFormulaAttribute and custom fields. If you concern about the PXFormulaAttribute and Copy-Paste feature, set enabled property to True (equal to the default property value) for base OrderQty field input control:

<px:PXNumberEdit ID="edOrderQty" runat="server" DataField="OrderQty" Enabled="True" />

and implement RowSelected handler for the SOOrder DAC in the SOOrderEntry BLC extension to undo PXUIFieldAttribute.SetEnabled<SOOrder.orderQty>(cache, doc, false); command from the SOOrderEntry BLC:

protected virtual void SOOrder_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
    SOOrder soorder = (SOOrder)e.Row;
    if (soorder == null) return;

    if (soorder != null && soorder.Completed != true && soorder.Cancelled != true && sender.AllowUpdate)
    {
        PXUIFieldAttribute.SetEnabled<SOOrder.orderQty>(sender, soorder, true);
    }
}

This will enable Ordered Qty. field and eventually result in duplicated value in copy-pasted documents:

For future reference, to check if document template includes custom field:

  1. Restart IIS or recycle app pool to clear previously cached Acumatica ScreenInfo

  2. Select Save as Template… option from Clipboard menu and verify if custom field exists in generated document template:

Please note: fields with Null values will always be excluded from document template



回答3:

If you need to exclude a field value from being copied you can use PXCopyPasteHiddenFieldsAttribute on your data view.

This is the Current Document view on the sales order entry. you will need to extend the graph, include the view, and add your field to the list of excluded fields. Something similar to what i have below...

[PXCopyPasteHiddenFields(typeof(SOOrder.cancelled), typeof(SOOrder.preAuthTranNumber), typeof(SOOrder.ownerID), typeof(SOOrder.workgroupID), typeof(SOOrder.UsrTotalRevenue))]
public PXSelect<SOOrder, Where<SOOrder.orderType, Equal<Current<SOOrder.orderType>>, And<SOOrder.orderNbr, Equal<Current<SOOrder.orderNbr>>>>> CurrentDocument;

The way the sumcalc formula works is it adds the difference, not the sum. So making changes to a value that is already out of wack will not "fix" it self. For example if you add a sales line with a value of 11, it will just add 11 vs trying to sum up all sales lines. Same if you delete, it will just subtract 11. This is why you would not be able to delete the lines to get back to zero in your user field.

Edit: FYI for others, PXCopyPasteHiddenFieldsAttribute can be used on a DAC directly, not just a graph data view property.

Example:

[PXCopyPasteHiddenFields(
    typeof(MyBaseDacExtension.someField1), 
    typeof(MyBaseDacExtension.someField2)))]
[PXTable(typeof(MyBaseDac.keyField1), IsOptional = true)]
public class MyBaseDacExtension : PXCacheExtension<MyBaseDac>
{
...
}


标签: acumatica