I have segmented INLocation.LocationCD and need to restrict the PXSelector to include or exclude a particular segment based on the screen where the location is being entered. I have been able to access the DAC extension field in the displayed columns of the PXSelector, but the field is evaluated as null during the Search<> parameter of PXSelector.
I have tried:
referencing INLocationExt.myField directly,
making an inherited DAC to define the custom field directly,
and creating a PXProjection in hopes that the additional layer would cause the unbound field to be retrieved to populate the PXSelector in case the field was not being loaded in time for the Search<>.
Key points:
This is an unbound field in the DAC extension
It returns an value based evaluating the last segment of the INLocation.LocationCD
Generic Inquiries properly display this value
I have been unable to make PXSelector return any values when I reference the unbound field unless I simply check And
The field defined in my DAC extension:
[PXString(1)]
[PXUIField(DisplayName = "Condition")]
[ConditionType.List]
public String UsrSSCondition
{
get
{
if (LocationCD == null || LocationCD.Length == 0) return ConditionType.Undefined;
switch (LocationCD.Substring(LocationCD.Length - 1, 1))
{
case "N":
return ConditionType.New;
case "R":
return ConditionType.Repair;
case "C":
return ConditionType.Core;
case "U":
return ConditionType.Used;
default:
return ConditionType.Undefined;
}
}
}
public abstract class usrSSCondition : PX.Data.BQL.BqlString.Field<usrSSCondition> { }
The PXSelector:
[PXSelector(typeof(Search<INLocation.locationID, Where<INLocation.receiptsValid, Equal<True>,
And<INLocationExt.usrSSCondition, NotEqual<ConditionType.core>>>>),
typeof(INLocation.locationCD),
typeof(INLocation.active),
typeof(INLocation.primaryItemID),
typeof(INLocation.primaryItemClassID),
typeof(INLocationExt.usrSSCondition),
typeof(INLocation.receiptsValid),
SubstituteKey = typeof(INLocation.locationCD))]
The PXProjection:
[Serializable]
[PXCacheName("SSCS INLocation")]
[PXProjection(typeof(Select<INLocation>))]
public partial class SSINLocation : IBqlTable
{
#region LocationID
[PXDBInt(IsKey = true, BqlField = typeof(INLocation.locationID))]
public int? LocationID { get; set; }
public abstract class locationID : PX.Data.BQL.BqlInt.Field<locationID> { }
#endregion
#region LocationCD
[PXDBString(BqlField = typeof(INLocation.locationCD))]
public String LocationCD { get; set; }
public abstract class locationCD : PX.Data.BQL.BqlString.Field<locationCD> { }
#endregion
#region UsrSSCondition
[PXDBString(BqlField = typeof(INLocationExt.usrSSCondition))]
public String UsrSSCondition { get; set; }
public abstract class usrSSCondition : PX.Data.BQL.BqlString.Field<usrSSCondition> { }
#endregion
#region ReceiptsValid
[PXDBBool(BqlField = typeof(INLocation.receiptsValid))]
public bool? ReceiptsValid { get; set; }
public abstract class receiptsValid : PX.Data.BQL.BqlBool.Field<receiptsValid> { }
#endregion
#region Active
[PXDBBool(BqlField = typeof(INLocation.active))]
public bool? Active { get; set; }
public abstract class active : PX.Data.BQL.BqlBool.Field<active> { }
#endregion
#region PrimaryItemID
[PXDBInt(BqlField = typeof(INLocation.primaryItemID))]
public int? PrimaryItemID { get; set; }
public abstract class primaryItemID : PX.Data.BQL.BqlInt.Field<primaryItemID> { }
#endregion
#region PrimaryItemClassID
[PXDBInt(BqlField = typeof(INLocation.primaryItemClassID))]
public int? PrimaryItemClassID { get; set; }
public abstract class primaryItemClassID : PX.Data.BQL.BqlInt.Field<primaryItemClassID> { }
#endregion
}
I have tried simple versions and various combinations of those to no avail. How do I leverage my "condition" in the Search<> clause of the PXSelector?
Edit 1:
Picture of PXSelector returning values as a column - does not work as a PXRestrictor or as a where clause in Select<>.
Edit 2: More info
I have simplified the DAC extension to use PXFormula and pick out the last character of the LocationCD retrieved by PXFormula to set the value.
We need to use the last segment of the LocationCD to manage the condition of the part in a bin. This would allow us to separate cost as well as manage MRO Spares for maintenance as New, Used, Repaired, and Requiring Repair while also allowing the ability to specify other conditions later (like NCM if received damaged, etc.) if needed. While some materials can be used globally, materials of certain conditions need to be available under certain use cases. My intended strategy is to apply the rules to the last segment of the Location CD to allow the PXSelector to control user entry, either as a DAC extension on INLocation or as a Cache_Attached in the relevant graphs, if necessary.
I created a DAC extension on INLocation for usrSSCondition as a PXString. My latest attempt was to use a PXFormula to pull the LocationCD value and then custom code on the set{} to pick out the last segment and set the code for the relevant condition. (This technique was actually new to me, and a response in the stackoverflow post guided me to the idea.)
When using in a PXSelector as a displayed column, I can see the value. However, the Select<> does not allow me to tap into that segment or custom PXString field used to display that condition. I was hoping that some "behind the scenes magic" would evaluate my PXString field to limit the results, but it seems that the field is returned as null during the Select and then processed in a later step of DAC processing. When I think about what a Select is doing, it would make sense that data not stored in the database cannot be used to filter the results. PXRestrictor does not impact it either.
1) Is there a way to get my DAC to process the PXString value before the PXSelector applies the where clause?
2) Is this something I need to take to an attribute for post-processing? (If so, any suggestions on where to look for a simple example?)
Updated DAC:
#region usrSSCondition
private String _condition;
[PXString]
[PXUIField(DisplayName = "Condition")]
[PXFormula(typeof(INLocation.locationCD))]
[ConditionType.List]
public String UsrSSCondition
{
get { return _condition; }
set
{
string Loc = value;
if (Loc == null || Loc.Length == 0)
{
_condition = ConditionType.Undefined;
}
else
{
_condition = (Loc.Substring(Loc.Length - 1, 1)) switch
{
"N" => ConditionType.New,
"R" => ConditionType.Repair,
"C" => ConditionType.Core,
"U" => ConditionType.Used,
_ => ConditionType.Undefined,
};
}
}
}
Do not ever use code inside the getter, it won't be work properly in BQL expressions!
If you wanna check Loc.Substring(Loc.Length - 1, 1) somewhere in BQL just write your own BQL function
public class ConditionTypeBySegment<Source> : BqlFunction, IBqlOperand, IBqlCreator
where Source : IBqlOperand
{
private IBqlCreator _source;
public void Verify(PXCache cache, object item, List<object> pars, ref bool? result, ref object value)
{
if (!getValue<Source>(ref _source, cache, item, pars, ref result, out value) || value == null)
return;
if (value is string strValue)
{
switch (strValue.Substring(strValue.Length - 1, 1))
{
case "N":
value = ConditionType.New;
break;
case "R":
value = ConditionType.Repair;
break;
case "C":
value = ConditionType.Core;
break;
case "U":
value = ConditionType.Used;
break;
default:
value = ConditionType.Undefined;
break;
}
return;
}
value = ConditionType.Undefined;
}
public bool AppendExpression(ref SQLExpression exp, PXGraph graph, BqlCommandInfo info, BqlCommand.Selection selection)
{
...
return true;
}
}
or use a combination of existing functions. For example:
[PXSelector(typeof(Search<INLocation.locationID,
Where<INLocation.receiptsValid, Equal<True>,
And<Substring<FABookBalance.deprToPeriod, Sub<StrLen<FABookBalance.deprToPeriod>, int1>, int1>, NotEqual<ConditionTypes.tCore>>>>),
typeof(INLocation.locationCD),
typeof(INLocation.active),
typeof(INLocation.primaryItemID),
typeof(INLocation.primaryItemClassID),
typeof(INLocationExt.usrSSCondition),
typeof(INLocation.receiptsValid),
SubstituteKey = typeof(INLocation.locationCD))]
public static class ConditionTypes
{
public class tNew : PX.Data.BQL.BqlString.Constant<tNew> { public tNew() : base("N") { } }
public class tRepair : PX.Data.BQL.BqlString.Constant<tRepair> { public tRepair() : base("R") { } }
public class tCore : PX.Data.BQL.BqlString.Constant<tCore> { public tCore() : base("C") { } }
public class tUsed : PX.Data.BQL.BqlString.Constant<tUsed> { public tUsed() : base("U") { } }
}
Simple solution - simplify. Changed the field to simply hold the value and then let the BLC set the value when the locaitonCD value is set. On creation of the record, the locationCD field is empty, so defining the FieldDefaulting logic causes the condition to be undefined initially. By monitoring FieldUpdated of the LocationCD, we can then reapply the FieldDefaulting rules to the "real" value.
DAC field definition:
#region usrSSCondition
[PXDBString]
[PXUIField(DisplayName = "Condition")]
[ConditionType.List]
public String UsrSSCondition { get; set; }
public abstract class usrSSCondition : PX.Data.BQL.BqlString.Field<usrSSCondition> { }
#endregion
Event Handlers in the BLC:
#region INLocationExt_UsrSSCondition_FieldDefaulting
protected void INLocation_UsrSSCondition_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
INLocation row = (INLocation)e.Row;
string Loc = row?.LocationCD;
if (Loc == null || Loc.Length == 0)
{
e.NewValue = ConditionType.Undefined;
}
else
{
e.NewValue = (Loc.Substring(Loc.Length - 1, 1)) switch
{
ConditionType.New => ConditionType.New,
ConditionType.Repair => ConditionType.Repair,
ConditionType.Core => ConditionType.Core,
ConditionType.Used => ConditionType.Used,
_ => ConditionType.Undefined,
};
}
}
#endregion
#region INLocation_LocationCD_FieldUpdated
protected void _(Events.FieldUpdated<INLocation.locationCD> e)
{
INLocation row = (INLocation)e.Row;
e.Cache.SetDefaultExt<INLocationExt.usrSSCondition>(row);
}
#endregion
Since locations are defined in INSiteMaint, the event handlers in that graph allow setting the field value to store in the database without any translation. That enables use of PXRestrictorAttribute to limit available locations accordingly or write rules to set the location flags on the INSiteMaint screen.
Below is one example of CacheAttached to add a PXRestrictor to prevent receipt into a Core location type unless it is done from the NcmTag Screen. (A heavy hand controlling what locations a user may select is not needed universally, so this was not applied globally to the DAC field.)
#region INTran_LocationID_CachedAttached
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRestrictor(typeof(Where<INLocationExt.usrSSCondition, NotEqual<ConditionType.core>,
Or<Current<AccessInfo.screenID>, Equal<SSCS.Constants.NcmTagScreenID>>>), "")]
#endregion
Also worth noting, since my purpose is to use a character of the LocationCD for the end user to recognize the location type, I have to prevent the user from changing the LocationCD value through the RowSelected event for INLocation.
#region INLocation_RowSelected
protected void _(Events.RowSelected<INLocation> e)
{
INLocation row = e.Row;
if(row?.SiteID != null)
{
INLocationExt rowExt = row.GetExtension<INLocationExt>();
PXUIFieldAttribute.SetEnabled<INLocation.locationCD>(e.Cache, row,
!DisableLocationRename(row?.LocationID));
}
}
#endregion
#region DisableLocationRename
protected virtual bool DisableLocationRename(int? locationID)
{
int counter = PXSelect<SSINNcmTag,
Where<SSINNcmTag.locationID, Equal<Required<SSINNcmTag.locationID>>,
And<SSINNcmTag.tranRefNbr, IsNull>>>
.SelectSingleBound(Base, null, locationID).Count;
if (counter > 0) return true;
counter = PXSelect<INLocationStatus,
Where <INLocationStatus.locationID, Equal<Required<INLocationStatus.locationID>>,
And<INLocationStatus.qtyOnHand, Greater<DecimalZero>>>>
.SelectSingleBound(Base, null, locationID).Count;
if (counter > 0) return true;
return false;
}
#endregion
While we have the ability to write some very interesting code, it is important to stop once in a while to ask, "Why am I making this complicated?" Whenever possible, simplify.
There is master-child table and master record has composite key on OrderNbr and RevisionNbr,
On UI we want to present the Selector for both the fields
OrderNbr with Max Revision number for Order number selector
Revision Number selector based on order Number selected ON UI.
also on UI we have function to create New revision, How we can create revision by Code, I am getting error of MUlti part query, when I defined IsKey= true for both the fields and tried to Save the data By
Graph.primaryView.Insert()
ForEach(UIRow){
Graph.ChildView.Insert
}
Graph.Persist();
Note the MAster table has Identity column in the SQL server has Key
Update-
Parent Table DAC -
#region TestSuiteID
public abstract class testSuiteID:PX.Data.IBqlField {
}
protected int? _TestSuiteID;
[PXDBIdentity()]
public virtual int? TestSuiteID {
get {
return this._TestSuiteID;
}
set {
this._TestSuiteID = value;
}
}
#endregion
#region TestSuiteCD
public abstract class testSuiteCD:PX.Data.IBqlField {
}
protected string _TestSuiteCD;
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
[PXDefault()]
[PXUIField(DisplayName = "Test Group ID")]
[PXSelector(typeof(EWQCTestSuite.testSuiteCD), typeof(EWQCTestSuite.testSuiteCD), typeof(EWQCTestSuite.revisionNo), typeof(EWQCTestSuite.name))]
public virtual string TestSuiteCD {
get {
return this._TestSuiteCD;
}
set {
this._TestSuiteCD = value;
}
}
#endregion
#region RevisionNo
public abstract class revisionNo:PX.Data.IBqlField {
}
protected int? _RevisionNo;
[PXDBInt(IsKey=true)]
[PXDefault(1)]
[PXUIField(DisplayName = "Revision No")]
public virtual int? RevisionNo {
get {
return this._RevisionNo;
}
set {
this._RevisionNo = value;
}
}
#endregion
Child Table Dac -
#region TestSuiteVariableID
public abstract class testSuiteVariableID:PX.Data.IBqlField {
}
protected int? _TestSuiteVariableID;
[PXDBIdentity(IsKey = true)]
public virtual int? TestSuiteVariableID {
get {
return this._TestSuiteVariableID;
}
set {
this._TestSuiteVariableID = value;
}
}
#endregion
#region TestSuiteID
public abstract class testSuiteID:PX.Data.IBqlField {
}
protected int? _TestSuiteID;
[PXDBInt()]
[PXDBDefault(typeof(EWQCTestSuite.testSuiteID))]
[PXParent(typeof(Select<EWQCTestSuite, Where<EWQCTestSuite.testSuiteID, Equal<Current<EWQCTestSuite.testSuiteID>>>>))]
public virtual int? TestSuiteID {
get {
return this._TestSuiteID;
}
set {
this._TestSuiteID = value;
}
}
#endregion
Note - Child table does not have MAster tab CD and revision number column as I added Identity column fr reference and have PXParent with Identity column.
Both Dac has other fields that I have not added here.
Other issue that I am facing is When I am Deleting the record I am getting some Primarykey reference error (Delete by default acumatica delete button on Primary DataView)
The multi-part query error is usually related to a master-detail (parent-child) query that resolves to more than one parent when executed. The child DAC should have a PXParent attribute that includes where clause on all the key fields.
[PXParent(typeof(Select<Master,
Where<Master.key1, Equal<Current<Child.key1>>,
And<Master.key2, Equal<Current<Child.key2>>>>>>))]
You should make sure all BQL queries in the DAC and the Graph that have such a relationship include a where filter or on join clause on all keys. Also make sure all key fields in DAC have IsKey=true and that the matching database fields where created as key fields in the database.
For composite key ..Instead of using PXGraph use PXRevisionableGraph.
This graph is part of manufactoring module and supports composite key..
All your key should be decorated with attribute inherited by AcctSubAttribute
I'm lost and not sure I am going about this the right way.
I have placed a PXAction Button on the EP305000 screen.
EP305000 Screen
When the button is pressed I wanted to search for any records in the PMTimeActivity table for the current user that has the UsrPIXIClockIn NOT null AND the UsrPIXIClockOut Null.
If found, need to set its value to the current time. If not found then create a new record with the UsrPIXIClockIn field set to the current time.
However, my first attempt of just trying to read the Usr fields is creating an error. When I try to parse a PXResultSet, code lines containing my Usr Fields are throwing an error when compiling.
There error is: 'PX.Objects.CR.PMTimeActivity' does not contain a definition for 'UsrPIXIClockIn' and no extension method 'UsrPIXIClockIn' accepting a first argument of type 'PX.Objects.CR.PMTimeActivity'
I am not sure where this definition belongs or how to define it. Here is the Code I have:
[Serializable]
public class PMTimeActivityExt : PXCacheExtension<PMTimeActivity>
{
public PXSelect<PMTimeActivity> PMTimeActivity;
#region UsrPIXIClockIn
[PXDBTime(DisplayMask = "t", UseTimeZone = false)]
[PXUIField (DisplayName="Clock In")]
public virtual DateTime? UsrPIXIClockIn { get; set; }
public abstract class usrPIXIClockIn : IBqlField { }
#endregion
#region UsrPIXIClockOut
[PXDBTime(DisplayMask = "t", UseTimeZone = false)]
[PXUIField (DisplayName="Clock Out")]
public virtual DateTime? UsrPIXIClockOut { get; set; }
public abstract class usrPIXIClockOut : IBqlField { }
#endregion
#region UsrPIXITotalHours
[PXDBDecimal]
[PXUIField (DisplayName="Total Hours")]
public virtual decimal? UsrPIXITotalHours { get; set; }
public abstract class usrPIXITotalHours : IBqlField { }
#endregion
}
public class TimeCardMaint_Extension : PXGraphExtension<TimeCardMaint>
{
public PXSelect<PMTimeActivity> PMTimeActivity;
public PXAction<EPTimeCard> PunchCard;
public PXAction<PX.Objects.EP.EPTimeCard> PunchTimeCard;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Punch Time Card")]
protected void punchTimeCard(PXCache cache)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("Started...\n");
EPEmployee employeeByUserID = PXSelect<EPEmployee, Where<EPEmployee.bAccountID, Equal<Current<EPTimeCard.employeeID>>>>.Select(this.Base);
Guid userID = (Guid) employeeByUserID.UserID;
sb.AppendLine("user ID:" + userID.ToString() + "\n");
// This one returns all records.
PXResultset<PMTimeActivity> TimeActivity = PXSelect<PMTimeActivity>.Select(this.Base);
foreach (PMTimeActivity timeRecord in TimeActivity)
{
if (timeRecord.OwnerID == userID) {
sb.AppendLine("UsrPIXIClockIn:" + timeRecord.UsrPIXIClockIn + "\n");
}
}
sb.AppendLine("\nEnded...\n");
throw new PXException("Clicked Punch Time Card!\n" + sb.ToString());
}
}
As for the PXSelect for the PXResultSet I tried this:
PXResultset<PMTimeActivity> TimeActivity = PXSelect<PMTimeActivity, Where<Required<PMTimeActivity.ownerID>, Equal<Current<PMTimeActivity.ownerID>>>>.Select(this.Base, userID);
But no records are ever found. What am I doing wrong?
The error message boils down to: PMTimeActivity does not contain UsrPIXIClockIn.
PMTimeActivity is the base DAC and the user fields are in the extended DAC PMTimeActivityExt.
You need to fetch the extension from the base DAC first using GetExtension method and access your user field on the extension record timeRecordExt.UsrPIXIClockIn:
foreach (PMTimeActivity timeRecord in TimeActivity)
{
PMTimeActivityExt timeRecordExt = timeRecord.GetExtension<PMTimeActivityExt>();
if (timeRecordExt != null && timeRecord.OwnerID == userID) {
sb.AppendLine("UsrPIXIClockIn:" + timeRecordExt.UsrPIXIClockIn + "\n");
}
}
In your question I see there's a data view declaration in the DAC extension. Maybe that's a typo, you should remove it as DAC extension should only contain user fields. This is important because Acumatica aggressively parses these code structures. So remove that line from PMTimeActivityExt class:
public PXSelect<PMTimeActivity> PMTimeActivity;
I want to add Attributes on InventoryID lookup on POLine. I have written below code and in adding a column on InventoryId Lookup. I want to make the attribute field filterable. I am not sure how to do it. Can anyone please help me with this? And also I wanted to add the same filterable for InventoryId on ADD Item lookup on PO Screen.
public class PXAddAtttributeColumns : CRAttributesFieldAttribute
{
string[] _names;
public PXAddAtttributeColumns(string[] names, Type classID,
Type noteID)
: base(classID, noteID)
{
_names = names;
}
public override void CacheAttached(PXCache sender)
{
this._IsActive = true;
base.CacheAttached(sender);
}
protected override void AttributeFieldSelecting(PXCache sender, PXFieldSelectingEventArgs e, PXFieldState state, string attributeName, int idx)
{
if (_names.Any(attributeName.Equals))
{
state.DisplayName = (!String.IsNullOrEmpty(state.DisplayName)) ? (state.DisplayName.Replace("$Attributes$-", "")) : attributeName;
state.Visible = true;
state.Visibility = PXUIVisibility.Dynamic;
}
base.AttributeFieldSelecting(sender, e, state, attributeName, idx);
}
}
public class InventoryItemExt : PXCacheExtension<PX.Objects.IN.InventoryItem>
{
public abstract class itemAttributes : IBqlField { }
[InventoryItemMaint_Extension.PXAddAtttributeColumns(new[] {
"COLOR" },
typeof(InventoryItem.itemClassID),
typeof(InventoryItem.noteID))]
public virtual string[] ItemAttributes { get; set; }
}
You can set FastFilterFields property for the POLine grid to make the attribute column filterable.
Example: FastFilterFields="COLOR_ItemAttributes"
This should allow you to filter the grid based on your attribute column in InventoryID lookup.
I want to show which user created Invoice, for this i have added default Acumatica field, but the label is showing as Created By, how can i change that label name to "Invoice Created By". Please have a look at below screenshot for field am referring to.
You could use PXUIFieldAttribute.SetDisplayName static method to change DAC field’s display name. This change will be applicable only for Sales Invoice Entry Graph (SO303000 screen)
public class SOInvoiceEntryPXDemoExt : PXGraphExtension<SOInvoiceEntry>
{
public override void Initialize()
{
PXUIFieldAttribute.SetDisplayName<ARInvoice.createdByID>(Base.Document.Cache, "Invoice Created By");
}
}
If you need display name changed for this field in all screens, you need to have DAC Extension as below:
With this, attributes specified in an extension DAC apply to DAC class in every Graph of the application unless a Graph replaces them with other attributes.
public class ARInvoicePXDemoExt : PXCacheExtension<ARInvoice>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXUIField(DisplayName = "Invoice Created By", Enabled = false, IsReadOnly = true)]
public virtual Guid? CreatedByID { get; set; }
}
You need to add CreatedByID field on screen SO303000
And set DisplayMode property to Text.
The 'CreatedByID' I believe is an audit field and therefore cannot easily change the ui of the additional data fields available through the control. The resolution I would suggest is a non database backed UI field that is populated during row selecting. Example can be found below :
public class SOOrderEntryExtension : PXGraphExtension<SOOrderEntry>
{
public virtual void SOOrder_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
SOOrder row = e.Row as SOOrder;
if(row != null)
{
SOOrderExtension rowExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(row);
Users user = PXSelectReadonly<Users, Where<Users.pKID, Equal<Required<Users.pKID>>>>.Select(this.Base, row.CreatedByID);
if(user != null)
{
rowExt.InvoiceCreatedBy = user.DisplayName;
}
}
}
}
public class SOOrderExtension : PXCacheExtension<SOOrder>
{
public abstract class invoiceCreatedBy : PX.Data.IBqlField
{
}
[PXString]
[PXUIField(DisplayName = "Invoice Created By")]
public virtual string InvoiceCreatedBy { get; set; }
}