Filtering PXselector with search - acumatica

I have a custom grid using a custom dac that I have declared. Originally i had a PXselector attribute set to POOrder.orderNbr
With this selector it grabs all POOrders in my grid selector
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXSelector(typeof(POOrder.orderNbr))]
[PXUIField(DisplayName = "Po#")]
public string Po { get; set; }
public class po : IBqlField { }
But i want the selector to filter POOrders that appear on the Document details->POReceiptLine. I attempted to filter using the Search but only get the lowest value POOrder.ordnbr on the receipt. The images below should illustrate what I mean. I want it to show all POOrder.orderNbr but it's only retrieving the first lowest value order.
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXSelector(typeof(Search<POOrder.orderNbr,Where<POOrder.orderNbr,Equal<Current2<POReceiptLine.pONbr>>>>))]
[PXUIField(DisplayName = "Po#")]
public string Po { get; set; }
public class po : IBqlField { }

I believe it would be much better if you inner join POReceiptLine DAC, and then aggregate associated Purchase Orders to eliminate possible duplicates:
[PXSelector(typeof(Search5<POOrder.orderNbr,
InnerJoin<POReceiptLine, On<POReceiptLine.pOType, Equal<POOrder.orderType>,
And<POReceiptLine.pONbr, Equal<POOrder.orderNbr>,
And<POReceiptLine.receiptNbr, Equal<Current<POReceipt.receiptNbr>>>>>>,
Aggregate<GroupBy<POOrder.orderType,
GroupBy<POOrder.orderNbr>>>>))]

The main issue in the selector is that you are looking for POOrder where the order Number equals the current Receipt Line. That means that depending the line selected, the value will change but will always yield only one result. The solution would then be to change the condition to be on the Purchase Receipt document instead.
To do so you have two options. Either you search directly POReceiptLine.poNbr, but then your selector will display columns from that table, or you join POOrder to POReceiptLine.
With search on POReceiptLine
[PXSelector(typeof(Search<POReceiptLine.pONbr,
Where<POReceiptLine.receiptType,
Equal<Optional<POReceipt.receiptType>>,
And<POReceiptLine.receiptNbr,
Equal<Optional<POReceipt.receiptNbr>>>>>), DirtyRead = true)]
With search on POOrder
[PXSelector(typeof(Search2<POOrder.orderNbr,
InnerJoin<POReceiptLine, On<POReceiptLine.pOType,
Equal<POOrder.orderType>,
And<POReceiptLine.pONbr,
Equal<POOrder.orderNbr>>>>,
Where<POReceiptLine.receiptType,
Equal<Optional<POReceipt.receiptType>>,
And<POReceiptLine.receiptNbr,
Equal<Optional<POReceipt.receiptNbr>>>>>))]
Also notice the parameter DirtyRead = true. This parameter will tell the selector to get it's information in the cache and not only from the Database. Because you are selecting something from the same page, you would need to save the whole document before being able to select the record otherwise. Unfortunatly it would not work with the second option because the Join is done at the database level.

Related

How to Override the InventoryCD Number Sequence

