Getting Combo-box values set for REST API

Hello everybody,

today I want to share idea on how to get getting Combo-box values set for REST API.

As usually values of comboboxes values are just hardcoded in web api calls, but sometimes it may be necessary to load them from Rest API, for example for cases if you want to target multiplve versions of Acumatica. For such a purpose I'd suggest to create Graph, which via reflection will read values from dlls. Quite similar to what Acumatica team does. 

Below goes source code of graph, which via reflection loads data:

using PX.Data;
using System;
using System.Collections;
using System.Reflection;
 
namespace LA
{
    public class ListAttributesInq : PXGraph<ListAttributesInq>
    {
        public PXCancel<ClassFilter> Cancel;
 
        public PXFilter<ClassFilter> Filter;
 
        public override bool IsDirty
        {
            get
            {
                return false;
            }
        }
 
        public ListAttributesInq()
        {
            Records.Cache.AllowInsert = false;
            Records.Cache.AllowDelete = false;
            Records.Cache.AllowUpdate = false;
 
            Actions["Process"].SetVisible(false);
            Actions["ProcessAll"].SetVisible(false);
            Actions["Schedule"].SetVisible(false);
        }
 
        [PXFilterable]
        [PXVirtualDAC]
        public PXFilteredProcessing<KeyValueRecordClassFilter> Records;
 
        protected IEnumerable records()
        {
            //var row = new ClassFilter { ClassName = "PX.Objects.CR.CRMSourcesAttribute" };
            var row = Filter.Current;
            if (row != null && !string.IsNullOrWhiteSpace(row.ClassName))
            {
                var type = Type.GetType(row.ClassName) ??
                            Type.GetType(row.ClassName + ", PX.Objects");
 
                if (type != null)
                    switch (type.BaseType.Name)
                    {
                        case "PXIntListAttribute":
                            {
                                int[] values;
                                string[] labels;
                                GetRecords(typeout valuesout labels);
 
                                for (int i = 0; i < values.Length; i++)
                                    yield return new KeyValueRecord { Key = values[i].ToString(), UiValue = labels[i] };
                                break;
                            }
                        case "PXStringListAttribute":
                            {
                                string[] valueslabels;
                                GetRecords(typeout valuesout labels);
 
                                for (int i = 0; i < values.Length; i++)
                                    yield return new KeyValueRecord { Key = values[i], UiValue = labels[i] };
                                break;
                            }
                    }
            }
        }
 
        private void GetRecords<T>(Type typeout T[] valuesout string[] labels)
        {
            var obj = Activator.CreateInstance(type);
            var flags = BindingFlags.NonPublic | BindingFlags.Instance;
 
            values = type.GetField("_AllowedValues"flags).GetValue(objas T[];
            labels = type.GetField("_AllowedLabels"flags).GetValue(objas string[];
        }
    }
}

 

It has few features:

  1. Method get records, which creates instance, and fills values
  2. Method records, which is delegate overload, and which distinguishes between PXIntListAttribute and PXStringListAttribute.
  3. yield return purpose of it is to be pageble ( for page opening it doesn't have big value ) and getting all records at once
  4. As entry, it is needed to enter class with it's namespace. Like PX.Objects.CR.CRMSourcesAttribute, and not like CRMSources

Aspx source code looks like this:

<%@ Page Language="C#" MasterPageFile="~/MasterPages/FormDetail.master" AutoEventWireup="true" ValidateRequest="false" CodeFile="LA401000.aspx.cs" Inherits="Page_LA401000" 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%"
        TypeName="LA.ListAttributesInq"
        PrimaryView="Filter">
		<CallbackCommands></CallbackCommands>
	</px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phF" Runat="Server">
    <px:PXFormView SkinID="" ID="form" runat="server" DataSourceID="ds" DataMember="Filter" Width="100%" Height="" AllowAutoHide="false">
		<Template>
			<px:PXLayoutRule ID="PXLayoutRule1" runat="server" StartRow="True"></px:PXLayoutRule>
			<px:PXTextEdit CommitChanges="True" runat="server" ID="CstPXTextEdit1" DataField="ClassName" ></px:PXTextEdit></Template>
	</px:PXFormView>
</asp:Content>
<asp:Content ID="cont3" ContentPlaceHolderID="phG" Runat="Server">
    <px:PXGrid AllowFilter="True" AllowPaging="True" AllowSearch="True" SyncPosition="True" ID="grid" runat="server" DataSourceID="ds" Width="100%" Height="150px" SkinID="Inquire" AllowAutoHide="false">
		<Levels>
			<px:PXGridLevel DataMember="Records">
			    <Columns>
				<px:PXGridColumn DataField="Key" Width="120" ></px:PXGridColumn>
				<px:PXGridColumn DataField="UiValue" Width="180" ></px:PXGridColumn></Columns>
			
				<RowTemplate>
					<px:PXTextEdit runat="server" ID="CstPXTextEdit2" DataField="Key" ></px:PXTextEdit>
					<px:PXTextEdit runat="server" ID="CstPXTextEdit3" DataField="UiValue" ></px:PXTextEdit></RowTemplate></px:PXGridLevel>
		</Levels>
		<AutoSize Container="Window" Enabled="True" MinHeight="150" ></AutoSize>
		<ActionBar >
		</ActionBar>
	</px:PXGrid>
</asp:Content>

One more necessary DAC Class ClassFilter looks like this:

using PX.Data;
using System;
 
namespace LA
{
    [Serializable]
    public class ClassFilter : IBqlTable
    {
        #region ClassName
        public abstract class className : PX.Data.IBqlField
        {
        }
        [PXString(50, IsUnicode = true, InputMask = "")]
        [PXUIField(DisplayName = "Class Name")]
        public string ClassName { getset; }
        #endregion
    }
}

 

DAC class KeyValueRecord looks very trivial in this case:

using PX.Data;
using System;
 
namespace LA
{
    [Serializable]
    public class KeyValueRecord : IBqlTable
    {
        #region Key
        public abstract class key : PX.Data.IBqlField
        {
        }
        [PXString(10, IsUnicode = true, InputMask = "")]
        [PXUIField(DisplayName = "Key")]
        public string Key { getset; }
        #endregion
        #region Value
        public abstract class uiValue : PX.Data.IBqlField
        {
        }
        [PXString(50, IsUnicode = true, InputMask = "")]
        [PXUIField(DisplayName = "Value")]
        public string UiValue { getset; }
        #endregion
    }
}

And the end result looks like this:

Summary

If you need to read something from Attributes, just use reflection.

Create payment proc

Hello everybody,

today I want to write a few words about method CreatePaymentProc in graph SOOrderEntry.

Among different features of this method, want to describe that it have for some reason out parameter! Take a look on it's declaration:

public virtual void CreatePaymentProc(SOOrder orderout PXGraph targetstring paymentType = ARPaymentType.Payment)
{

 

as you see, it accepts as a parameter PXGraph. Inside of open part of Acumatica source code there is only one more file, which has similar staff: ServiceOrderCore. Take a look on it:

public static void CreatePrepayment(FSServiceOrder fsServiceOrderRowFSAppointment fsAppointmentRowout PXGraph targetstring paymentType = ARPaymentType.Payment)
{

But similarities not finished. Take notice how those methods utilize PXGraph target. First goes ServiceOrderCore:

ARPaymentEntry graphARPaymentEntry = PXGraph.CreateInstance<ARPaymentEntry>();
target = graphARPaymentEntry;

And second one goes SOOrderEntry:

public virtual void CreatePaymentProc(SOOrder orderout PXGraph targetstring paymentType = ARPaymentType.Payment)
{
	ARPaymentEntry docgraph = PXGraph.CreateInstance<ARPaymentEntry>();
	target = docgraph;

And now you'd probably ask, and so what? How can I use it? One of the very useful ways would be getting graph of ARPaymentEntry after calling of the method. 

In one of my projects it was needed to call method CreatePaymentProc, and persisting of payment but without showing dialog window of creation of Payment. Below goes code how I've achieved it:

 
PXLongOperation.StartOperation(Base, delegate()
{
    PXGraph target;
    Base.CreatePaymentProc(Base.Document.Current, out target);
    var arPaymentEntry = target as ARPaymentEntry;
    //here some additional manipulations may be added to in memory created payment
    arPaymentEntry.Persist();
});

Summary

If you have a need to call method CreatePaymentProc, and then massage data after execution of that method, jast ignore showing pop up window and with help of type casting and additional changes make it. Afterwards call Persist.

 

Acumatica Application Architecuture

Hello everybody,

today I want to leave a short note about Acumatica Architecture. Take a look on this picture:

As you can see from the schema business logic controllers is kind of single source of truth. Acumatica doesn't have dependencies between UI and Web services. Also it means, if you hide something from UI on the page, it will be also hidden from Mobile app.

But want to say about few exceptions as well.

Suppose following scenario. You make a graph, and you know for sure, that from UI standpoint, it's logicall to load some visualization data, but from API call there is no reason to call some piece of code. How to inform Acumatica about it?

For this purpose you can use IsContractBasedAPI method of class graph. It may look like this:

if (!Base.IsContractBasedAPI)
{
    // Do something for UI
}
else
{
    //Do Something for non UI
}

Among other interesting modes of functionality, it is worthy to mention also IsMobile ( do something only for mobile apps ), IsExport ( do something in scope of export scenario ), IsImport ( do something in scope of Import scenario ), IsCopyPasteContext ( do something in scope of copy/paste functionality )

Summary

Taking into account all of these facts, you can be almost certain that in majority of cases Acumatica will execute pieces of logic like on the UI of Acumatica instance. But it will not be 100% times. So next time, when you API call give you data different of what you see in UI, check base source code, maybe there is the issue.

 

Acumatica summit 2020

Hello everybody,

today I want to write a few words or kind of summary of Acumatica summit 2020. 

First of all want to say that for me it was very interesting to see people which have big or even huge desire to make Acumatica grow. 

And also want to post some photos. 

Hackathon

When I thought about Hackathon, I didn't thought that I'll be part of a winning team. I thought that I will have some fun. Make some customization for Acumatica, congratulate the winner and will go home. But to my surprise, here is what I've got:

well, not only myself, but other teammates with me in team Theta. Harsha, Dhiren, Yuriy and Spencer. Guys, it was a huge pleasure to play with you on the one team!

MVP

Another reward which caught me was MVP Developer. It was an honor to get it, and now it stands on my table reminding me about high standards which I have to continue to follow: