Detecting when an Invoice exceeds the PO amount - acumatica

I have created a new APTran field called UsrPOTranAmt. I would like to populate it with the PO Line amount when the user either adds a PO or a PO Line to the invoice. Later I can compare the line transaction amount to the UsrPOTranAmt and determine if the user is paying more that the PO amount.
My initial thought was to detect when the PONbr field (PO Type, PO Number, and PO Line Number fields) was updated and then set the UsrPOTranAmt field to the same value as the CuryLineAmt or Amount field that was updated by the PO or PO Line selection. I have tried to detect the field_updated event with a warning message for PONbr and POLineNbr but neither approach has worked.
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
protected void APTran_PONbr_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (APTran)e.Row;
if (row.PONbr != null)
{
cache.RaiseExceptionHanding<APTran.PONbr>(row, row.PONbr,
new PXSetPropertyException("PO Line Number Changed", PXErrorLevel.Warning));
}
}
}
I do not receive the warning message so I don't expect that I can set the UsrPOTranAmt either with this event.

If you have not changed the default behavior of the Grid column of CommitChanges to True, it will not cause the postback and call the event. Give that a try.

The commitChanges is definitely required on a filed updated event, but here is the solution from ACM:
public class APTranExt : PXCacheExtension<PX.Objects.AP.APTran>
{
#region UsrPOTranAmt
[PXDecimal]
[PXUIField(DisplayName = "POTranAmt", Enabled = false)]
[PXDBScalar(typeof(Search<POLine.curyLineAmt,
Where<POLine.orderType, Equal<APTran.pOOrderType>,
And<POLine.orderNbr, Equal<APTran.pONbr>,
And<POLine.lineNbr, Equal<APTran.pOLineNbr>>>>>))]
public virtual Decimal? UsrPOTranAmt { get; set; }
public abstract class usrPOTranAmt : PX.Data.BQL.BqlDecimal.Field<usrPOTranAmt> { }
#endregion
}
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
#region Event Handlers
protected void APTran_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e, PXRowUpdated InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (APTran)e.Row;
if(row != null && row.POOrderType != null && row.PONbr != null && row.POLineNbr != null)
{
POLine poline = PXSelect<POLine,
Where<POLine.orderType, Equal<Required<POLine.orderType>>,
And<POLine.orderNbr, Equal<Required<POLine.orderNbr>>,
And<POLine.lineNbr, Equal<Required<POLine.lineNbr>>>>>>.Select(Base, row.POOrderType, row.PONbr, row.POLineNbr);
if(poline != null)
{
cache.SetValue(row, "UsrPOTranAmt", poline.CuryLineAmt);
}
}
}
#endregion
}

Related

Inventory ID that combines fields