I have a request to change the number sequencer for a new Item based on the Class that is selected for the new item. So, the user selects a particular class and the system need to find the next value for the InventoryItem.InventoryCD by using a number sequence that is set on a field in a preferences form.
Any ideas on how to do this?
I've tried the FieldDefaulting() event for the InventoryCD and InventoryID fields but, it's not working.
TIA!
A custom numbering attribute is required to achieve the functionality you desire. Please find working example below.
First we define the fields on setup which will hold our custom numbering sequences :
public class INSetupExtension : PXCacheExtension<INSetup>
{
#region InventoryNumbering1ID
public abstract class inventoryNumbering1ID : PX.Data.BQL.BqlString.Field<inventoryNumbering1ID>
{
}
[PXDBString(10, IsUnicode = true)]
[PXDefault("INV1")]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
[PXUIField(DisplayName = "Inventory Numbering One", Visibility = PXUIVisibility.Visible)]
public virtual String InventoryNumbering1ID { get; set; }
#endregion
#region InventoryNumbering1ID
public abstract class inventoryNumbering2ID : PX.Data.BQL.BqlString.Field<inventoryNumbering2ID>
{
}
[PXDBString(10, IsUnicode = true)]
[PXDefault("INV2")]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
[PXUIField(DisplayName = "Inventory Numbering Two", Visibility = PXUIVisibility.Visible)]
public virtual String InventoryNumbering2ID { get; set; }
#endregion
}
Next we define our custom Autonumbering attribute, here I have made the "default" numbering sequence the argument for the constructor declaration.
In the overridden RowPersisting event we utilize our criteria to determine the numbering sequence to use for the next persisted new item.
In my example I use one sequence if an item class is used, if no item class is selected I use another.
public class NonStockAutoNumbereAttribute : AutoNumberAttribute
{
public NonStockAutoNumbereAttribute() : base(typeof(INSetupExtension.inventoryNumbering2ID), typeof(AccessInfo.businessDate))
{
}
public override void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
InventoryItem row = e.Row as InventoryItem;
if(sender.GetStatus(row) == PXEntryStatus.Inserted)
{
base.RowPersisting(sender, e);
INSetup setup = PXSelectBase<INSetup, PXSelectReadonly<INSetup>.Config>.Select(sender.Graph, Array.Empty<object>());
INSetupExtension setupExt = setup.GetExtension<INSetupExtension>();
string nextNumber;
object newValue = sender.GetValue(e.Row, this._FieldOrdinal);
if(!string.IsNullOrEmpty((string)newValue))
{
INItemClass itemClass = PXSelectReadonly<INItemClass, Where<INItemClass.itemClassID, Equal<Required<INItemClass.itemClassID>>>>.Select(sender.Graph, row.ItemClassID);
if(itemClass != null)
{
nextNumber = AutoNumberAttribute.GetNextNumber(sender, e.Row, setupExt.InventoryNumbering1ID, DateTime.Now);
}
else
{
nextNumber = AutoNumberAttribute.GetNextNumber(sender, e.Row, setupExt.InventoryNumbering2ID, DateTime.Now);
}
sender.SetValue(e.Row, this._FieldName, nextNumber);
}
}
}
}
On the Inventory Item maintenance page I have added a simple CacheAttached :
public class NonStockItemMaintExtension : PXGraphExtension<NonStockItemMaint>
{
[PXDefault]
[NonStockAutoNumbereAttribute]
[InventoryRaw(typeof(Where<InventoryItem.stkItem.IsEqual<False>>), IsKey = true, DisplayName = "Inventory ID", Filterable = true)]
protected virtual void _(Events.CacheAttached<InventoryItem.inventoryCD> e) { }
}
The results can be seen below :
No class numbering
Class Numbering
You mentioned trying to do this on FieldDefaulting and also that you attempted on InventoryID and InventoryCD. Before we get into a couple of possible options, let's focus on the nature of InventoryID vs. InventoryCD and the FieldDefaulting event. Also, we need to consider the impact of AutoNunmber on InventoryCD.
InventoryID vs. InventoryCD
InventoryID is an Identity field. It is a sequence generated by the database on insert to allow us to uniquely identify the record even if we change other common values such as the InventoryCD, as you desire. The InventoryCD field is also known as the Inventory Code. This is the human readable number that adheres to our desired nomenclature for defining a part number. For instance, we use a 2 digit year to prefix a number sequence to clearly indicate how old the part number is. Because InventoryID is an identity field, NEVER try to update that field, or you may cause data integrity issues for all the related tables that point back to the InventoryItem record via the InventoryID field.
FieldDefaulting and SetDefaultExt
FieldDefaulting may be the place you want to set the value for InventoryCD. However, you don't know the value at the time you are creating the record. In fact, you don't even know the value when you are ready to press the save button. Unless you have configured numbering to allow manual entry, the number isn't assigned until the Persist. As the value is inserted into the database, the AutoNumber logic fires to identify the numbering sequence to generate the InventoryCD value. If you wish to use the FieldDefaulting event, you must first enable manual numbering for the Inventory numbering sequence. You then want to be sure to use SetDefaultExt on the FieldUpdated event of the item class field so that when you change item classes, you get a new inventory id. You also must decide if this value is allowed to change once assigned because the aforementioned logic will change the value every time you change the item class unless you put other measures in place.
Change ID Action
It is important to note that there is a Change ID action on the Action menu. It is intended for this very purpose, although it is a manual entry. I'd recommend that you explore the code behind that action to understand how Acumatica goes about altering the InventoryCD. Also, note that this is performed manually after the record has been created and and InventoryCD has been assigned already. Perhaps you have a way to identify that the InventoryCD value is still the default and needs to be changed, so you could do something around overriding Persist.
Custom Attribute Inheriting AutoNumber
If you are more adventurous than I am, you could consider a custom Attribute inheriting from the AutoNumber attribute and use a cacheattached to change to your attribute.
Business Events to Initiate Custom Action
Finally, you might consider a business event to watch for when an InventoryItem record has been inserted and then fire some custom action that simulates the Change ID action but using your logic for determining the new number based on the Item Class. If you only capture when the record is created and not when it is updated, then I suspect you can use the business event to make the initial InventoryCD change per Item Class and then require manual change through Change ID if needed again later.
Each of these points gives you some possible ways to approach making the change you need as well as some potential hazards. While this doesn't give you the code achieve your objective, I hope that it gives you some options to find the solution that works best for you. Please post a comment or answer once you reach a solution that works best for you.

