I'm using this selector and i want to sort (Asc) by LineNbr
PXSelector(typeof(Search<Choixfournisseur.inventoryCD, Where<Choixfournisseur.reqNbr, Equal<Current<Choixfournisseur.reqNbr>>>,**OrderBy<Asc<Choixfournisseur.lineNbr>>**>), new Type[] { typeof(Choixfournisseur.lineNbr), typeof(Choixfournisseur.orderQty),typeof(Choixfournisseur.itemDesc),typeof(Choixfournisseur.curyEstUnitCost),typeof(Choixfournisseur.curyEstExtCost),typeof(Choixfournisseur.inventory) },ValidateValue = false, Filterable = true)]
But it doesnt work
enter image description here
Thanks for your feedback
A workaround you can do, is that you can create a projection which is ordered by the LineNbr field. In the selector you can reference the projection instead of the Main DAC. The selector should then reflect the results as you want them.
Related
I'm new to Acumatica, could you please help me? I have too screens IN202500 (stock items) and SO301000(sales orders). I added a field to stock items and now I need to show a value from that field in grid column of sale orders for each stock items. I suppose that I need to use PXDefault attribute for this?
There are a number of ways you can do this. I'll provide 3 possibilities.
If your View used by the grid contains InventoryItem, you may be able simply to select your custom field from InventoryItem and add it directly to the screen. I'll assume this is not an option or you likely would have found it already.
Create a custom field in a DAC extension on SOLine where you add your custom field as unbound (PXString, not PXDBString) and then use PXDBScalar or PXFormula to populate it. I haven't used PXDBScalar or PXFormula to retrieve a value from a DAC Extension, so I'll leave it to you to research. I do know this is super easy if you were pulling a value directly from InventoryItem, so worth doing the research.
Create as an unbound field as in #2, but populate it in the SOLine_RowSelecting event. This is similar to JvD's suggestion, but I'd go with RowSelecting because it is the point where the cache data is being built. RowSelected should be reserved, in general, for controlling the UI experience once the record is already in the cache. Keep in mind that this will require using a new PXConnectionScope, as Acuminator will advise and help you add. (Shown in example.) In a pinch, this is how I would do it if I don't have time to sort out the generally simpler solution provided as option 2.
Code for Option 3:
#region SOLine_RowSelecting
protected virtual void _(Events.RowSelecting<SOLine> e)
{
SOLine row = (SOLine)e.Row;
if (row == null)
{
return;
}
using (new PXConnectionScope())
{
SOLineExt rowExt = row.GetExtension<SOLineExt>();
InventoryItem item = SelectFrom<InventoryItem>
.Where<InventoryItem.inventoryID.IsEqual<#P.AsInt>>
.View.Select(Base, row.InventoryID);
InventoryItemExt itemExt = item.GetExtension<InventoryItemExt>();
rowExt.UsrSSMyDatAField = itemExt.UsrSSMyDataField;
}
}
#endregion
I have a custom graph with multiple grids under different tabs. Each grid has a column for sales order order numbers, which have redirect actions to the SOOrderEntry graph. One of the grids use an action with this code to find and insert the sales order into the redirected graph, and this works fine:
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr);
However, using that same code in an action for a different grid does not work. Stepping through the debugger shows that
graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr);
returns an empty result.
Each action has DependOnGrid=true set in the pages data source, each grid has SyncPosition=true. Even when hardcoding a string order number into the search parameter it returns an empty result.
The workaround I've found to work is to use
PXSelect<SOOrder>.Search<SOOrder.orderNbr, SOOrder.orderType>(graph, row.OrderNbr, row.OrderType);
within the actions that aren't working.
Has anyone experienced this or maybe has some insight into what's going on here? It's strange to me that very similar redirect actions within the same graph need different code to work properly.
Edit: adding the view definitions
This is the view that uses the action with Search to find the needed document.
public PXSelectReadonly2<
SOOrder,
InnerJoin<BAccount,
On<SOOrder.customerID, Equal<BAccount.bAccountID>>,
LeftJoin<STSalesTerritory,
On<BAccountExt.usrTerritoryID, Equal<STSalesTerritory.territoryID>>>>,
Where2<
Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>,
And<SOOrder.status, NotEqual<SOOrderStatus.completed>,
And<SOOrder.status, NotEqual<SOOrderStatus.invoiced>,
And<SOOrder.status, NotEqual<SOOrderStatus.cancelled>>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNotNull,
And<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
And<Current<STSalesTerritoryInqFilter.repID>, IsNull,
And<BAccountExt.usrTerritoryID, IsNull,
And<SOOrder.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>>>>,
Or<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNotNull,
Or<Current<STSalesTerritoryInqFilter.territoryID>, IsNotNull,
Or<Current<STSalesTerritoryInqFilter.repID>, IsNotNull>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNull,
Or<STSalesTerritory.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
Or<STSalesTerritory.territoryID, Equal<Current<STSalesTerritoryInqFilter.territoryID>>>>,
And<Where<Current<STSalesTerritoryInqFilter.repID>, IsNull,
Or<STSalesTerritory.repID, Equal<Current<STSalesTerritoryInqFilter.repID>>>>>>>>>>>>>>>
OpenSOOrders;
And this is the view with the action where the search doesnt work, and a PXSelect is needed to find the right document
public PXSelectJoinGroupBy<
Customer,
InnerJoin<Address,
On<Customer.defAddressID, Equal<Address.addressID>>,
InnerJoin<SOOrder,
On<Customer.bAccountID, Equal<SOOrder.customerID>>,
InnerJoin<BAccount,
On<Customer.bAccountID, Equal<BAccount.bAccountID>>,
InnerJoin<STSalesTerritory,
On<BAccountExt.usrTerritoryID, Equal<STSalesTerritory.territoryID>>>
>>>,
Where2<
Where<SOOrder.orderDate, GreaterEqual<Current<STSalesTerritoryInqDateFilter.startDate>>,
And<SOOrder.orderDate, LessEqual<Current<STSalesTerritoryInqDateFilter.endDate>>>>,
And<
Where2<
Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>,
Or<SOOrder.orderType, Equal<SOOrderTypeConstantsExt.websiteOrder>>>,
And<
Where2<
Where<SOOrder.status, NotEqual<SOOrderStatus.hold>,
And<SOOrder.status, NotEqual<SOOrderStatus.cancelled>,
And<SOOrder.status, NotEqual<SOOrderStatus.voided>,
And<Customer.customerClassID, NotEqual<marketplaceCustomerClassID>
>>>>,
And<
Where2<
Where<Current<STSalesTerritoryInqFilter.territoryID>, IsNull,
Or<BAccountExt.usrTerritoryID, Equal<Current<STSalesTerritoryInqFilter.territoryID>>>>,
And<Where<Current<STSalesTerritoryInqFilter.ownerID>, IsNull,
Or<STSalesTerritory.ownerID, Equal<Current<STSalesTerritoryInqFilter.ownerID>>>>>>>>>>>>,
Aggregate<
GroupBy<Customer.acctCD>>,
OrderBy<SOOrder.orderDate.Desc>>
NewCustomers;
I am not sure I understand enough of what you are doing to be able to help, but I'll try.
The primary key of SOOrder is CompanyID, OrderType, OrderNbr - that means you should search for SOOrder by OrderType and OrderNbr. You did that in the "workaround" but not the first attempt.
If your grids return values and you just need to make the Order Nbr field a clickable link to the SOOrderEntry screen, add the field to the view (in the screen editor) under the grid's "Levels" section and then mark the OrderNbr field as AllowEdit = true to make it a clickable. Assuming you are showing the OrderNbr in the grid from the SOOrder table, the SOOrder DAC defines the primary graph of SOOrder as SOOrderEntry and will know how to redirect there for you.
If you really need to extract the current SOOrder from the view OpenSOOrders to manually redirect to the order, you access the "Current SOOrder" from the view (made possible by your grid setting "SyncPosition=true") via OpenSOOrders.Current. For example, you can find the current SOOrder, create an instance of the SOOrderEntry graph, and set the current SOOrder (Document) as follows:
SOOrder order = OpenSOOrders.Current;
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = order;
This should work because order is the exact record in the database. To search with just the order type and number, you would use:
graph.Document.Current = graph.Document.Search<SOOrder.orderType, SOOrder.orderNbr>
(order.OrderType, order.OrderNbr);
For completeness, if SOOrder was being updated in a method that you are overriding, you would use Locate instead of Search. Search will find the record in the database, but Locate will find it in the cache. This means that if the method updated values then you will retrieve those updates rather than wiping them out with a fresh copy of the record that Search would retrieve. Locate uses the key fields of the specified record to locate the record in the cache as shown below.
graph.Document.Current = graph.Document.Locate(order);
As for the view not finding the current record for you, it seems you aggregating by Customer. I don't use aggregate often, but I belive this means the results will be returned summarized by customer. If there is more than 1 OrderNbr value, it cannot be shown in the aggregate. As the second view aggregates by Customer, you would need to allow the user to select orders of the customer and use Current from THAT view.
If you want your aggregate in 1 grid and have it drive a view of SOOrders of the selected customer, you would add a new view (and secondary grid) for SOOrder as follows:
public PXSelect<SOOrder,
Where<SOOrder.customerID, Equals<Current<Customer.bAccountID>>>> CustomerOrders;
Just to put a plug in for FBQL, you could write that as:
public SelectFrom<SOOrder>
.Where<SOOrder.customerID.IsEqual<Customer.bAccountID.FromCurrent>>
.View CustomerOrders;
The SyncPosition on NewCustomers should cause CustomerOrders to refresh as you change rows of NewCustomers. Again, on this view, I would make the OrderNbr field of SOOrder a clickable link by using AllowEdit = true.
I have encountered scenarios in pages with 2 primary keys where the Search<> is invoked as follows:
graph.Document.Search<SOOrder.orderNbr>(row.OrderNbr, row.OrderType);
There are multiple references in Acumatica's source code. A good example can be found in the SOInvoiceEntry graph >> RecalcUnbilledTax() method:
soOrderEntry.Document.Current = soOrderEntry.Document.Search<SOOrder.orderNbr>(order.SOOrderNbr, order.SOOrderType);
Your second view has an aggregated selection, grouped by Customer.AcctCD. For the same customer you might have multiple orders: which one should be displayed and opened? By default, Acumatica generates the SQL with MAX for unspecified columns.
I would try to take the aggregation out of the view and see if it works.
I have a customization that overrides the Purchase Order Line Account field drop-down lookup selector on the Purchase Order screen. It populates like I want, but the account selected from the dropdown doesn't display in the grid field after chosen. Here it is:
[PXSelector(
typeof(Search5<Account.accountID,
InnerJoin<PMCostBudget, On<Account.accountGroupID, Equal<PMCostBudget.accountGroupID>>>,
Where2<Where<PMCostBudget.projectID, Equal<Current<POLine.projectID>>, Or<Current<POLine.projectID>, IsNull>>,
And2<Where<PMCostBudget.projectTaskID, Equal<Current<POLine.taskID>>, Or<Current<POLine.taskID>, IsNull>>,
And2<Where<PMCostBudget.costCodeID, Equal<Current<POLine.costCodeID>>, Or<Current<POLine.costCodeID>, IsNull>>,
And<Where<Current<POLine.lineType>, Equal<POLineType.nonStock>, Or<Current<POLine.lineType>, IsNull>>>>>>,
Aggregate<GroupBy<Account.accountID>>,
OrderBy<Asc<Account.accountCD>>>),
DescriptionField = typeof(Account.description),
Filterable = false,
SelectorMode = PXSelectorMode.DisplayModeValue
)]
It basically filters on the line type, project, task, and cost code selected on the same PO line. What am I missing or doing wrong so that the selected AccountCD value will display?
Assuming the rest of your customization properly handled the override and the selector itself works (I think that is what you are saying)... If you need to display AccountCD, you should add SubstituteKey = typeof(Account.accountCD). Without that, the selector is set right now to display the AccountID.
[PXSelector(
typeof(Search5<Account.accountID,
InnerJoin<PMCostBudget, On<Account.accountGroupID, Equal<PMCostBudget.accountGroupID>>>,
Where2<Where<PMCostBudget.projectID, Equal<Current<POLine.projectID>>, Or<Current<POLine.projectID>, IsNull>>,
And2<Where<PMCostBudget.projectTaskID, Equal<Current<POLine.taskID>>, Or<Current<POLine.taskID>, IsNull>>,
And2<Where<PMCostBudget.costCodeID, Equal<Current<POLine.costCodeID>>, Or<Current<POLine.costCodeID>, IsNull>>,
And<Where<Current<POLine.lineType>, Equal<POLineType.nonStock>, Or<Current<POLine.lineType>, IsNull>>>>>>,
Aggregate<GroupBy<Account.accountID>>,
OrderBy<Asc<Account.accountCD>>>),
SubstituteKey = typeof(Account.accountCD),
DescriptionField = typeof(Account.description),
Filterable = false,
SelectorMode = PXSelectorMode.DisplayModeValue
)]
On PXSelector, the first "typeof" is the value to be selected. You can add subsequent typeof() references if you want to designate fields to display in the PXSelector (if your intent is to display an actual selector).
SubstituteKey = typeof(DAC Field) alters the selector's display to show the designated field rather than the actual value. It is very common to select the recordID field and substitute the recordCD field.
DescriptionField displays the field designated after the displayed valued (the specifically selected field or the SubstituteKey field if specified). I could be wrong, but I don't believe this shows when the selector is displayed in a grid. I believe it only applies to form fields, such as if you toggle the grid row to a form view.
Filterable is optional, but it allows you to set filters in the selector, such as when you have a lot of records to retrieve and may want to quickly locate a value. I believe there is some overhead to using it, so setting to false as you did could be a tiny performance gain.
I never used SelectorMode before, so you taught me something new here! If adding SubstituteKey does not resolve your issue, you might try removing the SelectorMode line to see if that resolves your issue. By the way it reads, that could be changing the behavior of the selector from what I would expect.
Acumatica support's suggestion solved my problem. Since the Account id is a segmented key, I needed to manually change the PXSegmentedValue to a PXSelector in the .ASPX to get it to work. I also got it to work without that change by using a PXDimensionSelector instead of a plain PXSelector in the override.
[PXDimensionSelector(AccountAttribute.DimensionName,
typeof(Search5<Account.accountID>...
I am trying to make a field required on the line item of an AP Invoice, the Tax Category field. However when I change the field to be required I run into the problem of the detail total and the balance to no longer update on the form.
What I've tried doing is eliminating the PersistingCheck = PXPersistingCheck.Nothing of the PXDefault attribute of the TaxCategoryID. This causes the field to be required on the form, however as I've stated, it also causes the form to no longer update totals. I've tried changing the PersistingCheck to PXPersistingCheck.Null, but this also prevents the totals from being updated.
Originally the PXDefault attribute for the Tax Category field is as follows:
[PXDefault(typeof(Search<InventoryItem.taxCategoryID,
Where<InventoryItem.inventoryID, Equal<Current<APTran.inventoryID>>>>),
PersistingCheck = PXPersistingCheck.Nothing)]
This is what my code is:
[PXDefault(typeof(Search<InventoryItem.taxCategoryID,
Where<InventoryItem.inventoryID, Equal<Current<APTran.inventoryID>>>>))]
What I want is to be able to have the Tax Category field required and the totals to be updated as usual, but I am not able to due to something in the code preventing the totals to be updated when the PXDefault attribute of the Tax Category field is changed.
Is there anything additional I must do in order for these issues to be resolved or am possibly going about this the wrong way?
You need to correctly change the PersistenceCheck and add Required=true to PXUIFieldAttribute for showing a red asterisk symbol near the column's name. Please see the example of how to do that using PXMergeAttributesAttribute and PXCustomizeBaseAttribute:
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
#region Event Handlers
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXCustomizeBaseAttribute(typeof(PXUIFieldAttribute), nameof(PXUIFieldAttribute.Required),true)]
[PXCustomizeBaseAttribute(typeof(PXDefaultAttribute), nameof(PXDefaultAttribute.PersistingCheck), null)]
protected virtual void APTran_TaxCategoryID_CacheAttached(PXCache cache)
{
}
#endregion
}
Current customization project I'm working on has the requirement of displaying / editing a grid with a "Sort Order" for records. The "SortOrder" field is read only with up/down buttons to allow the user to re-order the items in the grid.
The "SortOrder" column in the DAC is a simple Int field.
The PXSelect statement for the grid is using a OrderBy>> to display the records.
The Grid in the ASPX is a defined with "SyncPosition= true"
I've added an Up/Down button that increments/decrements the "SortOrder" value for the current selected record.
The issue that I'm running into is that the first time "Up" or "Down" is clicked, the "SortOrder" field is updated however the rows do not move. Once I click Save to persist the update, the grid then refreshes with the right order.
I've looked through the the rest of the code but all other situations where this is used is for treeviews, not grids.
I've tried adding a View.RequestRefresh() at the end of my Action but this doesn't cause the reorder.
What would be the best way without a Persist after each move to get the Grid to update and reflect the current order from the cache values? As usual I'm assuming I'm overlooking something simple.
Any advice would be appreciated.
I had a look at the generic inquiry designer source code - it has an up/down button in the grid to reorder the fields. The views don't have an OrderBy clause:
public PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>>> Parameters;
OrderBy is not necessary because the LineNbr field is a key field - system automatically orders the records by the key fields.
public abstract class lineNbr : IBqlField { }
[PXDBInt(IsKey = true)]
[PXDefault]
[PXLineNbr(typeof(GIDesign))]
[PXParent(typeof(Select<GIDesign,
Where<GIDesign.designID, Equal<Current<GIFilter.designID>>>>))]
public virtual int? LineNbr { get; set; }
The code for the button looks like this:
[PXButton(ImageKey = Sprite.Main.ArrowUp, Tooltip = ActionsMessages.ttipRowUp)]
[PXUIField(DisplayName = ActionsMessages.RowUp, MapEnableRights = PXCacheRights.Update)]
protected void moveUpFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter prev = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Less<Current<GIFilter.lineNbr>>>>, OrderBy<Desc<GIFilter.lineNbr>>>.Select(this);
if (prev != null)
this.SwapItems(this.Parameters.Cache, prev, this.Parameters.Current);
}
[PXButton(ImageKey = Sprite.Main.ArrowDown, Tooltip = ActionsMessages.ttipRowDown)]
[PXUIField(DisplayName = ActionsMessages.RowDown, MapEnableRights = PXCacheRights.Update)]
protected void moveDownFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter next = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Greater<Current<GIFilter.lineNbr>>>>, OrderBy<Asc<GIFilter.lineNbr>>>.Select(this);
if (next != null)
this.SwapItems(this.Parameters.Cache, next, this.Parameters.Current);
}
The SwapItems function is shared between all the move up / move down actions:
private void SwapItems(PXCache cache, object first, object second)
{
object temp = cache.CreateCopy(first);
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(first, field.Name, cache.GetValue(second, field.Name));
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(second, field.Name, cache.GetValue(temp, field.Name));
cache.Update(first);
cache.Update(second);
}
Finally, there's a bit of JavaScript code in the ASPX code - it may or may not be what you're missing to get the feature to work correctly; i'm not exactly sure what it's doing but would encourage you to open SM208000.aspx in an editor and look for commandResult. Also check out the CallbackCommands that are defined on the grids which support up/down - it may have something to do with it.