Anyone know if it's possible to combine a field like Item Class (we use a 3 letter code for each one) with a sequential number to get a unique Inventory ID?
e.g. we need all hardware to be HWR-00988 but all Shipping Supplies to be SUP-00989 and so on.
My workaround right now is to create an Attribute called ITEMCLASS and basically mirror the item class codes (HWR, SUP, etc) then add attributes to each Item Class and use the Inventory ID Segment Settings to make it look like it's pulling the actual Item Class.
Seems like this should exist though? I know the data exists in INITEMMENU already.
If you mean having a unique InventoryCD key that switches between constants HWR-XXXXX and SUP-XXXXX based on custom business rules (like appending Item Class); I don't think that's possible out-of-box without programming.
Usually this is accomplished with a custom attribute. Here's an example for [CuryID] attribute which computes key value based on arbitrary logic.
Usage, decorate key fields with custom attribute [CuryID]:
[PXDBString(5, IsUnicode = true)]
[PXDefault(typeof(AccessInfo.baseCuryID))]
[PXUIField(DisplayName = "Currency", ErrorHandling = PXErrorHandling.Never)]
[PXSelector(typeof(Currency.curyID))]
[CuryID]
public virtual String CuryID { get; set; }
For your scenario you would replace InventoryRaw with your custom attribute:
[PXDefault]
[InventoryRaw(IsKey = true, DisplayName = "Inventory ID")]
public virtual String InventoryCD { get; set; }
Custom attribute [CuryID], if key is autogenerated consider adding FieldDefaulting event:
public sealed class CuryIDAttribute : PXEventSubscriberAttribute, IPXFieldUpdatedSubscriber, IPXRowSelectedSubscriber, IPXRowUpdatingSubscriber, IPXRowUpdatedSubscriber, IPXFieldVerifyingSubscriber
{
public void FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
//reset effective date to document date first
info.SetDefaultEffDate(sender);
try
{
info.defaultCuryRate(sender);
}
catch (PXSetPropertyException ex)
{
sender.RaiseExceptionHandling(_FieldName, e.Row, sender.GetValue(e.Row, _FieldOrdinal), ex);
}
info.CuryPrecision = null;
}
}
public void RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
bool disabled = info.IsReadOnly == true || (info.CuryID == info.BaseCuryID);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyMultDiv>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.sampleCuryRate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.sampleRecipRate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyRateTypeID>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyEffDate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.baseCuryID>(sender, info, false);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.displayCuryID>(sender, info, false);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyID>(sender, info, true);
}
}
private bool? currencyInfoDirty = null;
public void RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null && info._IsReadOnly == true)
{
e.Cancel = true;
}
else
{
currencyInfoDirty = sender.IsDirty;
}
}
public void RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
CurrencyInfo old = e.OldRow as CurrencyInfo;
if (old != null && (String.IsNullOrEmpty(info.CuryID) || String.IsNullOrEmpty(info.BaseCuryID)))
{
info.BaseCuryID = old.BaseCuryID;
info.CuryID = old.CuryID;
}
if (currencyInfoDirty == false
&& info.CuryID == old.CuryID
&& info.CuryRateTypeID == old.CuryRateTypeID
&& info.CuryEffDate == old.CuryEffDate
&& info.CuryMultDiv == old.CuryMultDiv
&& info.CuryRate == old.CuryRate)
{
sender.IsDirty = false;
currencyInfoDirty = null;
}
}
}
public void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
CMSetup CMSetup = info.getCMSetup(sender);
}
}
}

How to save a record with two fields disabled on the PM301000 screen

Good afternoon everyone,
I need your help, I want to record one or several records on the PM301000 Projects screen in the detail tab, Cost Budget.
When recording one or more records, two Mark for PO and Vendor ID fields must be disabled. When the condition of the Mark for PO field is equal to true.
I have used the RowPersisting event and it disables it but when I modify or leave the registry the fields are enabled again.
Please help me or tell me how I should do it, my code is as follows.
Thanks in advance.
namespace PX.Objects.PM
{
public class PMBudgetExt : PXCacheExtension<PX.Objects.PM.PMBudget>
{
#region UsrVendorID
[PXDBInt]
[PXUIField(DisplayName = "Vendor ID", Visibility = PXUIVisibility.Visible)]
[PXDimensionSelectorAttribute("VENDOR", typeof(Search<VendorR.bAccountID, Where<VendorR.type, Equal<BAccountType.vendorType>,
And<VendorR.status, Equal<BAccount.status.active>>>>),
typeof(VendorR.acctCD), new Type[] { typeof(VendorR.acctCD), typeof(VendorR.acctName) })]
public virtual int? UsrVendorID { get; set; }
public abstract class usrVendorID : PX.Data.BQL.BqlInt.Field<usrVendorID> { }
#endregion
#region UsrMarkforPO
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Mark for PO")]
public virtual bool? UsrMarkforPO { get; set; }
public abstract class usrMarkforPO : PX.Data.BQL.BqlBool.Field<usrMarkforPO> { }
#endregion
}
}
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<ProjectEntry>
{
#region Event Handlers
protected void PMCostBudget_RowPersisting(PXCache cache, PXRowPersistingEventArgs e)
{
PMCostBudget newRow = (PMCostBudget)e.Row;
if (newRow == null) return;
PMBudgetExt newRowE = PXCache<PMBudget>.GetExtension<PMBudgetExt>(newRow);
if (Base.CostBudget.Cache.AllowUpdate == true)
{
if (newRowE.UsrMarkforPO == true)
{
PXUIFieldAttribute.SetEnabled<PMBudgetExt.usrMarkforPO>(cache, newRow, false);
PXUIFieldAttribute.SetEnabled<PMBudgetExt.usrVendorID>(cache, newRow, false);
}
}
}
#endregion
}
}
RowPersisting event executes only on save event. Therefore it's not suited for setting the field states. You will get better results with RowSelected event which is executed everytime a record is selected to be displayed on screen. You should set the state on every callback whether it is enabled or disabled. Also, the event should be declared on the same DAC type you are using to set the field state so the cache object match.
public void PMBudget_RowSelected(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected del)
{
if (del != null)
{
del(sender, e);
}
bool isFieldEnabled = [your_condition];
PXUIFieldAttribute.SetEnabled<PMBudget.field>(sender, e.Row, isFieldEnabled);
}

