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.
The field in the DAC is defined like this.
#region NextMonthHours
[PXDBDecimal(2, MinValue = 0.0, MaxValue = 280.0)]
[PXUIField(DisplayName = "Next Month Hours")]
[PXDefault(TypeCode.Decimal, "0.0")]
public virtual Decimal? NextMonthHours { get; set; }
public abstract class nextMonthHours : PX.Data.BQL.BqlDecimal.Field<nextMonthHours> { }
#endregion
I change the display name of the field in RowSelected event.
PXUIFieldAttribute.SetDisplayName<EVEPPlannedHoursDetails.nextMonthHours>(sender, nextMonth+"Hours");
where nextMonth is "February".
I need to add this field to Acumatica Mobile Screen. When I go to web service schema the field name is "FebruaryHours"
<s:element minOccurs="0" maxOccurs="1" name="FebruaryHours" type="tns:Field"/>
I cannot use the name "FebruaryHours" because it changes every month but I also when I use field name NextMonthHours it is not added in the mobile screen.
Any idea how to solve this issue?
Thanks
There's quite a few ways to workaround this depending on the use case and whether the label value is static or dynamic.
If all you want to do is to change a static label in UI without having to change the display name property you can add a separate label and merge group.
Here's an example to change Billable in UI without changing DisplayName property using that technique.
Set SuppressLabel property to true to hide the original label bounded to DisplayName on UI.
Use ADD CONTROLS tab to add a Layout Rule with Merge property set to true.
Use ADD CONTROLS tab to add a label control in the merged group.
Put the original field in the merge group so they show up together on the same line in UI.
End result, label is now a UI control and wouldn't interfere with DisplayName property.
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
}
I have a custom screen with a multiple custom selectors, which change what they select based on dropdown lists.
The solution I implemented is shown in a previous case:
Dynamically changing PXSelector in Acumatica (thanks).
My challenge is twofold:
1.) If the dropdown selection is "No Lookup", then I want the PXSelector Attribute to essentially be removed - leaving just a text entry. Not sure if this is even possible...
2.) If one of the selectors (let's say Projects) is selected, I'd like the selection of the following selector (let's say Tasks) to filter based on the Project selected.
Thanks much...
1) I think the only way to do this is to create your own attribute.
Something like that:
public class PXSelectorTextEditAttribute : PXSelectorAttribute
{
bool selectorMode;
public PXSelectorTextEditAttribute(Type type, bool selectorOn):base(type)
{
selectorMode = selectorOn;
}
public override void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
if(selectorMode)
base.FieldVerifying(sender, e);
}
public static void SwitchSelectorMode(PXSelectorTextEditAttribute attribute, bool onOff)
{
attribute.selectorMode = onOff;
}
}
You will be able to turn on and off the 'selector' part of the attribute. With the field verifying turned off you will be able to put any value to the field just like in simple TextEdit field. However, the lookup button in the right end of the field still will be visible. I have no idea how to hide it.
2) This behavior can be implemented easily. You will need something like that(example based on cashaccount):
[PXSelector(typeof(Search<CABankTran.tranID, Where<CABankTran.cashAccountID, Equal<Current<Filter.cashAccountID>>>>))]
If you want to see all records when the cashaccount is not defined then you just modify the where clause by adding Or<Current<Filter.cashAccountID>, isNull>
Also don't forget to add AutoRefresh="true" to the PXSelector in the aspx. Without it your selector will keep the list of the records untill you press refresh inside of it.