Displaying GI in tab on any screen - acumatica

I want to display a GI on one of the tab in any screen.
For example, there is a new custom GI for OrderMargin which I want to display on SO screen on a new tab that will show the Order Margin for a particular order only.
OrderMargin is simple GI with SOOrder, SOLine and InventoryItem table joins and few columns required columns with margin calculations.
Can anyone suggest?

Let's say you've created a GI called SalesOrderMargin with 2 hidden parameters:
To embed this GI into the Sales Orders page, you should follow the steps below:
Declare new unbound field for SOOrder to return absolute URL for the SalesOrderMargin GI:
public class SOOrderExt : PXCacheExtension<SOOrder>
{
public abstract class marginGiUrl : IBqlField { }
[PXString]
[PXUIField(Visible = false)]
public string MarginGiUrl
{
get
{
if (string.IsNullOrEmpty(Base.OrderType) ||
string.IsNullOrEmpty(Base.OrderNbr)) return string.Empty;
string inqName = "SalesOrderMargin";
var url = new StringBuilder(PXGenericInqGrph.INQUIRY_URL)
.Append("?name=").Append(inqName)
.Append("&SOOrderType=").Append(Base.OrderType);
.Append("&SOOrderNbr=").Append(Base.OrderNbr);
.Append("&hidePageTitle=true");
return PX.Common.PXUrl.SiteUrlWithPath().TrimEnd('/') +
url.ToString().Remove(0, 1);
}
}
}
On the Sales Orders screen, add new tab with a PXSmartPanel container set up to render as an iframe:
<px:PXTabItem Text="Margins" >
<Template>
<px:PXSmartPanel runat="server" ID="panelMarginGI" RenderIFrame="True"
AutoSize-Enabled="true" SkinID="Frame" LoadOnDemand="true"/>
</Template>
</px:PXTabItem>
Place input control for the custom SOOrder unbound field declared in step 1 somewhere in the Sales Orders' top level PXFormView container (input control will always be hidden from the users and is only required to assign source URL for the PXSmartPanel):
<px:PXFormView ID="form" runat="server" DataSourceID="ds" Width="100%"
DataMember="Document" Caption="Order Summary"...>
<Template>
...
<px:PXTextEdit ID="edMarginGiUrl" runat="server" DataField="MarginGiUrl" />
</Template>
</px:PXFormView>
In SO301000.aspx insert JavaScript code to assign source URL for the PXSmartPanel:
<script type="text/javascript" language="javascript">
function commandResult(ds, context) {
var commands = ["ReloadPage", "Save", "Cancel", "Insert", "First", "Previous", "Next", "Last"];
if (commands.indexOf(context.command) >= 0) {
var marginGiUrl = px_alls["edMarginGiUrl"];
var smartpanel = px_alls["panelMarginGI"];
if (marginGiUrl || smartpanel) {
var url = marginGiUrl.getValue();
smartpanel.setPageUrl(url);
smartpanel.repaint();
}
}
}
</script>
Subscribe to the CommandPerformed event of PXDataSource to invoke the commandResult JavaScript function:
<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" TypeName="PX.Objects.SO.SOOrderEntry" PrimaryView="Document">
<ClientEvents CommandPerformed="commandResult" />
...
</px:PXDataSource>
And this is how your SalesOrderMargin GI should appear on the Sales Orders screen:

Related

Change Selector values based on Action