Calculating and Saving the custom field

I have a bound custom field grossprofit added into the SOLine grid which is calculated based on Item UnitCost, AverageCost and Qty.
I have the code written into SOLine_CuryUnitPrice_FieldSelecting event. However, it is not saving into the Database.
Also, I need a total of all lineitems for the custom field as a TotalGrossProfit on Order summary. It is calculating but for some reason it is not reflecting on screen.
Can anyone suggest?
Here is the code
public class SOOrderExtension : PXCacheExtension<SOOrder>
{
#region UsrTotalGrossProfit
public abstract class usrTotalGrossProfit : PX.Data.IBqlField
{
}
protected Decimal? _UsrTotalGrossProfit;
[PXCurrency(typeof(SOOrder.curyInfoID), typeof(SOOrder.orderWeight))]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "Total Gross Profit")]
public virtual Decimal? UsrTotalGrossProfit
{
get
{
return this._UsrTotalGrossProfit;
}
set
{
this._UsrTotalGrossProfit = value;
}
}
#endregion
}
public class SOLineExtension : PXCacheExtension<SOLine>
{
#region UsrGrossProfit
public abstract class usrGrossProfit : PX.Data.IBqlField
{
}
protected Decimal? _UsrGrossProfit;
[PXDBCurrency(typeof(SOOrder.curyInfoID), typeof(SOOrder.discTot))]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "Gross Profit")]
public virtual Decimal? UsrGrossProfit
{
get
{
return this._UsrGrossProfit;
}
set
{
this._UsrGrossProfit = value;
}
}
#endregion
}
public class SOOrderEntryExtension : PXGraphExtension<SOOrderEntry>
{
protected virtual void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
SOOrder row = e.Row as SOOrder;
if (row == null) return;
SOOrderExtension orderExtension = PXCache<SOOrder>.GetExtension<SOOrderExtension>(row);
foreach (SOLine soLine in Base.Transactions.Select())
{
SOLineExtension lineItem = PXCache<SOLine>.GetExtension<SOLineExtension>(soLine);
orderExtension.UsrTotalGrossProfit = orderExtension.UsrTotalGrossProfit + lineItem.UsrGrossProfit;
}
}
protected void SOLine_CuryUnitPrice_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
var row = (SOLine)e.Row;
if (row != null && Base.Document.Current != null && Base.Document.Current.Status != "C")
{
SOLineExtension opportunity = PXCache<SOLine>.GetExtension<SOLineExtension>(row);
InventoryItem inv = PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(new PXGraph(), row.InventoryID);
if (inv.ItemType == "F") // Stock Item
{
opportunity.UsrGrossProfit = (row.UnitPrice - invc.AvgCost) * row.Qty;
}
Base.Save.Press();
Base.Persist();
}
}
}
These are the two events that I would use.. you may have to change them to your version of C# i am using 7...
protected void SOLine_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
if (!e.row is SOLine row) return;
if (!e.oldRow is SOLine oldRow) return;
//check for unwanted status, if true return
if (Base.Document.Current.Status == "C") return;
//check for the fields to have value, if no return
if (!row.UnitPrice.HasValue || !row.Qty.HasValue) return;
//get row extension
var rowExt = row..GetExtension<SOLineExtension>();
//select inventory record
var invItem = (InventoryItem)PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(Base, row.InventoryID);
if (invItem == null) return;
//add value to field, no persist or save
if (inv.ItemType == "F") // Stock Item
{
rowExt.UsrGrossProfit = (row.UnitPrice - invc.AvgCost) * row.Qty;
}
}
protected virtual void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
//this may not work in older versions of C#
if (!(e.row is SOOrder row) return;
var rowExt = row.GetExtension<SOOrderExtension>();
foreach (SOLine soLine in Base.Transactions.Select())
{
var lineItem = soLine.GetExtension<SOLineExtension>();
rowExt.UsrTotalGrossProfit += lineItem.UsrGrossProfit;
}
//this is probably why not showing up...
Base.Document.Cache.Update(Base.Document.Current);
}

