Acumatica seems to have moved the CreateSalesOrder method from OpportunityMaint to the new CRCreateSalesOrder class. I cannot figure out how to override the CreateSalesOrder method with the new structure. Below is the original code. Any help greatly appreciated.
using PX.Data;
using PX.Objects.AR;
using PX.Objects.CM;
using PX.Objects.Common.Discount;
using PX.Objects.CR;
using PX.Objects.CR.Extensions.CRCreateSalesOrder;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.PO;
using PX.Objects.SO;
using PX.Objects.TX;
using System.Collections.Generic;
using static PX.Objects.CR.OpportunityMaint;
namespace CH.KV.CPLVendorSOPO
{
public class CHKVOpportunityMaintExt : PXGraphExtension<OpportunityMaint>
{
public delegate void DoCreateSalesOrderDelegate(CreateSalesOrderFilter param);
[PXOverride]
public void DoCreateSalesOrder(CreateSalesOrderFilter param, DoCreateSalesOrderDelegate baseMethod)
{
DoCreateSalesOrderCHKVExt(param);
}
protected virtual void DoCreateSalesOrderCHKVExt(CreateSalesOrderFilter param)
{
bool recalcAny = param.RecalculatePrices == true ||
param.RecalculateDiscounts == true ||
param.OverrideManualDiscounts == true ||
param.OverrideManualDocGroupDiscounts == true ||
param.OverrideManualPrices == true;
var opportunity = Base.Opportunity.Current;
Customer customer = (Customer)PXSelect<Customer, Where<Customer.bAccountID, Equal<Current<CROpportunity.bAccountID>>>>.Select(Base);
//do things
docgraph.Save.Press();
}
}
}
You have to add the CRCreateSalesOrder extension class and overwrite the DoCreateSalesOrder method in there. This works on Acumatica 2022R2. Not sure of 2021R2.
public class CRCreateSalesOrderExt : CRCreateSalesOrder<OpportunityMaint.Discount, OpportunityMaint, CROpportunity>
{
#region Initialization
public static bool IsActive() => IsExtensionActive();
protected override DocumentMapping GetDocumentMapping()
{
return new DocumentMapping(typeof(CROpportunity))
{
QuoteID = typeof(CROpportunity.quoteNoteID)
};
}
#endregion
#region Events
public virtual void _(Events.RowSelected<CROpportunity> e)
{
CROpportunity row = e.Row as CROpportunity;
if (row == null) return;
CRQuote primaryQt = Base.PrimaryQuoteQuery.SelectSingle();
bool hasProducts = Base.Products.SelectSingle() != null;
var products = Base.Products.View.SelectMultiBound(new object[] { row }).RowCast<CROpportunityProducts>();
bool allProductsHasNoInventoryID = products.Any(_ => _.InventoryID == null) && !products.Any(_ => _.InventoryID != null);
bool hasQuotes = primaryQt != null;
CreateSalesOrder
.SetEnabled(hasProducts && !allProductsHasNoInventoryID
&& (
(!hasQuotes
|| (primaryQt.Status == CRQuoteStatusAttribute.Approved
|| primaryQt.Status == CRQuoteStatusAttribute.Sent
|| primaryQt.Status == CRQuoteStatusAttribute.Accepted
|| primaryQt.Status == CRQuoteStatusAttribute.Draft
)
)
)
&& (!hasQuotes || primaryQt.QuoteType == CRQuoteTypeAttribute.Distribution)
&& e.Row.BAccountID != null);
}
#endregion
#region Overrides
public override CRQuote GetQuoteForWorkflowProcessing()
{
return Base.PrimaryQuoteQuery.SelectSingle();
}
public override void DoCreateSalesOrder(SOOrderEntry docgraph, Extensions.CRCreateSalesOrder.Document masterEntity, CreateSalesOrderFilter filter)
{
base.DoCreateSalesOrder(docgraph, masterEntity, filter);
}
#endregion
}
You will need to find where the code was moved too. I have had to do something similar when acumatica shifted up some buttons within the Customer Payment Profile Maint Graph into its own dedicated class.
This is what the new graph extension declaration looked like I'm my case where the original was a direct extension of the Customer Payment Profile Maint Graph
public class APSPaymentProfileHostedFormExtension : PXGraphExtension<
CustomerPaymentMethodMaint.PaymentProfileHostedForm,
CustomerPaymentMethodMaint>
{
For clues, you want to look for something like this that will be declared on the graph the button originally resided. In my case, it looked like this.
public class PaymentProfileHostedForm : Extensions.PaymentProfile.PaymentProfileGraph<CustomerPaymentMethodMaint, CustomerPaymentMethod>
{
Hopefully, this is enough to get you in the right direction. Let me know how you make out?
Related
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 do I Enable usrsubcontractNbr in rowselected Event:
I am unable to access the usrSubcontractNbr from neither of the DAC (ApTran and ApTranExt)
UsrSubcontractNbr field was defined in construction feature package. APTran is converted into dll.
How can access this field?
Seems like a similar issue to this post: How to access Custom field,which is defined in Construction feature package- Acumatica
Searching the PX.Objects.CN.dll from the construction install package you will find:
using PX.Data;
using PX.Objects.AP;
using PX.Objects.CS;
namespace PX.Objects.CN.Subcontracts.AP.CacheExtensions
{
public sealed class ApTranExt : PXCacheExtension<APTran>
{
[PXString(15, IsUnicode = true)]
[PXUIField(DisplayName = "Subcontract Nbr.", Enabled = false, IsReadOnly = true)]
public string UsrSubcontractNbr
{
get
{
if (!(this.get_Base().get_POOrderType() == "RS"))
return (string) null;
return this.get_Base().get_PONbr();
}
}
[PXInt]
[PXUIField(DisplayName = "Subcontract Line", Enabled = false, IsReadOnly = true, Visible = false)]
public int? UsrSubcontractLineNbr
{
get
{
if (!(this.get_Base().get_POOrderType() == "RS"))
return new int?();
return this.get_Base().get_POLineNbr();
}
}
public static bool IsActive()
{
return PXAccess.FeatureInstalled<FeaturesSet.construction>();
}
public ApTranExt()
{
base.\u002Ector();
}
public abstract class usrSubcontractNbr : IBqlField, IBqlOperand
{
}
public abstract class usrSubcontractLineNbr : IBqlField, IBqlOperand
{
}
}
}
To access the field you will need to use PX.Objects.CN.Subcontracts.AP.CacheExtensions.ApTranExt
Edit. based on the comment if having an issue using rowselected be sure to use the signature with the PXRowSelected delegate so you can control when you enabled code to run after the base call. you might have a problem where the base call is running after your code which could disable the field again.
Ex:
protected void APTran_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected del)
{
del?.Invoke(cache, e);
var row = (APTran) e.Row;
if (row == null) return;
PXUIFieldAttribute.SetEnabled<PX.Objects.CN.Subcontracts.AP.CacheExtensions.ApTranExt.usrSubcontractNbr>(
cache, row, true);
}
I am working on making a thumbnail image on the Sales Order lines for the Document Details when the InventoryID is selected. The image however does not populate to the grid whenever I select the InventoryID in the line. Here is what I have so far:
DAC Extension:
namespace PX.Objects.IN
{
public class InventoryItemExt : PXCacheExtension<InventoryItem>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
}
Code Extension:
using PX.Data;
using PX.Objects.SO;
using System;
using PX.Objects.IN;
using PX.Web.UI;
namespace Combined
{
public class SOLineExt : PXCacheExtension<SOLine>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
public class SOOrderEntryExt: PXGraphExtension<SOOrderEntry>
{
public void SOLine_RowSelecting(PXCache sender, PXRowSelectingEventArgs e,PXRowSelecting baseMethod)
{
baseMethod.Invoke(sender, e);
if(e.Row!=null)
{
var row = e.Row as SOLine;
if (row.InventoryID != null)
{
InventoryItem currentLineItem = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(this.Base, row.InventoryID);
if (row != null && !string.IsNullOrEmpty(currentLineItem.ImageUrl))
{
if(currentLineItem.StkItem==true)
{
InventoryItemMaint inventoryItemMaint = PXGraph.CreateInstance<InventoryItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
else
{
NonStockItemMaint inventoryItemMaint = PXGraph.CreateInstance<NonStockItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
}
}
}
}
}
}
ASPX Code:
Code in the Grid:
<px:PXGridColumn DataField="ThumbnailURL" Width="300px" Type="Icon" />
Code on the InventoryID SegmentMask:
<px:PXSegmentMask CommitChanges="True" ID="edInventoryID" runat="server" DataField="InventoryID" AllowEdit="True" >
<GridProperties>
<Columns>
<px:PXGridColumn Type="Icon" DataField="ThumbnailURL" Width="300px" AutoGenerateOption="Add" />
</Columns>
</GridProperties>
</px:PXSegmentMask>
I did find a post about adding an image to the InventoryID Selector and it has a different method of adding images to that grid, does the same apply here? Here is the other post: How to show images inside selector lookup?
I have changed my code above to match the other post but now I am receiving this error:
\App_RuntimeCode\SOOrderEntry.cs(61): error CS0103: The name 'ControlHelper' does not exist in the current context
\App_RuntimeCode\SOOrderEntry.cs(61): error CS0103: The name 'ControlHelper' does not exist in the current context
Adding code from the first answer below but now the grid column is showing up blank:
Update 1: FIXED
I have redone all the code above to answer 1 along with adding the code from the post of Ruslan's answer in the post above. The screenshot is still coming back the same.
Update 2:
I have got everything working or so it seemed. I am now receiving this error only sometimes and I'm not sure what the cause is. Ignore the CustomerID error that is because their credit balance is overdue.
Add reference to PX.Web.UI.dll from Acumatica's Bin folder or using PX.Web.UI; if you are writing code in the customization's Code Editor.
ControlHelper is a static helper class for making easier work with Acumatica's Web controls .
UPDATE 1
In the answer you have noted addition of the Image is done in the lookup of the Inventory Item Selector and add to the Grid the field of the SOLineExt. In your case you are adding it to the SOLine. Here is code which is doing that:
using PX.Data;
using PX.Objects.SO;
using System;
using PX.Objects.IN;
using PX.Web.UI;
namespace ClassLibrary1
{
public class SOLineExt : PXCacheExtension<SOLine>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
public class SOOrderEntryExt: PXGraphExtension<SOOrderEntry>
{
public void SOLine_RowSelecting(PXCache sender, PXRowSelectingEventArgs e,PXRowSelecting baseMethod)
{
baseMethod?.Invoke(sender, e);
if(e.Row!=null)
{
var row = e.Row as SOLine;
if (row.InventoryID != null)
{
InventoryItem currentLineItem = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(this.Base, row.InventoryID);
if (row != null && !string.IsNullOrEmpty(currentLineItem.ImageUrl))
{
if(currentLineItem.StkItem==true)
{
InventoryItemMaint inventoryItemMaint = PXGraph.CreateInstance<InventoryItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
else
{
NonStockItemMaint inventoryItemMaint = PXGraph.CreateInstance<NonStockItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
}
}
}
}
}
}
As you can see I have added the ThumbnailURL directly to SOLine.
Also now it's needed to create instance of the InventoryItemMaint or NonStockItemMaint depending on the Item Type(Stock/NonStock).
As a result you should get this:
I have this property of a custom object to be displayed in a PropertyGrid:
[DisplayName("Dirección IP Local")]
[Editor(typeof(Configuracion.Editors.IPAddressEditor), typeof(UITypeEditor))]
[Description("Dirección IP del computador en el cual está conectado el dispositivo.")]
public IPAddress IPLocal { get; set; }
In constructor of the same class I have:
this.IPLocal = Common.Helper.ProgramInfo.GetLocalIPAddresses().FirstOrDefault();
The IPAddressEditor is this:
public class IPAddressEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
private IpAddressInput _ipAddressInput;
private bool _escKeyPressed;
public IPAddressEditor()
{
_ipAddressInput = new IpAddressInput();
_ipAddressInput.Width = 150;
_ipAddressInput.BackgroundStyle.BorderWidth = -1;
_ipAddressInput.ButtonClear.Visible = true;
_ipAddressInput.ValueChanged += _ipAddressInput_ValueChanged;
_ipAddressInput.PreviewKeyDown += _ipAddressInput_PreviewKeyDown;
}
void _ipAddressInput_PreviewKeyDown(object sender, System.Windows.Forms.PreviewKeyDownEventArgs e)
{
if (e.KeyCode == Keys.Escape)
_escKeyPressed = true;
}
void _ipAddressInput_ValueChanged(object sender, EventArgs e)
{
if (_editorService != null)
_editorService.CloseDropDown();
}
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
_editorService =
provider.GetService(
typeof(IWindowsFormsEditorService))
as IWindowsFormsEditorService;
}
if (_editorService != null)
{
_escKeyPressed = false;
_editorService.DropDownControl(_ipAddressInput);
if (!_escKeyPressed)
{
IPAddress ip = IPAddress.None;
if (IPAddress.TryParse(_ipAddressInput.Value, out ip))
return ip;
}
}
return value;
}
}
The problem is that the control inside the editor (in this case _ipAddressInput) is not initialized with the value I assigned in object constructor.
This is obvious because in type editor constructor I am creating a new instance of IpAddressInput, so the question is: what is the best way to initialize it?
I was thinking about creating a public setter for that variable and calling in constructor of the custom object using a TypeDescriptor, but I think this is tricky,
Is there a better solution?
Regards
Jaime
Finally, I have found the solution.
I just added
_ipAddressInput.Text = value.ToString();
before the call to
_editorService.DropDownControl(_ipAddressInput);
I have thought about it before but I underestimate it because I was wondering what will happen when updating the property using the editor and whether it will keep the modified value.It was no problem with that and the editor worked like a charm.
I have a custom field in AR Invoice and Memos (Screen ID AR301000) for the corresponding AP Ref. Nbr. And in the similar manager another custom field in AP Bills and Adjustment (Screen ID AP301000) for the corresponding AR Ref. Nbr.
I am trying to update AP Ref. Nbr. on AR screen when user updates the AR Ref. Nbr. in AP screen.
For example-
I am on AR Screen Invoice 0001, I am updating AP Ref. Nbr. to abc01. System will automatically update the AP Bill abc01 with the corresponding AR Ref. Nbr. with 0001.
I have below code written to achieve this but it runs into infinite loop as both it is trying to update the corresponding fields in other screen. Let me know if I missing anything or there is a better way.
On AR Graph Extension
public class ARInvoiceEntryExtension : PXGraphExtension<ARInvoiceEntry>
{
protected virtual void ARInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
var row = (ARInvoice)e.Row;
if (row != null && sender.IsDirty)
{
ARRegisterExtension ext = PXCache<ARRegister>.GetExtension<ARRegisterExtension>(row);
if (ext != null && !string.IsNullOrEmpty(ext.UsrAPRefNbr))
{
APInvoiceEntry graph = PXGraph.CreateInstance<APInvoiceEntry>();
APInvoice apRow = PXSelect<APInvoice,
Where<APInvoice.refNbr, Equal<Required<APInvoice.refNbr>>>>.Select(graph, ext.UsrAPRefNbr);
if (apRow != null)
{
APRegisterExtension ext1 = PXCache<APRegister>.GetExtension<APRegisterExtension>(apRow);
if (ext1 != null && string.IsNullOrEmpty(ext1.UsrARRefNbr)) //Update only if it is empty
{
ext1.UsrARRefNbr = row.RefNbr;
graph.Document.Current = apRow;
graph.Caches[typeof(APRegister)].SetValue<APRegisterExtension.usrARRefNbr>(apRow, row.RefNbr);
graph.Caches[typeof(APRegister)].Update(apRow);
graph.Actions.PressSave();
}
}
}
}
}
}
On AP Graph Extension
public class APInvoiceEntryExtension : PXGraphExtension<APInvoiceEntry>
{
protected virtual void APInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
var row = (APInvoice)e.Row;
if (row != null && sender.IsDirty)
{
APRegisterExtension ext = PXCache<APRegister>.GetExtension<APRegisterExtension>(row);
if (ext != null && !string.IsNullOrEmpty(ext.UsrARRefNbr))
{
ARInvoiceEntry graph = PXGraph.CreateInstance<ARInvoiceEntry>();
ARInvoice arRow = PXSelect<ARInvoice,
Where<ARInvoice.refNbr, Equal<Required<ARInvoice.refNbr>>>>.Select(graph, ext.UsrARRefNbr);
if (arRow != null)
{
ARRegisterExtension ext1 = PXCache<ARRegister>.GetExtension<ARRegisterExtension>(arRow);
if (ext1 != null && string.IsNullOrEmpty(ext1.UsrAPRefNbr)) //Update only if it is empty
{
ext1.UsrAPRefNbr = row.RefNbr;
graph.Document.Current = arRow;
graph.Caches[typeof(ARRegister)].SetValue<ARRegisterExtension.usrAPRefNbr>(arRow, row.RefNbr);
graph.Caches[typeof(ARRegister)].Update(arRow);
graph.Actions.PressSave();
}
}
}
}
}
}
AR Cache Extension
public class ARRegisterExtension : PXCacheExtension<ARRegister>
{
public abstract class usrAPRefNbr : PX.Data.IBqlField
{
}
protected string _usrAPRefNbr;
[PXDBString(15)]
[PXUIField(DisplayName = "AP Ref Nbr.", Visibility = PXUIVisibility.SelectorVisible)]
[APInvoiceType.RefNbr(typeof(Search3<PX.Objects.AP.Standalone.APRegisterAlias.refNbr,
InnerJoinSingleTable<APInvoice, On<APInvoice.docType, Equal<PX.Objects.AP.Standalone.APRegisterAlias.docType>,
And<APInvoice.refNbr, Equal<PX.Objects.AP.Standalone.APRegisterAlias.refNbr>>>,
InnerJoinSingleTable<Vendor, On<PX.Objects.AP.Standalone.APRegisterAlias.vendorID, Equal<Vendor.bAccountID>>>>,
OrderBy<Desc<APRegister.refNbr>>>))]
public virtual string UsrAPRefNbr
{
get; set;
}
}
AP Cache Extension
public class APRegisterExtension : PXCacheExtension<APRegister>
{
public abstract class usrARRefNbr : PX.Data.IBqlField
{
}
protected string _usrARRefNbr;
[PXDBString(15)]
[PXUIField(DisplayName = "AR Ref Nbr.", Visibility = PXUIVisibility.SelectorVisible)]
[ARInvoiceType.RefNbr(typeof(Search3<PX.Objects.AR.Standalone.ARRegisterAlias.refNbr,
InnerJoinSingleTable<ARInvoice, On<ARInvoice.docType, Equal<PX.Objects.AR.Standalone.ARRegisterAlias.docType>,
And<ARInvoice.refNbr, Equal<PX.Objects.AR.Standalone.ARRegisterAlias.refNbr>>>,
InnerJoinSingleTable<Customer, On<PX.Objects.AR.Standalone.ARRegisterAlias.customerID, Equal<Customer.bAccountID>>>>,
OrderBy<Desc<ARRegister.refNbr>>>))]
public virtual string UsrARRefNbr
{
get; set;
}
}
When saving, APInvoice_RowUpdated modifies ARInvoice which in turn modifies APInvoice which triggers APInvoice_RowUpdated producing an infinite loop of event calls. The inverse in ARInvoice_RowUpdated which updates APInvoice will result in a similar infinite loop.
To get out of this, you can remove the graph event handler at runtime after instantiating the graph. First make your event handler access modifier public so you can reference them:
public virtual void APInvoice_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
After creating a graph, get the extension and remove the handler that cause the infinite loop:
APInvoiceEntry graph = PXGraph.CreateInstance<APInvoiceEntry>();
APInvoiceEntryExtension graphExt = graph.GetExtension<APInvoiceEntryExtension>();
graphExt.RowUpdated.RemoveHandler<APInvoice>(graphExt.APInvoice_RowUpdated);
The same modification has to be done for ARInvoice because the infinite loop goes both way, from AP to AR and from AR to AP.