Field filter within the grid

I'm not good at English.
I want to know if it is possible to filter field in grid.
I have a problem I am filtering a field, but it is not updated in selector, where it should show what I want.
here I show an image.
I will thank you all very much.
here I put the code I did:
#region CategoryID
[PXDBInt()]
[PXUIField(DisplayName = "Category ID")]
[PXSelector(typeof(Search<PESKPriceIndexCat.categoryID,
Where<PESKPriceIndexCat.state, Equal<Current<stateID>>,
And<PESKPriceIndexCat.active, Equal<True>>>>),
typeof(PESKPriceIndexCat.categoryID),
typeof(PESKPriceIndexCat.categoryCD),
typeof(PESKPriceIndexCat.descripcion),
DescriptionField = typeof(PESKPriceIndexCat.descripcion), SubstituteKey =
typeof(PESKPriceIndexCat.categoryCD))]
public virtual int? CategoryID { get; set; }
public abstract class categoryID : PX.Data.BQL.BqlInt.Field<categoryID> { }
#endregion
The definition of the selector in the DAC seems to be correct, therefore one possible solution would be to set the AutoRefresh property on the selector from the layout editor to true and the SyncPosition property on the grid to true as well. With these two properties the current value will update on row change and when opening the selector it will automatically refresh with the correct dataset.

How to change the display name of description field of selector

I have a customization where I'm adding a Selector to a grid field that has a description field as follows:
[PXSelector(typeof(Search<EPEmployee.bAccountID,
Where<EPEmployee.status, Equal<SetupTypes.active>>>),
typeof(EPEmployee.acctCD),
typeof(EPEmployee.acctName),
SubstituteKey = typeof(EPEmployee.acctCD),
DescriptionField = typeof(EPEmployee.acctName))]
My problem is that the field has a description (in this case, 'Employee Name') field that is automatically added, but I can't find a way to change the display name of that field.
Since I have another field that uses the same employee lookup, they both have the same 'Employee Name' description field - so I have no way of knowing which description goes with which Selector field unless I choose a value and see the description show up in its associated 'Employee Name' field.
Is there a way to change the display name of that description field? I've tried CacheAttached and the RowSelected event with a PXUIFieldAttribute.SetDisplayName, but nothing seems to work.. seems that 'field_description' is an automatically added field that doesn't exist anywhere in the DAC to be able to change the display name.
Per Acumatica support, the only way that this can be done is as follows:
The only way that seems to work is to have two different DACs that are projections on the EPEmployee DAC and use each in the separate selector attribute.
For instance:
[PXProjection(typeof(EPEmployee))]
public class EPEmployeeTest : IBqlTable
{
#region BAccountID
...
#endregion
#region AcctCD
...
#endregion
public abstract class acctName : PX.Data.BQL.BqlString.Field<acctName> { }
[PXDBString(60, IsUnicode = true, BqlField=typeof(EPEmployee.acctName))]
[PXUIField(DisplayName = "Employee Name New Label", Enabled = false, Visibility = PXUIVisibility.SelectorVisible)]
public override string AcctName{get; set;}
}