Populating unbound field from another bound custom field

I have a requirement to have a field on SalesOrder screen and the same field should appear on Shipment screen also for respective SalesOrder. And the user should be able to update these field on both the screen.
I created a bound field on Sales Order screen which user can save it. Then I created an unbound field on Shipment screen to show the text from Sales Order. For that I have written a SOShipment_RowSelected event and later for user to update it to the database, I have written SOShipment_RowUpdated. However, when I try to edit the field, it fires RowSelected event and it overwrites the editing and bring back in the same original value.
I have tried with SOShipment_ShipmentNbr_FieldUpdated & SOShipment_ShipmentNbr_FieldUpdating event but its not firing everytime.
Here is the code for Cache extension-
public class SOOrderExtension : PXCacheExtension<SOOrder>
{
#region UsrNotesText
[PXDBString(255)]
[PXUIField(DisplayName = "Pick List Notes")]
public virtual string UsrNotesText { get; set; }
public abstract class usrNotesText : IBqlField { }
#endregion
}
public class SOShipmentExtension : PXCacheExtension<SOShipment>
{
#region UsrNotesText
[PXString(255)]
[PXUIField(DisplayName = "Pick List Notes")]
public virtual string UsrNotesText { get; set; }
public abstract class usrNotesText : IBqlField { }
#endregion
}
SOShipmentExtension code-
public class SOShipmentEntryExtension : PXGraphExtension<SOShipmentEntry>
{
PXSelect<SOOrder> soOrder;
protected virtual void SOShipment_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row != null)
{
SOOrder order = PXSelectJoin<SOOrder,
LeftJoin<SOOrderShipment, On<SOOrder.orderNbr, Equal<SOOrderShipment.orderNbr>,
And<SOOrder.orderType, Equal<SOOrderShipment.orderType>>>,
LeftJoin<SOShipment, On<SOOrderShipment.shipmentNbr, Equal<SOShipment.shipmentNbr>>>>,
Where<SOShipment.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>>.Select(Base);
if (order != null)
{
SOOrderExtension orderExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(order);
SOShipment soShipment = Base.Document.Current;
SOShipmentExtension ext = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(soShipment);
ext.UsrNotesText = orderExt.UsrNotesText;
}
}
}
protected virtual void SOShipment_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
SOShipment oldRow = (SOShipment)e.OldRow;
SOShipment newRow = (SOShipment)e.Row;
if (oldRow != null || newRow != null)
{
SOShipmentExtension oldExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(oldRow);
SOShipmentExtension newExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(newRow);
if (oldExt.UsrNotesText != newExt.UsrNotesText)
{
{
SOOrder order = PXSelectJoin<SOOrder,
LeftJoin<SOOrderShipment, On<SOOrder.orderNbr, Equal<SOOrderShipment.orderNbr>,
And<SOOrder.orderType, Equal<SOOrderShipment.orderType>>>,
LeftJoin<SOShipment, On<SOOrderShipment.shipmentNbr, Equal<SOShipment.shipmentNbr>>>>,
Where<SOShipment.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>>.Select(Base);
soOrder.Current = order;
if (order != null)
{
SOOrderExtension orderExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(order);
orderExt.UsrNotesText = newExt.UsrNotesText;
soOrder.Update(order);
}
}
}
}
}
}
Any suggestions?
The trick is to initialize UsrNotesText elsewhere.
You can use PXDefault attribute:
[PXDefault(typeof(Search<SOOrderExtension.usrNotesText, Where< [...] >>))]
Or FieldDefaulting event handler:
public void SOShipment_UsrNotesText_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
e.NewValue = [...]
}
Sometimes you also want to re-initialize when user changes key fields that are not re-triggering the default.:
public void SOShipment_ShipmentNbr_FieldUpdated(PXCache sender, PXFieldDefaultingEventArgs e)
{
SOShipment shipment = e.Row as SOShipment;
if (shipment != null)
{
SOShipmentExtension shipmentExt = PXCache<SOShipment>.GetExtension<SOShipmentExtension>(shipment);
if (shipmentExt != null)
{
shipmentExt.UsrNotesText = [...];
}
}
}
In such case manually re-triggering FieldDefaulting event with RaiseFieldDefaulting is often a better option.
However method you choose to initialize avoid setting the field value in RowSelected because that event is called at times when you don't want to initialize the custom field.

