Lets say I have a DAC record, like SOOrder, and I have a field like customerID, where there is a PXSelectorAttribute defined on an integer field, that has a SubstitueKey = typeof(Customer.acctCD) and Description = typeof(Customer.acctName). Is there some way that I can get the values of the substitute key / description field for that record without doing a PXSelect against the selectors table?
Thanks
-Kyle
Note since no version was specified my example was coded against 2020r2
The following is an example that updates an Invoice/Memo's Description to the Location's CD and Descr fields [CD: Descr] when the location is changed on the Invoice/Memo. I believe the function you're looking for is PXSelectorAttribute.Select<SELECTOR_FIELD>(SELECTOR_FIELD_CACHE, SELECTOR_FIELD_RECORD) AS SELECTOR_TARGET_DAC.
public class ArInvoiceEntrySoExt : PXGraphExtension<ARInvoiceEntry>
{
#region Event Handlers
#region ArInvoice
public virtual void _(Events.FieldUpdated<ARInvoice.customerLocationID> e, PXFieldUpdated del)
{
var inv = e.Row as ARInvoice;
del?.Invoke(e.Cache, e.Args);
if (inv != default)
{
var loc = PXSelectorAttribute.Select<ARInvoice.customerLocationID>(e.Cache, inv) as Location;
e.Cache.SetValueExt<ARInvoice.docDesc>(inv, string.Format("{0}: {1}", loc?.LocationCD, loc?.Descr));
}
}
#endregion
#endregion
}
An alternative, you can use PXSelectorAttribute.GetField(SELECTOR_FIELD_CACHE, SELECTOR_FIELD_RECORD, "SELECTOR_FIELD_NAME", SELECTOR_FIELD_VALUE, "SELECTOR_TARGET_FIELD_NAME") to get a specific field from the selector's target record. As an example, the following code does the same thing as above using this alternative method:
public class ArInvoiceEntrySoExt : PXGraphExtension<ARInvoiceEntry>
{
#region Event Handlers
#region ArInvoice
public virtual void _(Events.FieldUpdated<ARInvoice.customerLocationID> e, PXFieldUpdated del)
{
var inv = e.Row as ARInvoice;
del?.Invoke(e.Cache, e.Args);
if (inv != default)
{
var loc = PXSelectorAttribute.Select<ARInvoice.customerLocationID>(e.Cache, inv) as Location;
e.Cache.SetValueExt<ARInvoice.docDesc>(inv, string.Format("{0}: {1}",
PXSelectorAttribute.GetField(e.Cache, inv, "customerLocationID", inv.CustomerLocationID, "LocationCD"),
PXSelectorAttribute.GetField(e.Cache, inv, "customerLocationID", inv.CustomerLocationID, "Descr")));
}
}
#endregion
#endregion
}
I also was told about another method that can be used
PXFieldState.UnwrapValue(object value) will return the UI equivalent of a value in the back end. This is nice because then you can use one method for both drop downs and selectors, but I cant seem to see how to get the description field using this
Related
Does anyone have a code snippet on how to go from RefNoteId => DAC when I dont know what dac type the note is attached to?
I have made it this far (row.RefNoteID is what I am starting from)
Note note = PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Select(this, row.RefNoteID);
Type recordType = Type.GetType(note.EntityType);
PXCache recordCache = Caches[recordType];
How can I now do a PXSelect<recordType, Where<recodType.noteID, Equal<Required<recordType.noteID>>>>.Select(GRAPH) ? The recordType could be any DAC in the system that has a noteID.
Thanks
The below code works for me and it is based on the way Acumatica gets the record inside the PXRefNoteSelectorAttribute.PrimaryRow_RowPersisted.
The problem with this approach is that this will work for Header entities like SOOrder, INRegister, SOInvoice, SOShipment, and others. But for "detail" entities like SOLine, INTran, and others this approach will work only if that corresponding record has some Note related to Text/File. Acumatica is adding records corresponding to their NoteID into the Note table only if that detail records have some Note/Text. My best guess is that this is done in order to avoid over-spamming the Note table.
using PX.Data;
using PX.Objects.SO;
using System;
using System.Collections;
using System.Linq;
using System.Web.Compilation;
namespace SearchByNoteID
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> searchByNoteID;
[PXUIField(DisplayName ="Search by Note ID")]
[PXButton(CommitChanges = true)]
public virtual IEnumerable SearchByNoteID(PXAdapter adapter)
{
var order = adapter.Get<SOOrder>().FirstOrDefault();
if(order!=null)
{
//
//...
//
Guid? noteID = GetNoteID();
object record = GetRecordByNoteID(noteID);
//
//... do whatever you want with the record
//
}
return adapter.Get();
}
protected object GetRecordByNoteID(Guid? noteID)
{
var type = GetEntityType(this.Base, noteID);
if(type==null) return null;
object entityRow = new EntityHelper(this.Base).GetEntityRow(type, noteID);
return entityRow;
}
protected Type GetEntityType(PXGraph graph, Guid? noteID)
{
if (noteID == null)
{
return null;
}
Note note = PXSelectBase<Note, PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Config>.SelectWindowed(graph, 0, 1, new object[]
{
noteID
});
if (note == null || string.IsNullOrEmpty(note.EntityType))
{
return null;
}
return PXBuildManager.GetType(note.EntityType, false);
}
}
}
I have 50+ custom paired fields "Inches" and "Centimeters", each enabled and editable. I need to update "Inches" if the user changed the value of "Centimeters" and visa verse. I was able to do this using SetValuePending on one of the paired fields and SetValueExt on the other during the Field Updated Event. My question, is there a way to do this on a higher level without having to do a Field_Updated event for all the 100+ fields. I know that Formulas would create a circular reference so cannot be used. Thanks
Well, you can use one method to handle FieldUpdated events for all fields you need using graph FieldUpdated.AddHandler method in constructor. To get a field name just extend a standard Acumatica FieldUpdated delegate with one additional parameter (name for example) and put it during the FieldUpdated.AddHandler call.
Here is an example with "Invoices and Memos" screen and ARInvoiceEntry graph.
public ARInvoiceEntry()
{
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.inches).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.inches).Name));
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.centimeters).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.centimeters).Name));
...
}
protected virtual void CommonFieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e, string name)
{
// do something
}
Moreover, you can add handlers dynamically using fields collection for example
foreach(var field in Caches[typeof(ARTran)].Fields)
{
// add handler depends on field name
}
You can do it by PXFormula + ExternalValue BQL expression (the same as PXRowUpdatedEventArgs.ExternalCall for example), which will prevent circular reference between pair fields. The idea is to calculate field only when a related field has been changed by the user from UI (ExternalCall = true) and skip calculation when related field updated by the formula (ExternalCall = false).
public class centimetersInInches : PX.Data.BQL.BqlDecimal.Constant<centimetersInInches>
{
public centimetersInInches() : base(2.54m) { }
}
[PXDecimal]
[PXUIField(DisplayName = "Inches")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Div<centimeters, centimetersInInches>>))]
public virtual decimal? Inches { get; set; }
public abstract class inches : PX.Data.BQL.BqlDecimal.Field<inches> { }
[PXDecimal]
[PXUIField(DisplayName = "Centimeters")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Mult<inches, centimetersInInches>>))]
public virtual decimal? Centimeters { get; set; }
public abstract class centimeters : PX.Data.BQL.BqlDecimal.Field<centimeters> { }
And aspx
<px:PXGridColumn DataField="Inches" CommitChanges="True" />
<px:PXGridColumn DataField="Centimeters" CommitChanges="True" />
You can use the RowUpdated event and compare the old row with the new row to detect which field changed. I agree that keeping all logic in a single method is preferable.
public virtual void DAC_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
DAC row = e.Row as DAC;
DAC oldRow = e.OldRow as DAC;
if (row == null || oldRow == null) return;
// Compare old row with new row to determine which field changed
if (row.Inches != oldRow.Inches)
{
// Inches field changed, update CM value
row.CM = row.Inches * INCHES_TO_CM_CONSTANT;
}
// Add more conditions for the other fields
[..]
}
I know that Formulas would create a circular reference so cannot be
used.
Yes I wouldn't recommend it either, you could use the DAC property Setter though.
public string _Inches
public virtual string Inches
{
get
{
return this._Inches;
}
set
{
this._Inches = value;
this.CM = value * INCHES_TO_CM_CONSTANT;
}
}
For all solution (except Formula/DAC attributes) I think a condition to stop recursion should be possible if it's absolutely necessary:
if (this.CM != value * INCHES_TO_CM_CONSTANT)
this.CM = value * INCHES_TO_CM_CONSTANT;
Ideally proper use/avoidance of SetValueExt to control when events are raised (Ext method raises events) would be enough to stop infinite loops.
I have a customization to the Release Time Activities screen (EP507020) where I add a user field. This user field will contain the result of fetching the Appointment status from the Appointments screen (FS300200) based on the Appointment ID that I've also added to the Release Time Activities screen grid.
This was done so that the process grid could be filtered for appointment status that were a certain value. The Appointment Status User field I've added contains the same attributes that the Status field contains on the Appointments screen, with the Cache extension looking as follows:
#region UsrApptStatus
public abstract class usrApptStatus : IBqlField
{
}
[PXDBString(1)]
[FSAppointment.status.ListAtrribute]
[PXUIField(DisplayName = "Appt Status",Enabled = false)]
public virtual string UsrApptStatus { get; set; }
#endregion
This works fine when I fetch the status as follows in a Graph extension:
protected virtual void EPActivityApprove_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var epactivityapprove = (EPActivityApprove)e.Row;
if (epactivityapprove != null)
{
FSxPMTimeActivity rowExt = epactivityapprove.GetExtension<FSxPMTimeActivity>();
if (rowExt != null)
{
var appointmentID = rowExt.AppointmentID;
var fsappt = (FSAppointment)PXSelect<FSAppointment,
Where<FSAppointment.appointmentID, Equal<Required<FSAppointment.appointmentID>>>>.Select(Base, appointmentID);
var epactivityapproveext = PXCache<EPActivityApprove>.GetExtension<EPActivityApproveExt>(epactivityapprove);
epactivityapproveext.UsrApptStatus = fsappt.Status;
}
}
}
The problem is that when I go to filter the Status column, no matter what I choose from the list of options, it clears all rows. I have no idea why this wouldn't work, but I'm sure I'm missing something.
Grid before filtering:
Filter:
Grid after filter:
It seems to me that changing the value in the RowUpdated event is interfering with the filter. If you wrote both extensions, it would help to have both custom fields in the same extension and use the PXFormula attribute to set the value of your status based on the AppointmentID field. This way you wouldn't have to rely on the event:
#region UsrApptStatus
public abstract class usrApptStatus : IBqlField
{
}
[PXDBString(1)]
[FSAppointment.status.ListAtrribute]
[PXFormula(typeof(Selector<appointmentID, FSAppointment.status>))]
[PXUIField(DisplayName = "Appt Status",Enabled = false)]
public virtual string UsrApptStatus { get; set; }
#endregion
I want to hide or update a field on the UI based on conditions of another field.
For example, if I have a field called Color:
[PXUIField(DisplayName="Color")]
[PXStringList("Red,Blue,Other")]
[PXDefault("Red")]
And text field for comments only shown when "Other" is selected, how is this accomplished?
The requested behavior can either be accomplished either with a series of event handlers or with a bunch of attributes. You can find several examples on how to subscribe to the RowSelected and FieldUpdated events in the T200 training course, available at Acumatica University and Acumatica Open University
Going with field attributes is a more convenient and way easier option for your particular scenario. I would recommend setting CommitChanges to True for the drop-down, so the Comments field is cleared and disabled/enabled immediately after the user updates Color. Also, it's very to have your Color declared after Comments, so the framework will process Comments field first and always clear the current Comments value after the Color field got updated.
public class Other : Constant<string>
{
public Other() : base("Other") { }
}
public abstract class comments : IBqlField { }
[PXDBString(255, IsUnicode = true)]
[PXUIField(DisplayName = "Comments")]
[PXUIEnabled(typeof(Where<color, Equal<Other>>))]
[PXFormula(typeof(Default<color>))]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
public string Comments { get; set; }
public abstract class color : IBqlField { }
[PXDBString(10, IsUnicode = true)]
[PXUIField(DisplayName = "Color")]
[PXStringList("Red,Blue,Other")]
[PXDefault("Red")]
public string Color { get; set; }
The only way to conditionally hide/show editor on a form is though the RowSelected event handler:
public void YourDAC_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
YourDAC row = e.Row as YourDAC;
if (row == null) return;
PXUIFieldAttribute.SetVisible<YourDAC.comments>(sender, row, row.Color == "Other");
}
I believe, in the T200 training course, there are several examples on the PXUIFieldAttribute.SetVisible method.
In Acumatica project , on Receipt screen have a lookup button "Add Item". When Inventory lookup show dialog ( it's FromDetail template ), i will scan barcode from my bill. But, i must scan second times it to working.
How can i to filter grid details and set checked a row filtered according barcode ? And how can to know row count filtered? . Please help me.
The below figure illustrates.
snip code
Screen Receipt
You will be in a much better position with FieldVerifying handler implemented as follows for the INSiteStatusFilter.BarCode field to split scanned barcode and assign the new values both to BarCode field (though the PXFieldVerifyingEventArgs.New property) and custom unbound FilterQtySelected field. Then inside the FieldUpdated handler you will select all records from the detail grid, that satisfy filter condition and set Qty. Selected according to your unbound FilterQtySelected field:
public class INReceiptEntryExt : PXGraphExtension<INReceiptEntry>
{
public class INSiteStatusFilterExt : PXCacheExtension<INSiteStatusFilter>
{
#region QtySelected
public abstract class filterQtySelected : PX.Data.IBqlField
{
}
[PXQuantity]
public virtual decimal? FilterQtySelected { get; set; }
#endregion
}
public void INSiteStatusFilter_BarCode_FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
var barCode = (string)e.NewValue;
string[] codes = barCode.Split(';');
decimal qtySelected;
if (codes.Length == 3 && decimal.TryParse(codes[2], out qtySelected))
{
e.NewValue = codes[1];
sender.GetExtension<INSiteStatusFilterExt>(e.Row).FilterQtySelected = qtySelected;
return;
}
sender.GetExtension<INSiteStatusFilterExt>(e.Row).FilterQtySelected = null;
}
public void INSiteStatusFilter_BarCode_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
INSiteStatusFilter filter = e.Row as INSiteStatusFilter;
var filterExt = filter.GetExtension<INSiteStatusFilterExt>();
if (string.IsNullOrEmpty(filter.BarCode) || !filterExt.FilterQtySelected.HasValue) return;
foreach(INSiteStatusSelected record in Base.sitestatus.Select())
{
record.Selected = true;
record.QtySelected = sender.GetExtension<INSiteStatusFilterExt>(e.Row).FilterQtySelected;
Base.sitestatus.Update(record);
}
}
}
You will need to add your custom 2D format support on the existing barcode infrastructure screens via a customization project.