Show unbound field with PXFormula

I'm trying to show a value calculated with PXFormula but the field doesn't show the value.
I have my CustomDAC named EDITran
public class EDITran : IBqlTable
{
#region Doctype
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Doctype")]
[PXStringList
(new string[]
{"SO", "SHI", "INV" },
new string[]
{"Sales Order", "Shipment", "Invoice"}
)]
public virtual string Doctype { get; set; }
public abstract class doctype : PX.Data.BQL.BqlString.Field<doctype> { }
#endregion
#region Erprefnbr
[PXDBString(30, IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "ERP RefNbr")]
public virtual string Erprefnbr { get; set; }
public abstract class erprefnbr : PX.Data.BQL.BqlString.Field<erprefnbr> { }
#endregion
#region Sync
[PXDBBool()]
[PXUIField(DisplayName = "Sync")]
public virtual bool? Sync { get; set; }
public abstract class sync : PX.Data.BQL.BqlBool.Field<sync> { }
#endregion
}
}
so I what to show the value of sync field on the Sales Order screen. The key is the ERP RefNbr (which would be SOOrder.OrderNbr)
I have added the custom non persisted field on the SOOrderExt DAC with this attributes
using PX.Objects.SO;
[PXBool]
[PXUIField(DisplayName="EDI Sync" , Enabled = false)]
[PXFormula(typeof(Selector<SOOrder.orderNbr,
Selector<EDITran.erprefnbr,
EDITran.sync>>))]
But when I added a record in EDITran and try to visualize it in SOOrder Form and I checked that EDITran.Sync = 1, it doesn't show the saved value.
Sales Order Screen
What I'm I doing wrong? Is the PXFormula correctly used?
PXFormula used incorrectly. PXFormula attribute works only with current DAC (which is SOOrder) or foreign DAC, existing in PXSelector join condition (you can use Selector keyword to get it).
For example, here is the SOOrder.OrderNbr selector declaration
[SO.RefNbr(typeof(Search2<SOOrder.orderNbr,
LeftJoinSingleTable<Customer, On<SOOrder.customerID, Equal<Customer.bAccountID>,
And<Where<Match<Customer, Current<AccessInfo.userName>>>>>>,
Where<SOOrder.orderType, Equal<Optional<SOOrder.orderType>>,
And<Where<Customer.bAccountID, IsNotNull,
Or<Exists<Select<SOOrderType,
Where<SOOrderType.orderType, Equal<SOOrder.orderType>,
And<SOOrderType.aRDocType, Equal<ARDocType.noUpdate>,
And<SOOrderType.behavior, Equal<SOBehavior.sO>>>>>>>>>>,
OrderBy<Desc<SOOrder.orderNbr>>>), Filterable = true)]
public virtual String OrderNbr
you can get some fields from the related Customer record, using Selector keyword
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
Customer.consolidateStatements>))]
As a result, there are two possible solutions:
1) Rewrite SOOrder.OrderNbr selector declaration with your EDITran DAC
...
[SO.RefNbr(typeof(Search2<SOOrder.orderNbr,
LeftJoinSingleTable<Customer, On<SOOrder.customerID, Equal<Customer.bAccountID>,
And<Where<Match<Customer, Current<AccessInfo.userName>>>>>,
LeftJoin<EDITran, On<EDITran.doctype, Equal<SOOrder.orderType>,
And<EDITran.erprefnbr, Equal<SOOrder.orderNbr>>>>>,
Where<SOOrder.orderType, Equal<Optional<SOOrder.orderType>>,
And<Where<Customer.bAccountID, IsNotNull,
Or<Exists<Select<SOOrderType,
Where<SOOrderType.orderType, Equal<SOOrder.orderType>,
And<SOOrderType.aRDocType, Equal<ARDocType.noUpdate>,
And<SOOrderType.behavior, Equal<SOBehavior.sO>>>>>>>>>>,
OrderBy<Desc<SOOrder.orderNbr>>>), Filterable = true)]
public virtual String OrderNbr
Then it will be possible to get you field from there
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
EDITran.sync>))]
2) Use PXDBScalar attribute to just get what you need. Note this will be a separate request to the database!!
...
[PXBool]
[PXDBScalar(typeof(Search<EDITran.sync,
Where<EDITran.doctype, Equal<SOOrder.orderType>,
And<EDITran.erprefnbr, Equal<SOOrder.orderNbr>>>>))]
public virtual bool? Sync
I'm not sure that's the right use of PXFormula. Here's the reference I normally use when I need to use PXFormula. See if it helps you simplify your PXFormula. You may need to simplify to a single Selector and define a foreign key to be able to complete your lookup.
Also, are you sure that the database contains the values that you expect? I often find that my problem is not where I think, and your issue may lie in setting the values or writing them to the database in your business logic.
It appears that you need the PXFormula to look to your EDITran table to find the record identified by the key (1st field specified in Selector) to then return back the value of the second field specified, which would reside in EDITran.
From Acumatica Developers Blog (AsiaBlog):
Selector
Selector does following:
Fetches a PXSelectorAttribute defined on the foreign key field (KeyField) of the current DAC.
Fetches the foreign data record currently referenced by the selector.
Using calculates and returns an expression on that data record as defined by ForeignOperand.
public class APVendorPrice : IBqlTable
{
// Inventory attribute is an aggregate containing a PXSelectorAttribute
// inside, which is also valid for Selector<>.
[Inventory(DisplayName = "Inventory ID")]
public virtual int? InventoryID
[PXFormula(typeof(Selector<
APVendorPrice.inventoryID,
InventoryItem.purchaseUnit>))]
public virtual string UOM { get; set; }
}
From that, I'd expect your PXFormula to look more like:
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
EDITran.sync>))]
... assuming you have enough defined to tell Acumatica how to relate SOOrder.orderNbr to EDITran.erprefnbr.