Performance issue with 200+ SO line items with unbound columns

I have added two new unbound columns for SOLine DAC and updating them with in RowSelected handler. However, but it is very slow when there are more number of line items.
Here is the code for unbound columns-
[System.Serializable]
public class SOLineExtension : PXCacheExtension<SOLine>
{
#region UsrQtyAllocated
public abstract class usrQtyAllocated : IBqlField { }
protected decimal? _UsrQtyAllocated;
[PXUIField(DisplayName = "Qty. Allocated")]
public virtual decimal? UsrQtyAllocated { get; set; }
#endregion
#region UsrItemClass
public abstract class usrItemClass : IBqlField { }
protected string _UsrItemClass;
[PXUIField(DisplayName = "Item Class")]
public virtual string UsrItemClass { get; set; }
#endregion
}
Here is the code snippet for RowSelected handler-
protected void SOLine_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
SOLine row = (SOLine)e.Row;
if (row == null) return;
PXUIFieldAttribute.SetEnabled<SOLineExtension.usrQtyAllocated>(sender, row, false);
PXUIFieldAttribute.SetEnabled<SOLineExtension.usrItemClass>(sender, row, false);
INItemClass defItemClass = PXSelectJoin<INItemClass,
InnerJoin<InventoryItem,
On<InventoryItem.itemClassID, Equal<INItemClass.itemClassID>>>,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(Base, row.InventoryID);
if (defItemClass != null)
{
sender.SetValue<SOLineExtension.usrItemClass>(row, defItemClass.Descr);
}
SOLineSplit defSOLine = PXSelectJoin<SOLineSplit,
InnerJoin<SOLine,
On<SOLine.orderType, Equal<SOLineSplit.orderType>,
And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>,
And<SOLine.inventoryID, Equal<SOLineSplit.inventoryID>>>>>,
Where<SOLineSplit.isAllocated, Equal<Required<SOLineSplit.isAllocated>>,
And<SOLineSplit.orderNbr, Equal<Required<SOLineSplit.orderNbr>>,
And<SOLineSplit.inventoryID, Equal<Required<SOLineSplit.inventoryID>>>>>>.Select(Base, 1, row.OrderNbr, row.InventoryID);
if (defSOLine != null)
{
sender.SetValue<SOLineExtension.usrQtyAllocated>(row, defSOLine.Qty);
}
}
One should never use RowSelected handlers to set values for unbound DAC fields: RowSelected event is intended to execute only UI presentation logic. Instead it’s always recommended to assign values to unbound fields with a RowSelecting handler as shown in API Reference and Step 5.2: Customizing Business Logic for the Sales Orders form the T300 class documentation.
Also for information: none of your custom fields are decorated with PXTypeAttribute, which always a must.

Resources