I am working on a process screen with an Action drop-down and a selector. I want the selector to display values based on Action selected.
For example, I have an Action drop-down of two values 1. Prepare and Release Invoice and 2. Print COnsolidated Invoice and selector which shows Sales orders with Completed status when the first Action is selected. Can anyone give me an idea on how to implement this? Below is my filter table with Action and a selector fields.
[Serializable()]
public partial class ImportInvoiceFilter : PX.Data.IBqlTable
{
public const string PrintConsolidatedInvoice = "PCI";
public const string PrepareAndReleaseInvoice = "PRI";
public class prepareAndReleaseInvoice : Constant<string> { public prepareAndReleaseInvoice() : base(PrepareAndReleaseInvoice) { } }
public class printConsolidatedInvoice : Constant<string> { public printConsolidatedInvoice() : base(PrintConsolidatedInvoice) { } }
#region ReferenceID
[PXString(30, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Customer Reference Nbr")]
[PXSelector(typeof(Search<SOOrder.customerRefNbr,
Where<Where2<Where<SOOrder.status,
Equal<SOOrderStatus.open>,
And<Current<ImportInvoiceFilter.selectAction>,
Equal<ImportInvoiceFilter.prepareAndReleaseInvoice>>>,
Or<Where2<Where<SOOrder.status,
Equal<SOOrderStatus.completed>>,
And<Current<ImportInvoiceFilter.selectAction>,
Equal<ImportInvoiceFilter.printConsolidatedInvoice>>>>>>>),
typeof(SOOrder.orderNbr),typeof(SOOrder.orderDesc),
typeof(SOOrder.customerRefNbr))]
public virtual string ReferenceID { get; set; }
public abstract class referenceID : IBqlField { }
#region SelectAction
public abstract class selectAction : PX.Data.IBqlField
{
}
protected string _SelectAction;
[PXUIField(DisplayName = "Select ")]
[PXStringList(new string[] { PrepareAndReleaseInvoice, PrintConsolidatedInvoice },
new string[] { "Prepare and Release Invoice", "Print Consolidated Invoice" })]
[PXDefault(PrepareAndReleaseInvoice)]
public virtual string SelectTemplate
{
get
{
return this._SelectAction;
}
set
{
this._SelectAction = value;
}
}
#endregion
}
ASPX:
<%# Page Language="C#"
MasterPageFile="~/MasterPages/FormDetail.master"
AutoEventWireup="true" ValidateRequest="false"
CodeFile="KN506000.aspx.cs" Inherits="Page_KN506000" Title="Untitled
Page" %>
<%# MasterType VirtualPath="~/MasterPages/FormDetail.master" %>
<asp:Content ID="cont1" ContentPlaceHolderID="phDS" Runat="Server">
<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%"
PrimaryView="Filter" TypeName="KNLANOrderProcess.OrderInvoiceProcess">
</px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phF" Runat="Server">
<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-
index: 100"
Width="100%" DataMember="Filter">
<Template>
<px:PXLayoutRule runat="server" ID="CstPXLayoutRule8" StartColumn="True" ></px:PXLayoutRule>
<px:PXDropDown runat="server" ID="CstPXDropDown10" DataField="SelectTemplate" CommitChanges="True" ></px:PXDropDown>
<px:PXSelector runat="server" ID="CstPXSelector9" DataField="ReferenceID" CommitChanges="True" AutoRefresh="True" ></px:PXSelector></Template>
</px:PXFormView>
</asp:Content>
<asp:Content ID="cont3" ContentPlaceHolderID="phG" Runat="Server">
<px:PXGrid ID="grid" runat="server" DataSourceID="ds" Style="z-index: 100"
Width="100%" Height="150px" SkinID="Details" TabIndex="300"
TemporaryFilterCaption="Filter Applied">
<EmptyMsg ComboAddMessage="No records found.
Try to change filter or modify parameters above to see records here."
NamedComboMessage="No records found as '{0}'.
Try to change filter or modify parameters above to see records here."
NamedComboAddMessage="No records found as '{0}'.
Try to change filter or modify parameters above to see records here."
FilteredMessage="No records found.
Try to change filter to see records here." FilteredAddMessage="No records
found.
Try to change filter to see records here." NamedFilteredMessage="No
records found as '{0}'.
Try to change filter to see records here." NamedFilteredAddMessage="No
records found as '{0}'.
Try to change filter to see records here." AnonFilteredMessage="No records
found.
Try to change filter to see records here." AnonFilteredAddMessage="No
records found.
Try to change filter to see records here."></EmptyMsg>
<Levels>
<px:PXGridLevel DataKeyNames="OrderType,OrderNbr,LineNbr"
DataMember="ImportInvoiceList">
<Columns>
<px:PXGridColumn DataField="OrderType">
</px:PXGridColumn>
<px:PXGridColumn DataField="OrderNbr">
</px:PXGridColumn>
<px:PXGridColumn DataField="OrderQty" TextAlign="Right" Width="100px">
</px:PXGridColumn></Columns>
</px:PXGridLevel>
</Levels>
<AutoSize Container="Window" Enabled="True" MinHeight="150" ></AutoSize>
</px:PXGrid>
</asp:Content>
Sample Screen:
Graph:
public class OrderInvoiceProcess : PXGraph<OrderInvoiceProcess>
{
#region Views
public PXCancel<ImportInvoiceFilter> Cancel;
public PXFilter<ImportInvoiceFilter> Filter;
[PXFilterable]
public PXFilteredProcessing<SOOrder, ImportInvoiceFilter> ImportInvoiceList;
public PXSelect<SOOrder, Where<SOOrder.customerRefNbr, Equal<Current<ImportInvoiceFilter.referenceID>>
>> SOOrders;
#endregion
public OrderInvoiceProcess()
{
ImportInvoiceList.SetProcessCaption("Process");
ImportInvoiceList.SetProcessVisible(false);
ImportInvoiceList.SetProcessAllCaption("Process ALL");
ImportInvoiceFilter currentFilter = this.Filter.Current;
ImportInvoiceList.SetProcessDelegate(
delegate (List<SOOrder> list)
{
ProcessOrders(list, currentFilter, true);
});
}
public IEnumerable importInvoiceList()
{
PXSelectBase<SOOrder> ImportOrderListBase = null;
ImportInvoiceFilter currentInquiryfilter = Filter.Current;
if (currentInquiryfilter != null && !string.IsNullOrEmpty(currentInquiryfilter.SelectTemplate))
{
switch (currentInquiryfilter.SelectTemplate)
{
case ImportInvoiceFilter.PrepareAndReleaseInvoice:
ImportOrderListBase = new PXSelectJoin<SOOrder, LeftJoin<SOOrderShipment, On<SOOrder.orderNbr,
Equal<SOOrderShipment.orderNbr>>>, Where2<Where<SOOrderShipment.confirmed, Equal<True>,
And<Where<SOOrderShipment.invoiceNbr, IsNull, And<SOOrder.customerRefNbr, Equal<Current<ImportInvoiceFilter.referenceID>>>>>>,
And<Where<SOOrder.orderType, Equal<salesOrderTypeRO>,
Or<SOOrder.orderType, Equal<salesOrderTypeCO>>>>>>(this);
return ImportOrderListBase.Select();
case ImportInvoiceFilter.PrintConsolidatedInvoice:
break;
}
}
return ImportOrderListBase.Select();
}
}
EDIT:
The other issue was that you were missing the base type attribute on the SelectAction field. You only had PXStringList:
[PXStringList(new string[] { PrepareAndReleaseInvoice, PrintConsolidatedInvoice },
new string[] { "Prepare and Release Invoice", "Print Consolidated Invoice" })]
PXStringList is not a base type so you should add the PXString attribute which is the base type:
[PXString(30, IsUnicode = true, InputMask = "")]
[PXStringList(new string[] { PrepareAndReleaseInvoice, PrintConsolidatedInvoice },
new string[] { "Prepare and Release Invoice", "Print Consolidated Invoice" })]
You can use your DAC filter field (selectAction) in the selector BQL query to filter the selector data based on your filter field:
[PXSelector(typeof(Search<SOOrder.orderNbr,
Where<Where2<Where<SOOrder.status, Equal<SOOrderStatus.open>, And<Current<SOImportFilter.selectAction>, Equal<SOImportFilter.createandConfirmShipment>>>,
Or<Where2<Where<SOOrder.status, Equal<SOOrderStatus.completed>>, And<Current<SOImportFilter.selectAction>, Equal<SOImportFilter.printInvoice>>>>>>>
In the above BQL request the logic of the where clause is equivalent to:
If (sales order status is open And select action is create and confirm shipment)
Or (sales order status is completed And select action is print invoice)
You can tweak it with your own rules.
For this filter to work you need to handle refresh behavior with CommitChanges and AutoRefresh properties on the ASPX controls.
You need CommitChanges set to true to send back the filter DAC modifications to the business layer as soon as the user change it:
// In a Grid
<px:PXGridColumn DataField="SelectAction" CommitChanges="True" />
// In a Form or in the RowTemplate of the Grid
<px:PXDropDown runat="server" ID="edSelectAction" DataField="SelectAction" CommitChanges="True" />
For the Sales Order selector you need AutoRefresh set to true so that the BQL query of the selector is executed every time the user opens the selector instead of using the stale values from cache:
// In a Grid RowTemplate element
<px:PXGridLevel DataMember="YourDataView"
<RowTemplate>
<px:PXSelector runat="server" ID="edSalesOrderSelector" DataField="SalesOrderSelector" AutoRefresh="True" />
</RowTemplate>
<Columns>
<px:PXGridColumn DataField="SalesOrderSelector" CommitChanges="True" />
</Columns>
</px:PXGridLevel>
// In a Form
<px:PXSelector runat="server" ID="edSalesOrderSelector" DataField="SalesOrderSelector" AutoRefresh="True" />

How to add a memo / formatted text field ala Cases

I'd like to add a tab to a screen which contains a memo / formatted text area the way the Cases screen does, e.g.:
Adding a tab is straightforward, no help necessary there, but I don't remember seeing anything about how to add this type of text area in the training courses. If there's an example I'd appreciate a point in the right direction.
You can add the RichTextEditor manually to your aspx file.
<px:PXTabItem Text="Test">
<Template>
<px:PXRichTextEdit runat="server" AllowLoadTemplate="false"
AllowAttached="true" AllowSearch="true" AllowMacros="true"
AllowSourceMode="true" DataField="YOURFIELD" ID="edDescription"
Style='width:100%;'>
<AutoSize Enabled="True" />
</px:PXRichTextEdit>
</Template>
</px:PXTabItem>
Make sure you Tab has the correct Datamember where your field used on the RichTextEditor is located.
<px:PXTab DataMember="Document" ID="tab" runat="server" Height="540px" Style="z-index: 100;" Width="100%">
Also you could mark your Field used on the RichTextEditor as PXDBText.
#region YourField
public abstract class yourField : IBqlField { }
protected String _yourField;
[PXDBText(IsUnicode = true)]
[PXUIField(DisplayName = "YOURFIELD")]
public virtual String YourField
{
get
{
return this._yourField;
}
set
{
this._yourField = value;
}
}
#endregion

Make Attribute appear as hyperlink

I have an attribute with the case number from another instance. I have to make it appear as a hyperlink so user can click on it and it can direct visit to that case.
Any suggestion.
It adds up the link to all row, but I need hyperlink only on Case Number attribute. (see below image).
Assuming you want the link inside a grid cell.
First make an action in your graph with it's primary DAC:
public PXAction<MyPrimaryDAC> viewCase;
Primary DAC can be found in the graph:
public class MyGraph : PXGraph<MyGraph, MyPrimaryDAC>
Make an event handler for the action in your graph to open the case:
[PXButton]
public virtual IEnumerable ViewCase(PXAdapter adapter)
{
// Assuming the case you want is already set as current
// Otherwise lookup case by ID if necessary
CRCase crCase = CRCases.Current as CRCase;
if (crCase != null && crCase.CaseID != null)
PXRedirectHelper.TryRedirect(this, crCase, PXRedirectHelper.WindowMode.NewWindow);
return adapter.Get();
}
Declare the action in the CallbackCommands of your ASPX page:
<asp:Content ID="cont1" ContentPlaceHolderID="phDS" runat="Server">
<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%"
PrimaryView="MyDataView" TypeName="MyNamespace.MyGraph">
<CallbackCommands>
<px:PXDSCallbackCommand CommitChanges="True" Name="ViewCase" DependOnGrid="gridCase" Visible="False" />
</CallbackCommands>
</px:PXDataSource>
</asp:Content>
Finally use LinkCommand attribute on your grid field to bind the action:
<px:PXGridColumn DataField="MyField" LinkCommand="ViewCase" />

Showing smartpanel after field value changes - grid remains empty

I have the need in one of my customization to show a popup directly after the user modifies the value of one of the controls (in this case, a custom field in the SOLine of the Sales Order Entry screen). This popup shows some additional values in a grid that the user must select before completing the row.
Using the standard process a SmartPanel was added to the screen.
If I call this from an action / PXLookupButton, the popup shows and the grid is populated correctly.
If I move this to either the "FieldUpdated" or "RowSelected" event, the smartpanel is displayed however the grid is always empty. Once more, if I then click on the button the grid stays empty till I cancel the modifications and re-enter using only the button.
I tried calling the action's press method in these events as well but the same result occurs.
Watching SQL profiler and the debugger events I can see that the BQL statement is being executed and returning the correct rows it's just not displaying in the smartpanel's grid.
Is it possible to handle this type of request? I'm assuming I need to either move this to a different method and/or pass some additional values but haven't found the right combination.
This holds true on Acumatica 5.3 / 6.1
Any input would be appreciated.
RowUpdated handler allowed me to achieve requested behavior and show SmartPanel after field value change.
Example below relies on custom unbound Trigger Dialog field declared for the SOLine DAC. When a user checks or uncheckes Trigger Dialog flag, the system will show Item Quantity dialog to update Quantity for selected SOLine record:
public class SOLineExt : PXCacheExtension<SOLine>
{
#region TriggerDialog
public abstract class triggerDialog : PX.Data.IBqlField
{
}
[PXBool]
[PXUIField(DisplayName = "Trigger Dialog")]
public virtual bool? TriggerDialog { get; set; }
#endregion
}
Very basic SmartPanel declaration in Aspx:
<px:PXSmartPanel runat="server" ID="CstSmartPanel2" Key="SOLineParam" Caption="Item Quantity" AutoRepaint="True"
CaptionVisible="True" AcceptButtonID="CstButton6" AutoReload="true" >
<px:PXFormView runat="server" ID="CstFormView3" DataMember="SOLineParam" SkinID="Transparent" >
<Template>
<px:PXLayoutRule runat="server" StartColumn="True" />
<px:PXNumberEdit runat="server" ID="CstPXNumberEdit10" DataField="OrderQty" />
</Template>
</px:PXFormView>
<px:PXLayoutRule runat="server" StartRow="True" />
<px:PXPanel runat="server" ID="CstPanel5" SkinID="Buttons">
<px:PXButton runat="server" ID="CstButton6" DialogResult="OK" CommandName="ChangeOk" CommandSourceID="ds" />
<px:PXButton runat="server" ID="CstButton7" DialogResult="Cancel" Text="Cancel" />
</px:PXPanel>
</px:PXSmartPanel>
Accomplished with the SOOrderEntry BLC extension subscribing to RowUpdated handler for the SOLine DAC to show Item Quantity dialog to a user:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
[Serializable]
public class SOLineParams : IBqlTable
{
#region OrderQty
public abstract class orderQty : PX.Data.IBqlField
{
}
[PXDBDecimal]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "Quantity")]
public virtual decimal? OrderQty { get; set; }
#endregion
}
public PXFilter<SOLineParams> SOLineParam;
public PXAction<SOOrder> ChangeOk;
[PXUIField(DisplayName = "OK")]
[PXButton(CommitChanges = true)]
protected void changeOk()
{
var lineParams = SOLineParam.Current;
Base.Transactions.Cache.SetValue<SOLine.orderQty>(Base.Transactions.Current, lineParams.OrderQty);
SOLineParam.Cache.Clear();
}
public void SOLine_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
if (!sender.ObjectsEqual<SOLineExt.triggerDialog>(e.Row, e.OldRow) && e.ExternalCall == true)
{
SOLineParam.AskExt();
}
}
}
Another part of the extension class is ChangeOk action invoked by SmartPanel to update Quantity for selected record in the Document Details grid. To hide ChangeOk action from screen toolbar, it's also necessary to add the following command into PXDataSource.CallbackCommands collection:
<px:PXDSCallbackCommand Name="ChangeOk" Visible="False" />

(Sitecore) Navigation with Subnavigation

I'm trying to build main navigation with drop down subnavigation menu anchors. I got the HTML with CSS ready, but I don't know how to do it in the sublayout and its code behind.
I have done a lot of navigations, but those were all 1-dimensional menus using asp:repeaters or asp:ListViews.
Can anyone point me to the right direction?
Essentially you will want to have nested repeaters on the number of levels (or "dimensions") you want to display on your navigation. See an example below.
<asp:Repeater runat="server" ID="TopNavRepeater" OnItemDataBound="TopNavRepeater_OnItemDataBound">
<ItemTemplate>
<sc:Link runat="server" ID="sclTopLink" Field="__Display Name" />
<asp:Repeater runat="server" ID="SecondNavRepeater" OnItemDataBound="SecondNavRepeater_OnItemDataBound">
<ItemTemplate>
<sc:Link runat="server" ID="sclSecondLink" Field="__Display Name" />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
You will want to get the children of each Item bound to the Top Repeater and bind it to the second Repeater. Use Sitecore Link Controls to render the links to the pages by settings the Items, and Fields, on the OnItemDataBound event.
See below for a rough example
protected void Page_Load(object sender, EventArgs e)
{
TopNavRepeater.DataSource = YourHomeItem.Children();
TopNavRepeater.DataBind();
}
protected void TopNavRepeater_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var item = e.Item.DataItem as Item;
if (item == null)
return;
var sclTopLink = e.Item.FindControl("sclTopLink") as Link;
var SecondNavRepeater = e.Item.FindControl("SecondNavRepeater") as Repeater;
if (sclTopLink != null)
{
sclTopLink.Item = item;
}
if (SecondNavRepeater != null)
{
SecondNavRepeater.DataSource = item.Children;
SecondNavRepeater.DataBind();
}
}
}

Resources