How To Override Part Of C Code In Automation Steps In Acumatica

 

Hello everybody,

today I want to share one interesting gotcha which took plenty of efforts from mine side in order to understand it.

Recently I got an assignment to modify behaviour of Create Prepayment action at purchase orders. 

If to look at CreatePreapyment implementation, it has following part of code:

 
if (this.Document.Current == null)
  return;
this.Save.Press();
APInvoiceEntry instance = PXGraph.CreateInstance<APInvoiceEntry>();
if (this.Document.Current.PrepaymentRefNbr == null)
{

if to put simply it checks if current purchase order already has a prepayment, and in case if it has, then give to a user error message. 

Imagine, that you need to modify that behaviour for some reason. How can you achieve it? One of the ways is to modify C# code, which is pretty good way, and workable. 

But let's try another way, automation steps.

Before I'll continue I want to say very important think: Automation Steps has priority over anything. It means that in case of conflict, Automation steps instruction will have priority over anything.

If your code says to Acumatica to do A, but automation step says to do B, Acumatica will do B. If your security settings says to do A, but Automation step says to do B, Acumatica will do B, and not A.

I can continue that list on and on but remember, Automation steps has priority over anything. 

For me they become some kind of pain points especially when dealing with Purchase orders. 

Take a look at another screenshot:

basically it says that on Step NL Prepaid action CreatePrepayment will be disabled. 

And whatever you'll do with a code or security, or even js hacking, menu item Create Prepayment will be disabled.

But if you take out checkbox Disabled, then Create Prepayment will become enabled.

Now let's back to the question, how to set value of PrepaymentRefNbr to null with Automation Steps.

You can use following steps for this:

  1. go to Automation steps screen
  2. Choose for Screen ID Purchase Orders
  3. For Step ID type NL Prepaid
  4. Go to Actions tab, and find there Action CreatePrepayment.
  5. Click on Fill with values as shown on the screen:

6. In the pop up window pick PrepaymentRefNbr as shown at screenshot:

7. Click on close.

After all of those manipulations in debugger ( well, not only in debugger ) you'll eyewitness miracle, value of PrepaymentRefNbr will be null independently of created before Prepayments.

also if to look at tab other information PrepaymentRefNbr showed something different:

as you can see from screenshot Prepayment Ref Nbr wasn't empty, in reality it showed as empty.

Conclusion

As usually I don't write conclusions, but here I want to write a few. First of all, if you write some code in Acumatica and it doesn't behave as you'd like, take a look at Automation steps, maybe there is the reason of your problems.

Second, if you want to override some code and don't want to create customization, C# and so on, take a look at Automation steps. With such a trick you can quickly help to your customer and also puzzle him about level of your knowledge of Acumatica.

No Comments

Add a Comment
 

 

How To Override Create Prepayment In Acumatica Without Usage Of Custom Delegate

 

Hello everybody,

today I want to share with you on how to override Action CreatePrepayment in Acumatica.

General rule of overriding methods in Acumatica according to T300 manual is like this:

  1. Create delegate.
  2. Add [PXOverride] over your method which is named exactly as base method ( in our case CreatePrepayment )
  3. Add your implementation

For example you can achieve it like this:

//Create your delegate
public delegate void CreatePrepaymentDelegate();
 
[PXOverride]
public void CreatePrepayment(CreatePrepaymentDelegate baseDel)
{
    // your code of overriding
}
 

Way from manual T300 is perfectly workable, but  I propose you to use feature of .Net which is named Action, which is declared very much exactly as mentioned delegate. 

Then your code for overriding will look pretty much the same, you'll just need use Action and omit delegate declaration:

[PXOverride]
public void CreatePrepayment1(Action baseDel)
{
         // your code of overriding
}

with such simple trick you can save a bit of time on typing.

No Comments

Add a Comment
 

 

Creating Custom Autonumber Autogenerated Id In Acumatica

 

If you want to create custom Attribute for autonumbering field you need:

  1. Create Setup page for configuring autonumbering field or maybe you can use existing
  2. Create Attribute which you'll add to your entity
  3. Add attribute to field that you need increment 

Create Setup page for configuration autonumbering field

Setup.cs like this:

    [System.SerializableAttribute()]
    [PXPrimaryGraph(typeof(CurrencyMaint))]
    [PXCacheName("Your company Preferences")]
    public class Setup : PX.Data.IBqlTable
    {
        #region DocumentRefNbr
        public abstract class documentRefNbr : PX.Data.IBqlField
        {
        }
        protected string _DocumentRefNbr;
        [PXDBString(15, IsUnicode = true)]
        [PXDefault("00010")]
        [PXUIField(DisplayName = "Document Last Ref. Number")]
        public virtual string DocumentLastDocNbr
        {
            get
            {
                return this._DocumentRefNbr;
            }
            set
            {
                this._DocumentRefNbr = value;
            }
        }
        #endregion
        #region ReturnDocRefNbr
        public abstract class returnLastDocNbr : PX.Data.IBqlField
        {
        }
        protected string _ReturnLastDocRefNbr;
        [PXDBString(15, IsUnicode = true)]
        [PXDefault("00010")]
        [PXUIField(DisplayName = "Return Documnet Ref. Number")]
        public virtual string ReturnLastDocNbr
        {
            get
            {
                return this._ReturnLastDocRefNbr;
            }
            set
            {
                this._ReturnLastDocRefNbr = value;
            }
        }
        #endregion
        
        #region AutoNumbering
        public abstract class autoNumbering : PX.Data.IBqlField
        {
        }
        protected bool? _AutoNumbering;
        [PXDBBool()]
        [PXDefault(true, PersistingCheck = PXPersistingCheck.Nothing)]
        [PXUIField(DisplayName = "Auto Numbering")]
        public virtual bool? AutoNumbering
        {
            get
            {
                return this._AutoNumbering;
            }
            set
            {
                this._AutoNumbering = value;
            }
        }
        #endregion
    }

We create 3 fields:

  1. DocumentLastDocNbr - for setting start position to numbering;
  2. ReturnLastDocNbr - for get last number from db
  3. AutoNumbering bool field for setting auto or manual numbering

Next, create graph SetupMaint for Setup page:

public class SetupMaint : PXGraph<SetupMaint>
    {
        public PXSave<Setup> Save;
        public PXCancel<Setup> Cancel;
 
        public PXSelect<Setup> LastNumbers;
    }

Next, create view, page YC101000:

<%@ Page Language="C#" MasterPageFile="~/MasterPages/FormView.master" AutoEventWireup="true" ValidateRequest="false" CodeFile="YC101000.aspx.cs" Inherits="Page_YC101000" Title="Untitled Page" %>
<%@ MasterType VirtualPath="~/MasterPages/FormView.master" %>
 
<asp:Content ID="cont1" ContentPlaceHolderID="phDS" Runat="Server">
	<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" TypeName="CurrencyApplication.SetupMaint" PrimaryView="LastNumbers">
	</px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phF" Runat="Server">
	<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-index100" Width="100%" DataMember="LastNumbers" TabIndex="800">
		<Template>
			<px:PXLayoutRule runat="server" StartRow="True" ControlSize="SM" LabelsWidth="SM"/>
			<px:PXTextEdit ID="edLastDocNbr" runat="server" DataField="DocumentLastDocNbr">
			</px:PXTextEdit>
			<px:PXTextEdit ID="edReturnLastDocNbr" runat="server" DataField="ReturnLastDocNbr">
			</px:PXTextEdit>
		   <px:PXCheckBox ID="edAutoNumbering" runat="server" AlignLeft="True" DataField="AutoNumbering" Text="Auto Numbering">
			</px:PXCheckBox>
		</Template>
		<AutoSize Container="Window" Enabled="True" MinHeight="200" />
	</px:PXFormView>
</asp:Content>

                     

 

Create Attribute

After it, you can create attribute:

public class AutoNumberAttribute : PXEventSubscriberAttribute,
                                        IPXFieldDefaultingSubscriberIPXFieldVerifyingSubscriber,
                                        IPXRowPersistingSubscriberIPXRowPersistedSubscriber
    {
        public const string NewValue = "";
 
        private bool _AutoNumbering;
        private Type _AutoNumberingField;
        private BqlCommand _LastNumberCommand;
 
     
        public virtual Type LastNumberField { getprivate set; }
        public static void SetLastNumberField<Field>(PXCache sender, object row, Type lastNumberField)
            where Field : IBqlField
        {
            foreach (PXEventSubscriberAttribute attribute in sender.GetAttributes<Field>(row))
            {
                if (attribute is AutoNumberAttribute)
                {
                    AutoNumberAttribute attr = (AutoNumberAttribute)attribute;
                    attr.LastNumberField = lastNumberField;
                    attr.CreateLastNumberCommand();
                }
            }
        }
 
       public AutoNumberAttribute(Type autoNumbering)
        {
            if (autoNumbering != null &&
                (typeof(IBqlSearch).IsAssignableFrom(autoNumbering) ||
                 typeof(IBqlField).IsAssignableFrom(autoNumbering) && autoNumbering.IsNested))
            {
                _AutoNumberingField = autoNumbering;
            }
            else
            {
                throw new PXArgumentException("autoNumbering");
            }
        }
 
        public AutoNumberAttribute(Type autoNumbering, Type lastNumberField)
            : this(autoNumbering)
        {
            LastNumberField = lastNumberField;
            CreateLastNumberCommand();
        }
 
        private void CreateLastNumberCommand()
        {
            _LastNumberCommand = null;
 
            if (LastNumberField != null)
            {
                if (typeof(IBqlSearch).IsAssignableFrom(LastNumberField))
                    _LastNumberCommand = BqlCommand.CreateInstance(LastNumberField);
                else if (typeof(IBqlField).IsAssignableFrom(LastNumberField) && LastNumberField.IsNested)
                    _LastNumberCommand = BqlCommand.CreateInstance(typeof(Search<>), LastNumberField);
            }
 
            if (_LastNumberCommand == nullthrow new PXArgumentException("lastNumberField");
        }
 
        public override void CacheAttached(PXCache sender)
        {
            BqlCommand command = null;
            Type autoNumberingField = null;
            if (typeof(IBqlSearch).IsAssignableFrom(_AutoNumberingField))
            {
                command = BqlCommand.CreateInstance(_AutoNumberingField);
                autoNumberingField = ((IBqlSearch)command).GetField();
            }
            else
            {
                command = BqlCommand.CreateInstance(typeof(Search<>), _AutoNumberingField);
                autoNumberingField = _AutoNumberingField;
            }
            PXView view = new PXView(sender.Graph, true, command);
            object row = view.SelectSingle();
            if (row != null)
            {
                _AutoNumbering = (bool)view.Cache.GetValue(row, autoNumberingField.Name);
            }
        }
 
        public virtual void FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
        {
            if (_AutoNumbering)
            {
                e.NewValue = NewValue;
            }
        }
 
        public virtual void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
        {
            if (_AutoNumbering && PXSelectorAttribute.Select(sender, e.Row, _FieldName, e.NewValue) == null)
            {
                e.NewValue = NewValue;
            }
        }
 
        protected virtual string GetNewNumber(PXCache sender, Type setupType)
 
        { 
 
            if (_LastNumberCommand == null)
                CreateLastNumberCommand();
            PXView view = new PXView(sender.Graph, false, _LastNumberCommand);
            object row = view.SelectSingle();
            if (row == nullreturn null;
            long number;
 
            string lastNumber = (string)view.Cache.GetValue(row, LastNumberField.Name);
            number = Int64.Parse(lastNumber);
            number = number + 3;
 
         lastNumber = "0000" + Convert.ToString(number);
 
            view.Cache.SetValue(row, LastNumberField.Name, lastNumber);
            PXCache setupCache = sender.Graph.Caches[setupType];
            setupCache.Update(row);
            setupCache.PersistUpdated(row);
            return lastNumber;
        }
 
        public virtual void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
        {
            if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert)
            {
                Type setupType = BqlCommand.GetItemType(_AutoNumberingField);
                string lastNumber = GetNewNumber(sender, setupType);
                if (lastNumber != null)
                {
                    sender.SetValue(e.Row, _FieldOrdinal, lastNumber);
                }
            }
        }
 
        public virtual void RowPersisted(PXCache sender, PXRowPersistedEventArgs e)
        {
            if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert &&
                e.TranStatus == PXTranStatus.Aborted)
            {
                sender.SetValue(e.Row, _FieldOrdinal, NewValue);
                Type setupType = BqlCommand.GetItemType(_AutoNumberingField);
                sender.Graph.Caches[setupType].Clear();
            }
        }
    }

Pay attention that for correct work you must implement interfaces, and ovveride standart methods(RowPersisting.... etc, all that need)

Main function for generating new walue is GetNewNumber; New walue will be bigger on 3;

Ok, here we described method - 

SetLastNumberField()

This method we call from graph before when we click save data to database in event RowPersisting:

Add attribute to field that you need increment;

public class CurrencyMaint : PXGraph<CurrencyMaintUsrCurrency>
    {
 
        public PXFilter<ExchangeDateFilter> Filter;
        public PXSelect<UsrCurrencyWhere<UsrCurrency.currencyExchangeDateEqual<Current<ExchangeDateFilter.exchangeDate>>>> Currencies;
 
        public PXSetup<Setup> AutoNumSetup;
 
 
        public CurrencyMaint()
        {
            Setup setup = AutoNumSetup.Current;
        }
 
        protected virtual void UsrCurrency_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
        {
            UsrCurrency doc = (UsrCurrency)e.Row;
            if (sender.GetStatus(doc) == PXEntryStatus.Inserted)
            {
                AutoNumberAttribute.SetLastNumberField<UsrCurrency.extRefNbr>(
                    sender, doc,
                    typeof(Setup.returnLastDocNbr));
            }
        }
    }

So for example I have DAC class and field ExtRefNbr I want to set autonumber attrbute, to do this try: [AutoNumber(typeof(Setup.autoNumbering))]

Of course don`t forget to create aspx page :)

[System.SerializableAttribute()]
    public class UsrCurrency : PX.Data.IBqlTable
    {
        
        public abstract class extRefNbr : PX.Data.IBqlField
        {
 
        }
        protected string _ExtRefNbr;
        [PXDBString(40, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
        [PXDefault("00000000")]
        [PXUIField(DisplayName = "Document Ref.")]
         [AutoNumber(typeof(Setup.autoNumbering))]
        public virtual string ExtRefNbr
        {
            get
            {
                return this._ExtRefNbr;
            }
            set
            {
                this._ExtRefNbr = value;
 
            }
        }
 
        #region CurrencyID
        #endregion         #region CurrencyName         #endregion         #region CurrencyRate             #endregion         #region CurrencyCC                 #endregion     }

And after configure Setup page:

And try to add new rows, and test autonumber:

Enjoy the result )

2 Comments

  • Angie said

    Hi Yuriy,

    Excuse me, I have a question...

    What is ExchangeDateFilter?.

    But I don't know how implement these filters

    I hope your answer. Thank very much :)

  • docotor said

    Update:

    below goes class ExchangeDateFilter:

    [Serializable]
    public class ExchangeDateFilter : IBqlTable
    {
    #region ExchangeDate
    public abstract class exchangeDate : PX.Data.IBqlField
    {
    }
    [PXDate()]
    [PXDefault( typeof(AccessInfo.businessDate))]
    [PXUIField(DisplayName = "Currency ExchangenDate")]
    [PXSelector(
    typeof(Search<UsrCurrency.currencyExchangeDate>))]

    public virtual DateTime? ExchangeDate { get; set; }
    #endregion
    }

    This filter not need for autonumbering, it is for select all from UsrCurrency table where date equal current date.

Add a Comment
 

 

Entity Framework One To Many Relationship

 

Hello everybody,

today I want to make short post on how to confiugre one to many relationship in Entity Framework 6.

Imagine following: One group can have multiple students. So one to many relationship. For this purpose you can use following convention configuration agreement:

public class Student
{
    public int StudentId { getset; }
    public string StudentName { getset; }
}
 
public class Group
{
    public int GroupId { getset; }
    public string GroupName { getset; }
    public string Department { getset; }
 
    public List<Student> Students { getset; }
}

In presented example class group includes navigation property Students. 

Another convention is like this:

public class Student
{
    public int StudentId { getset; }
    public string StudentName { getset; }
 
    public Group CurrentGroup { getset; }
}
 
public class Group
{
    public int GroupId { getset; }
    public string GroupName { getset; }
    public string Department { getset; }
 
    public List<Student> Students { getset; }
}

as you can see, difference is that Student instance by itself knows to which Group it belongs. 

No Comments

Add a Comment
 

 

Complete Description Of Arguments In Jforex Submitorder

 

Hello everybody,

here I want to leave fragment of code related to submitOrder:

public void onStart(IContext context) throws JFException {
     IEngine engine = context.getEngine();
     IHistory history = context.getHistory();
     Instrument instrument = Instrument.EURUSD;
     context.setSubscribedInstruments(java.util.Collections.singleton(instrument), true);
     
     ITick lastTick = history.getLastTick(instrument);
     double price = lastTick.getAsk() + instrument.getPipValue() * 5;
     double sl = lastTick.getAsk() - instrument.getPipValue() * 20;
     double tp = lastTick.getAsk() + instrument.getPipValue() * 10;
     long gtt = lastTick.getTime() + TimeUnit.SECONDS.toMillis(30); //withdraw after 30 secs
     IOrder order = engine.submitOrder("BuyStopOrder", instrument, OrderCommand.BUYSTOP, 0.1, price, 20, sl, tp, gtt, "My comment"); 
 }

For me it was a challenge to find sequence of arguments to pass into submit order related to stop loss, take profit.

No Comments

Add a Comment
 

 

New Class In Acumatica Pximpersonationcontext

 

Hello everybody,

here I want to document new scope in Acumatica: PXImpersonationContext. 

As often is the case try to look at presented code:

var thr = new Thread(
                                   () =>
                                   {
                                       try
                                       {
                                           using (new PXImpersonationContext(PX.Data.Update.PXInstanceHelper.ScopeUser))
.

.

.

}
                            foreach (var thread in threads)
                            {
                                lock (thisLock)
                                {
                                    thread.Start();
                                }
                            }
 
                            foreach (var thread in threads)
                            {
                                thread.Join();
                            }

It does the following: creates threads, inside of threads users some logic for persistance to database through graphs and then waits for joining threads.

Recently I've had following problem. I've created processing screen that worked great in manual mode, but absoultely refused to work in Automation schedule mode. Refused means thread was crashed without any exception message.

Adding PXImpersonationContext solved the issue for me.

Now you may wonder, how PXImpersonationContext achieves it?

Let's consult with JetBrains decompiler:

public class PXImpersonationContext : IDisposable
{
  private IPrincipal a;
  private readonly WindowsImpersonationContext b;
 
  public PXImpersonationContext()
    : this(PXDatabase.Companies.Length != 0 ? "sys@" + PXDatabase.Companies[0] : "sys")
  {
  }
 
  public PXImpersonationContext(string userName)
    : this(userName, new string[0])
  {
  }
 
  public PXImpersonationContext(string userName, string[] roles)
  {
    WindowsIdentity windowsIdentity = (WindowsIdentitynull;
    IPrincipal principal = (IPrincipalnew GenericPrincipal((IIdentitynew GenericIdentity(userName), roles);
    IdentitySection section = (IdentitySection) WebConfigurationManager.GetSection("system.web/identity");
    if (section != null && section.Impersonate && HttpContext.Current != null)
    {
      HttpWorkerRequest service = (HttpWorkerRequest) ((IServiceProvider) HttpContext.Current).GetService(typeof (HttpWorkerRequest));
      if (service != null)
      {
        IntPtr userToken = service.GetUserToken();
        if (userToken != IntPtr.Zero)
          windowsIdentity = new WindowsIdentity(userToken);
      }
    }
    this.a = PXContext.PXIdentity.User;
    PXContext.PXIdentity.User = principal;
as you can see from decompiled code sample, PXImpersonationContext uses user name "sys@currentlyloggedcompany" then it tries to create WindowsIdentity user and then uses that user for processing your staff.
Pretty cool idea, huh?
One more addition.
PX.Data.Update.PXInstanceHelper.ScopeUser also will give you "sys@currentlyloggedcompany" which leads me to to conclusion that "sys@currentlyloggedcompany" is most cool user in Acumatica system.
Also it is not presented in table users which means that such a user is some kind of virtual user.

If you become puzzled from this long technical description, then go straight to the summary:
Summary

If your processing screen crashes with no reason in automation schedule mode, add to your code 

using (new PXImpersonationContext(PX.Data.Update.PXInstanceHelper.ScopeUser))

{

    // here shold be your processing logic

}

and try again. In mine case such addition "resurected" mine automation schedules. Hopefully yours will also come back to life.

No Comments

 

Add a Comment
 

 

Manage Serialization In Net Core 2 0

 

Hello everybody,

today I want to write a short notice on how to manage uppercase/lowercase options for serialization in .Net Core.

In mine practice I often had situation, when javascript or typescript code sends me some staff in lowercase class names, but in C# I'm used to Upper case class names. 

Another option that you sometime can need is switching between xml and json serialization. How those options can be managed in .Net Core 2.0 ?

For both of those options ( and even more ) you can use pipeline management of Startup class of ConfigureServices method.

For example if you need to have xml serialization output you can use following code:

public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
                .AddMvcOptions(o => o.OutputFormatters.Add(
                    new XmlDataContractSerializerOutputFormatter()));

And if you want to switch default naming from camel case to Upper case, then following code in Startup class can be added:

public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
                .AddJsonOptions(o =>
                {
                    if (o.SerializerSettings.ContractResolver != null)
                    {
                        var castedResolver = o.SerializerSettings.ContractResolver
                            as DefaultContractResolver;
                        castedResolver.NamingStrategy = null;
                    }
                });
        }

with such changes you can easier or harder to manipulate your input/output from .Net MVC controllers

No Comments

Add a Comment
 

 

Required Instead Of Current In Acumatica

 

Hello everybody,

today I want to fulfill my promise that I gave to one of my readers. 

He left at mine blog following question:

How do we use required<> in place of current<> and pass some string constant for selector? That question was asked in context of this article. 

That is good question and I also want to add that answer on it will be at least to some degree disappointing. First of all, if you work with selectors, you can't use Required. Required is intended for use in Graphs.

But if you want to use some constant filtering conditions, you don't need Required attribute at all. You can use mechanism of Constants, which works perfectly without Required. 

Take a look at the following declaration:

public static class OrderTypes
{
    public const string N = "N";
    public const string C = "C";
 
    public class closed : Constant<String>
    {
        public closed()
            : base(C)
        {
        }
    }
 
    public class open : Constant<String>
    {
        public open() : base(N)
        {
        }
    }
}

As you can see, we declared two cosntants for order types: N and C.

Below goes code that shows how can you create selector that will filter Orders by OrderTypes with usage of those constants:

class Test : PXCacheExtension<SOOrder>
{
    #region UsrPreviousTermsId
 
    public abstract class usrPreviousTermsId : IBqlField
    {
    }
 
    [PXSelector(typeof(Search<SOOrder.orderNbrWhere<SOOrder.statusEqual<OrderTypes.closed>>>))]
    [PXUIField(DisplayName = "Linked sales order")]
    [PXDBString(10, IsUnicode = true)]
    public string UsrLinkedSalesOrder { getset; }
 
    #endregion
}

Such code will give you selector that give you a list of orders with status closed. Also this sample of code shows that for selector you don't need Required in order to filter by some constant value.

 

 

1 Comment

Add a Comment
 

 

Build In Data Sets In Sklearn

 

Hello everybody,

today I want to write a few words about built in data sets in sklearn library of python but from Visual Studio 2017 prospective. You may ask why Visual Studio 2017 if there are plenty of other options? I can say that main reason is becuase I like debugging features that availabile in Visual Studio which make my life much simpler. 

Initial headers and one function

So, before we continue, we need as usually in Pythong import some basic libraries. You can do it in Visual studio like this:

import sklearn
from matplotlib import pylab
import matplotlib
import matplotlib.pyplot as pyplot
import numpy as np
 
from sklearn import datasets
 
def sanitize_sequence(data):
    return list(data)

Later you will see why funciton sanitize_sequence is needed.

Data generation

In python you can generate data with following functions:

  • make_classification
  • make_regression
  • make_circles
  • make_checkerboard
  • and some others

I can't say why those names were chosen, because IMHO make_classification name is better used for something that can classify some data set, but authors of sklearn.dataset decided that it's good idea. So, leave it on their conscience and let's consider some examples of usage. I'd like to mention that those functions allow us to generate data pairs (x, y) which is convenient for drawing on charts.

datasets.make_circles

circles = datasets.make_circles()

This function allows us to generate data set that looks on plane as two circles with one circle inside the other. Consider following code and visualization for it:

circles = datasets.make_circles()
 
print ("features: {}".format(circles[0][:10]))
print( "target: {}".format(circles[1][:10]))
 
from matplotlib.colors import ListedColormap
 
colors = ListedColormap(['red''yellow'])
 
pyplot.figure(figsize=(8, 8))
 
pyplot.scatter(list(maplambda xx[0], circles[0])), list(map(lambda xx[1], circles[0])) , c = circles[1], cmap = colors)
 
pyplot.show()

it will give you the following picture:

Method make circlies gives as output sequence (x, y) or in terms of python tuple that consists of two elements: list of (x,y) coordinates and list of target coordinates. 

For the next step let's merge two functions: figure and scatter in one function in order to shorter recordings. 

def plot_2d_dataset(datacolors):
    pyplot.figure(figsize(8,8))
    pyplot.scatter(list(maplambda xx[0], data[0])), list(map(lambda xx[1], data[0])) , c = data[1], cmap = colors)
    pyplot.show()

Having this we can decide to make our life different and generate for ourselves data set with blured borders. Let's try this approach and visualize it with the folloiwng lines of code:

noisy_circles = datasets.make_circles(noise = 0.05)
plot_2d_dataset(noisy_circles, colors)

and you'll see following result:

as you can see we can use this model for checking power of our model. But let's make model even more blurred:

with such approach we can see that life for classification model become even more complicated. 

Next let's consider function make_classification. Take a look at the code:

simple_classification_problem = datasets.make_classification(n_features = 2, n_informative = 1, 
                                                             n_redundant = 1, n_clusters_per_class = 1, random_state = 1)

With this function we can flexibly generate problem, and we can say how many objects we want to get, quantity of features, which number of features should be informative, and which redundant, we can even add repetative features. 

plot_2d_dataset(simple_classification_problem, colors)

Presented code sets numberf of features to 2, which gives us task of binary classification, with 1 informative feature, 1 redundant featue. Take a look how it looks like:

As you can see task is very simple, and it's trivial to figure out where to draw a separation line. 

Now let's make life harder, and let's add classification for four classes:

classification_problem = datasets.make_classification(n_features = 2, n_informative = 2, 
                                                      n_classes = 4, n_redundant = 0, n_clusters_per_class = 1, random_state = 1)
colors = ListedColormap(['red','blue','green','yellow'])
plot_2d_dataset(classification_problem, colors)

as you can see from picture we have four different classes of data. With this function you can generate data sets of any level of complexity without need of providing to others production data. 

Toy examples

sklearn.datasets has also so called toy examples. You can load them with the following functions:

  • load_irs
  • load_boston
  • load_diabetes
  • load_digits
  • load_linnerud
  • etc
We can take a look how iris looks like:

iris = datasets.load_iris() print(iris)

and here is screenshot of result:

as you can see, it looks like iris is some kind of object like dict with some values. 

Take a look on keys:

Snippet

print(iris.keys())

and screenshot:

Take a look at field DESCR. 

I will leave up to you to see how output of DESCR look like, but I can say that it is quite detailed for watching. 

Now take a look how data look like. With purpose of simplification of output we will print only first 10 values:

Snippet

print(iris.data[:10])

also take a look at target variable:

print(iris.target)

and here is another output but inside of Visual Studio:

so in case if you closed the window, you still will be able to see output.

Visualization of data

Convenience of visualization we will import DataFrame from pandas library. Also take a look at some visualizations. In Visual Studio it can be done like this:

from pandas import DataFrame
 
iris_frame = DataFrame(iris.data)
iris_frame.columns = iris.feature_names
iris_frame['target'] = iris.target
 
 
print(iris_frame.head())

In Visual Studio it will give you following visualization:

I can't say that I like it very much, be we have what we have. In any case, you can see inputs, and also targets for those inputs. If you want to change how the output look like, you can use the following:

from pandas import DataFrame
import pandas as pd
pd.set_option('display.max_columns', 0)
iris = datasets.load_iris()
iris_frame = DataFrame(iris.data)
iris_frame.columns = iris.feature_names
iris_frame['target'] = iris.target
 
 
print(iris_frame.head())

In that case you'll see the following picture:

IMHO it is more convenient to use then previous multiline view. 

For now let's continue with analyzis and replace in column targets numbers with names. This can be done with function apply:

iris_frame.target = iris_frame.target.apply(lambda x: iris.target_names[x])
print(iris_frame.head())

take a look at results:

   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  target
0                5.1               3.5                1.4               0.2  setosa
1                4.9               3.0                1.4               0.2  setosa
2                4.7               3.2                1.3               0.2  setosa
3                4.6               3.1                1.5               0.2  setosa
4                5.0               3.6                1.4               0.2  setosa

Now you can see instead of 0 for target more convenient name like setosa.

Let's check how looks diagram of features for class setosa:

iris_frame[iris_frame.target == 'setosa'].hist('sepal length (cm)')
pyplot.show()

and below you can see result of this code:

What it gives? Well, if you just want to see single distribution, you are done. But in reality it can be needed to analyze multiple distributions. Quite often I can say it is needed to see all features distribution in boundaries of all classes. How to achieve it? Such information will allow us to see are those features differnet, will it become possible to separate somehow those objects. One of the ways to achieve it can be via drawing each feature distribution at a time. But you'll agree that it is not very convenient. Imagine case when you have many features and many classes, that can be cumbersome task. So, what programmers as usually do in such cases? They write cycles. The same is possible in python.

Take a look at following continuation of code:

plotNumber = 0
for featureName in iris['feature_names']:
    for targetName in iris['target_names']:
        plotNumber +=1
        pyplot.subplot(4,3, plotNumber)
        pyplot.hist(iris_frame[iris_frame.target == targetName][featureName])
        pyplot.title(targetName)
        pyplot.ylabel(featureName[:-4])
       
pyplot.show()

It will give you the following output:

for this code I want to comment that method subplot allows us to construct matrix from charts. This method allows us to pass into it number of rows and columns which we want to use. And also each separated chart will require from us to set it's number. As you can see from the picture, we have in rows different features and at rows different classes. 

After that you can do some kind of analytics. For example if to speak about analysis of last row, you can see that maximum value for setosa is 0.6, while minimum value for versicolor is 1.0 which gives you idea about linerar separability of those items. 

Library seaborn 

One more convenient library for visulalization in python is seaborn

Take a look at presented code:

import seaborn as sns
 
sns.pairplot(iris_frame, hue='target')
pyplot.show()

Looks pretty straitforwared, and take a look at picture generated:

as you can see from the picture, the easiest for linear separation is setosa. Almost everywhere you can separate setosa from versicolor and virginica. But if to speak about separation between versicolor and virginica it will be relatively hard to separate them linearly, while possible with some error. 

Some tricks related to Visual studio

For me it was a bit of challenge to find in Visual studio how to import some packages. Finally I have found that in order to import some package it is needed to make clicks as displayed at screenshots:

step 1:

step 2:

click on pip install seaborn from PyPI

Wait and you'll get seaborn library or any other.

Summary

I can say that to visualize any kind of data with Python, pandas and especially with seaborn is relatively easy. Also this approach allows you to quickly assume what kind of data you have, what kind of dependencies exist between them and how to separate them. Also you can easily visualize them.

Source code

If you want to follow source code by yourself in Visual studio by yourself, then here is the code:

import sklearn
from matplotlib import pylab
import matplotlib
import matplotlib.pyplot as pyplot
import numpy as np
 
from sklearn import datasets
 
def sanitize_sequence(data):
    return list(data)
    
 
circles = datasets.make_circles()
 
#print ("features: {}".format(circles[0][:10]))
#print( "target: {}".format(circles[1][:10]))
 
from matplotlib.colors import ListedColormap
 
colors = ListedColormap(['red''yellow'])
 
#pyplot.figure(figsize=(8, 8))
#pyplot.scatter(list(map( lambda x: x[0], circles[0])), list(map(lambda x: x[1], circles[0])) , c = circles[1], cmap = colors)
#pyplot.show()
 
thirdArg = circles[1]
 
def plot_2d_dataset(datacolors):
    pyplot.figure(figsize=(8,8))
    pyplot.scatter(list(maplambda xx[0], data[0])), list(map(lambda xx[1], data[0])) , c = data[1], cmap = colors)
    pyplot.show(block = False)
 
noisy_circles = datasets.make_circles(noise = 0.05)
#plot_2d_dataset(noisy_circles, colors)
 
noisy_circles = datasets.make_circles(noise = 0.15)
#plot_2d_dataset(noisy_circles, colors)
 
simple_classification_problem = datasets.make_classification(n_features = 2, n_informative = 1, 
                                                             n_redundant = 1, n_clusters_per_class = 1, random_state = 1)
#plot_2d_dataset(simple_classification_problem, colors)
 
classification_problem = datasets.make_classification(n_features = 2, n_informative = 2, 
                                                      n_classes = 4, n_redundant = 0, n_clusters_per_class = 1, random_state = 1)
colors = ListedColormap(['red','blue','green','yellow'])
#plot_2d_dataset(classification_problem, colors)
 
iris = datasets.load_iris()
#print(iris)
 
#print(iris.keys())
 
#print (iris.DESCR)
 
#print(iris.data[:10])
 
#print(iris.target)
 
 
from pandas import DataFrame
import pandas as pd
pd.set_option('display.max_columns', 0)
iris = datasets.load_iris()
iris_frame = DataFrame(iris.data)
iris_frame.columns = iris.feature_names
iris_frame['target'] = iris.target
 
#print(iris_frame.head())
iris_frame.target = iris_frame.target.apply(lambda x: iris.target_names[x])
#print(iris_frame.head())
 
iris_frame[iris_frame.target == 'setosa'].hist('sepal length (cm)')
#pyplot.show()
 
def DrawFeaturesInCycle():
    plotNumber = 0
    for featureName in iris['feature_names']:
        for targetName in iris['target_names']:
            plotNumber +=1
            pyplot.subplot(4,3, plotNumber)
            pyplot.hist(iris_frame[iris_frame.target == targetName][featureName])
            pyplot.title(targetName)
            pyplot.ylabel(featureName[:-4])
    pyplot.show()
    return featureName, plotNumber
 
#featureName, plotNumber = DrawFeaturesInCycle()
 
import seaborn as sns
 
sns.pairplot(iris_frame, hue='target')
pyplot.show()

No Comments

Add a Comment
 

 

How To Check Automation Step Settings In Acumatica

 

Hello everybody,

today I want to describe some tricky feature of Automation steps. 

Quite often in Acumatica I face interesting challenge. I've added some code in Row_Selected, open some screen, and to mine disappointment I find that screen totally ignores mine code. What can stand behind such weird behaviour?

One of the explanations can lie behind automation steps. If to put simply, automation steps is a feature of Acumatica, that allows to program it without any usage of C# code. All that is needed from you as Acumatica user is just add via GUI designer staff on the form and then via automation steps configure it's behaviour. 

So, let's consider some details, that can help you to understand how automation steps work better. 

First step, that I propose you to do, is add following line to your web.config in appSettings section:

  <appSettings>
    <clear />
    <add key="AutomationDebug" value="true" />

with such a setting you'll see hint from Acumatica on each screen:

In top left corner you'll be able to see, which Automation step you need to edit.

Then you can go to screen Automation steps, and search for step IN Completed. For example you can do it like this:

After that you'll see screen with three tabs:

First tab tells you under which conditions two other tabs: Actions and fields will work. Let's dig deeper in this particular case. 

Current tab says that when you open invoice ( Behavior Equals to invoice ) and status of that Invoice is completed, let's switch to tab Actions:

following Actions should be Active:

Report, Copy Order, VlidateAddresses, Email Sales Order/Quote.

Also it says that Action Create Shipment, Open Order, Cancel Order will be inactive. 

Also it gives you idea, that in field Menu Text you can change Text of Actions to something different. For example instead of Print Sales Order/Quote you can type Print Document. Also you can assign some icon to that menu item.

Before those steps Menu Item in Report looks like this:

After you'll try those steps, you'll see the following:

If you have question, why this menu item wasn't renamed and just new menu item appeared, stay assured that I also have. Maybe somebody from Acumatica can give good answer as a comment. For now I also puzzled why. If you'll discover why in the future, please find the time to comment on this blog.

One final point of Automation steps is tab Fields. Take a look at particular Automation step In Completed.:

As you can see, and probably guess, during "In Completed" automation step almost everything is in state disabled. 

No Comments

Add a Comment