Get combination of CD and description for PXSelector

I'm trying to use a Selector to lookup up customers, and I want it to show the CD along with the description in the field. I've seen this many times in Acumatica - and I thought I knew how to do it, but it's not working. Here is my code:
#region CustomerLookup
public abstract class customerLookup : PX.Data.IBqlField
{
}
protected string _CustomerLookup;
[PXDBString(100, IsUnicode = true)]
[PXUIField(DisplayName = "Customer Lookup")]
[PXSelector(typeof(Customer.acctCD)
,typeof(Customer.acctCD)
,typeof(Customer.acctName)
,DescriptionField=typeof(Customer.acctName))]
public virtual string CustomerLookup
{
get
{
return this._CustomerLookup;
}
set
{
this._CustomerLookup = value;
}
}
#endregion
I would have thought providing the DescriptionField would take care of this, but it does not.
Before answering the question, let me first mention 2 major issues with the code snippet above:
In Acumatica Customers is one the specific system object types, that supports segmented keys. To create a look up for records, that support segmented keys, you should be using the PXDimensionSelectorAttribute instead of the PXSelectorAttribute. For Customer entity there are several attributes provided out of the box, like the CustomerAttribute or the CustomerActiveAttribute, that you can use to create Customer lookups:
CustomerLookup field must be of the Int32? (int?) type: in Acumatica users can change Customer ID with time (via the Change ID button on the Customers screen). To establish a foreign key for the Customer DAC, the better approach is to declare an integer field and associate your custom record with Customers though the Customer.BAccount field
Below is the recommended declaration of a DAC field, that creates Customer lookup in UI:
[Serializable]
public partial class DetailsResult : IBqlTable
{
...
#region CustomerId
public abstract class customerId : PX.Data.IBqlField
{
}
[Customer(DescriptionField = typeof(Customer.acctName))]
public virtual Int32? CustomerID { get; set; }
#endregion
...
}
With the DescriptionField property specified, PXDimensionSelector placed on a form will by default format selected record according to the "{SearchKey/SubstituteKey} - {DescriptionField}" pattern:
To show DescriptionField in a grid cell, you have to define additional column for the CustomerID_description field (where _description part is a special key word for Acumatica used specifically in this type of requests):
I believe the answer is one that's obvious - I don't think the description field works the same way in the grid. In a header field, it works to show the CD - Description when you navigate off of it. Grid fields apparently don't work that way, even when you specify the description field in the selector the way you would in a header selector.

Resources