Updating custom field is ending into infinite loop - acumatica

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.

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);
}
}
}

Detecting when an Invoice exceeds the PO amount

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
}

An unhandled exception has occurred in the function 'MoveNext'. Please see the trace log for more details

I have a customization Project that requires me to only show data (like Opportunities) that have been setup in added customization tab in Business Account. I've manage to comply these using data view delegate and a custom selector, The error occurs when:
1.) clicking an opportunity ID where the customization restricts such data in the generic inquiry.
2.) clicking next or previous button in the opportunities module
Here is the Code
protected virtual IEnumerable opportunity()
{
var output = new PXSelect<CROpportunity>(Base);
foreach (CROpportunity _output in output.Select())
{
bool returnOutput = false;
var memberships = PXSelectJoinGroupBy<EPEmployee, RightJoin<EPCompanyTreeMember, On<EPCompanyTreeMember.userID, Equal<EPEmployee.userID>>>, Where<EPEmployee.userID, Equal<Current<AccessInfo.userID>>>, Aggregate<GroupBy<EPCompanyTreeMember.workGroupID>>>.Select(Base);
foreach (PXResult<EPEmployee, EPCompanyTreeMember> member in memberships)
{
EPCompanyTreeMember _member = (EPCompanyTreeMember)member;
BAccountRestriction2 visible = PXSelect<BAccountRestriction2, Where<BAccountRestriction2.account, Equal<Required<BAccount.bAccountID>>, And<BAccountRestriction2.child, Equal<Required<BAccountRestriction2.child>>>>>.Select(Base, ((CROpportunity)_output).BAccountID, _member.WorkGroupID);
if (visible != null || ((CROpportunity)_output).OpportunityID == " <NEW>")
{
returnOutput = true;
break;
}
}
if (returnOutput)
{
yield return _output;
}
}
}
Here is the Custom Selector
public class OpportunityMaintExtension : PXGraphExtension<OpportunityMaint>
{
#region custom selector
public class PXCustomSelectorOpportunityAttribute : PXCustomSelectorAttribute
{
public PXCustomSelectorOpportunityAttribute()
: base(typeof(CROpportunity.opportunityID)
, new[] { typeof(CROpportunity.opportunityID),
typeof(CROpportunity.opportunityName),
typeof(CROpportunity.status),
typeof(CROpportunity.curyAmount),
typeof(CROpportunity.curyID),
typeof(CROpportunity.closeDate),
typeof(CROpportunity.stageID),
typeof(CROpportunity.cROpportunityClassID),
typeof(BAccount.acctName),
typeof(Contact.displayName) }
)
{
//this.DescriptionField = typeof(CQHRISLeave.refNbr);
}
protected virtual IEnumerable GetRecords()
{
foreach (var pc in this._Graph.GetExtension<OpportunityMaintExtension>().opportunity())
{
if (((CROpportunity)pc).OpportunityID != " <NEW>")
yield return pc as CROpportunity;
}
}
}
#endregion
And then the implementation of the custom selector attribute is here:
public class CROpportunityExtension : PXCacheExtension<CROpportunity>
{
#region OpportunityID
public abstract class opportunityID : PX.Data.IBqlField { }
public const int OpportunityIDLength = 10;
[PXDBString(OpportunityIDLength, IsUnicode = true, IsKey = true, InputMask = ">CCCCCCCCCCCCCCC")]
[PXUIField(DisplayName = "Opportunity ID", Visibility = PXUIVisibility.SelectorVisible)]
[AutoNumber(typeof(CRSetup.opportunityNumberingID), typeof(AccessInfo.businessDate))]
[NORDE.OpportunityMaintExtension.PXCustomSelectorOpportunity]
[PXFieldDescription]
public virtual String OpportunityID { get; set; }
#endregion
}
I've managed to "resolve" this issue by disabling the generic inquiry on this page. Initially, Opportunities page is a generic inquiry, for some reason, it may have affected the cache or the data view delegate and throws an error.
I simply: Edit Generic inquiry -> Entry Point Tab -> Uncheck "Replace Entry Screen with this Inuiry in Menu"

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.

identity field as a key for a custom table

I have two custom tables (Header / Detail) which have multiple fields as the key for both. I've created all the PXParent and PXDefault attributes in the detail select from the Header, just as shown in the T200 course.
The problem is that when I have the key fields on the header section and I go to try to create a new record, the form doesn't clear the values I've selected for those key fields.
I've gone on to just creating one auto-incrementing int identity field as the key, with the other former key fields assembled into a unique index instead. I'm using the PXDBIdentity(IsKey = true) attribute in my DAC - and it works well except for one problem: When I create a new record, the field shows -2147483647 until I save, when it then shows the newly created identity value.
So I guess my question is this:
What's the standard protocol / best practice for creating a key field for a custom table when the unique identity of that table consists of 6 fields? If I'm doing it correctly, how do I eliminate that -2147483647 from showing in the ID field?
There is only one major rule when it comes to defining key fields in a DAC:
only a field bound to an identity column can be the only key field in a DAC
you can define several key fields in a DAC as long as none of the defined key fields is bound to an identity column
The standard PXInsert action provided by the framework preserves values of all key fields, except the last one. If you want the Insert button to clear values of all key field, it's possible to inherit from the PXInsert<TNode> class and clear Searches array before base logic gets executed:
public class MyGraph : PXGraph<MyGraph>
{
public class PXInsertCst<TNode> : PXInsert<TNode>
where TNode : class, IBqlTable, new()
{
public PXInsertCst(PXGraph graph, string name)
: base(graph, name)
{
}
public PXInsertCst(PXGraph graph, Delegate handler)
: base(graph, handler)
{
}
[PXUIField(DisplayName = ActionsMessages.Insert,
MapEnableRights = PXCacheRights.Insert,
MapViewRights = PXCacheRights.Insert)]
[PXInsertButton]
protected override IEnumerable Handler(PXAdapter adapter)
{
adapter.Searches = null;
return base.Handler(adapter);
}
}
public PXSave<MyPrimaryDAC> Save;
public PXCancel<MyPrimaryDAC> Cancel;
// The standard PXInsert type was replaced with the custom PXInsertCst type
public PXInsertCst<MyPrimaryDAC> Insert;
public PXDelete<MyPrimaryDAC> Delete;
public PXCopyPasteAction<MyPrimaryDAC> CopyPaste;
public PXFirst<MyPrimaryDAC> First;
public PXPrevious<MyPrimaryDAC> Previous;
public PXNext<MyPrimaryDAC> Next;
public PXLast<MyPrimaryDAC> Last;
}
In case it's required to show the only DAC key field bound an identity column in the UI, but users don't want to see a temporary negative value generated for a new record, you should implement FieldSelecting and FieldUpdating event handlers for the DAC's identity column mapped field following the code sample below:
public class MyGraph : PXGraph<MyGraph>
{
protected void MyDAC_IdentityField_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (e.Row != null && e.ReturnValue is int?)
{
if ((e.ReturnValue as int?).GetValueOrDefault() < 0)
{
e.ReturnValue = null;
}
}
}
protected void MyDAC_IdentityField_FieldUpdating(PXCache sender, PXFieldUpdatingEventArgs e)
{
if (e.Row != null && e.NewValue == null && sender.Inserted.Count() == 1)
{
var defaultValue = sender.GetValue<MyDAC.identityField>(sender.Inserted.FirstOrDefault_());
if (defaultValue != null)
{
e.NewValue = defaultValue;
}
}
}
}
For a more generic approach on the DAC level, you can implement a custom attribute inherited from PXDBIdentityAttribute and override FieldSelecting and FieldUpdating event handlers following the code sample below:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter |
AttributeTargets.Class | AttributeTargets.Method)]
public class PXDBNewIdentityAttribute : PXDBIdentityAttribute
{
public override void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
base.FieldSelecting(sender, e);
if (e.Row != null && e.ReturnValue is int?)
{
if ((e.ReturnValue as int?).GetValueOrDefault() < 0)
{
e.ReturnValue = null;
}
}
}
public override void FieldUpdating(PXCache sender, PXFieldUpdatingEventArgs e)
{
if (e.Row != null && e.NewValue == null && sender.Inserted.Count() == 1)
{
var defaultValue = sender.GetValue(sender.Inserted.FirstOrDefault_(), FieldOrdinal);
if (defaultValue != null)
{
e.NewValue = defaultValue;
}
}
base.FieldUpdating(sender, e);
}
}
The problem with an identity field is that the number is generated by the Database and not by the ORM which generally holds new Inserted rows in the Cache before the record persists. My advice would be to either drop the identity from the UI altogether or figure out a different key.

Resources