Development methodologies one more time

Hello everybody,

Today I want to write a few words regarding different development methodologies in plain English. 

1. Lean. Lean is methodology of entering on the market. If you ever heard term MVP ( minimal viable product ), then it's origin is from Lean. It says that you don't need to create some huge product and jump on the market with such a huge product. Because if you'll write it for three years, market will say: uhhh, it's second hand outdated product. And even if you'll spend 10 million $ on that, market will say: I don't care. Throw it. Also it say that it is close to useless to quiz your customers. Because you quiz your users/customers, and they tell you some kind of crazy staff. There is famous story of company Sony, which wanted to make famous player. They surveyed their focus group which color they prefer. Members of group said we want red, green, purple. While near the exit everybody from focus group took black players. Why? Also nobody named black color. 

2. Kanban. Kanban speaks that we need to move tasks in visible manner. There is a ribbon on what we have in plan, what we are currently working on, what was delivered. Also Kanban says that we move tasks from one point to the other. This is actually all essence of Kanban. Please keep in mind that I give oversimplified version, mentioning only outside attributes. 

3. Scrum. Scrum says that you need to split your torrent of tasks into iterations. You need to group your tasks into something, that after it's completion will provide some value. And iteration after iteration you move to your goal. It is not very convenient ( as usually ) for developers, because as usually developers don't like to have their code tested. But that is good from business prospective. Business sees that each time you have incremental increase of functionality. And it's stable and works constantly. As additional bonus business will not loose much if market suddenly will change. 

4. Extreme programing. In it's pure form ( as proposed by Kent Beck ) it almost never used. For example extreme programming teaches that all code is written in two heads and four hands. Code is 100% covered by five types of tests. And so on. Name speaks of itself: extreme programming. I never seen such a think. As a developer I want to admit that physically it is hard to work with someone always. It is also hard to cover everything with 100% testing. 

 

Dynamically changing a selector for a given data record in grid for Acumatica

Hello everybody,

today I want to share with you one very cool technique. 

I’ll tell you how you can dynamically change the output data in the selector.

I’ll show you this by giving you an example of forming a selector depending on the selected "CommandID" field. The "APInvoiceEntry" chart and the AP301000 page were taken as an example.

 The first let's create a DAC with the fields we need:

[Serializable]
public class MyCommand : IBqlTable
{
    public abstract class commandID : PX.Data.BQL.BqlInt.Field<commandID>
    {
    }
 
    [PXDBInt(IsKey = true)]
    [PXIntList(new int[] { 0, 1 }, new string[] { "Command 1""Command 2" })]
    [PXUIField(DisplayName = "Command ID")]
    public virtual int? CommandID { getset; }
 
    public abstract class commandData : PX.Data.BQL.BqlString.Field<commandData>
    {
    }
 
    [PXDBString(50)]
    [PXUIField(DisplayName = "Data")]
    public virtual string CommandData { getset; }
}

 

Remember to create these fields in your database.

 We also need a class with an extension for “APInvoiceEntry”. Then we create a button with a Popup panel for convenience and an event that will control the change of data in the selector.

 

public class APInvoiceEntryExt : PXGraphExtension<APInvoiceEntry>
 {
     public PXSelect<MyCommand> Commands;
 
     public PXSelect<GLTran> GLTrans;
     public PXSelect<FixedAsset> FixedAssets;
 
     protected void MyCommand_CommandData_FieldSelecting(PXCache cachePXFieldSelectingEventArgs e)
     {
 
         var row = (MyCommand)e.Row;
         if (row == null)
             return;
 
         PXFieldState state = PXFieldState.CreateInstance(e.ReturnState,
             typeof(string), falsetrue, 1, nullnullnulltypeof(MyCommand.commandData).Name);
         e.ReturnState = state;
 
         var id = (int)row.CommandID;
         if (id == 0)
         {
             state.ViewName = "FixedAssets";
             state.DescriptionName = nameof(FixedAsset.description);
             state.FieldList = new string[]
             {
                 nameof(FixedAsset.assetID),
                 nameof(FixedAsset.description),
                 nameof(FixedAsset.assetTypeID),
                 nameof(FixedAsset.assetCD)
             };
             var selectorCache = Base.Caches<FixedAsset>();
             state.HeaderList = new string[]
             {
                 PXUIFieldAttribute.GetDisplayName<FixedAsset.assetID>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<FixedAsset.description>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<FixedAsset.assetTypeID>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<FixedAsset.assetCD>(selectorCache),
             };
         }
         else
         {
             state.ViewName = "GLTrans";
             state.DescriptionName = nameof(GLTran.tranDesc);
             state.FieldList = new string[]
             {
                 nameof(GLTran.module), nameof(GLTran.lineNbr), nameof(GLTran.batchNbr),
                 nameof(GLTran.accountID), nameof(GLTran.tranDesc), nameof(GLTran.branchID)
             };
             var selectorCache = Base.Caches<GLTran>();
             state.HeaderList = new string[]
             {
                 PXUIFieldAttribute.GetDisplayName<GLTran.module>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<GLTran.lineNbr>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<GLTran.batchNbr>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<GLTran.accountID>(selectorCache),
                 PXUIFieldAttribute.GetDisplayName<GLTran.tranDesc>(selectorCache)
             };
         }
 
         state.DisplayName = PXUIFieldAttribute.GetDisplayName<MyCommand.commandData>(cache);
         state.Visible = true;
         state.Visibility = PXUIVisibility.Visible;
         state.Enabled = true;
     }
 
     public PXAction<PX.Objects.AP.APInvoice> CommandButton;
 
     [PXButton(CommitChanges = true)]
     [PXUIField(DisplayName = "Command Button")]
     protected void commandButton()
     {
         Commands.AskExt();
     }
 }

 After these changes, you'll see on the page AP301000 this artefact:

Now add popup bar to the page:

<px:PXSmartPanel runat="server" ID="CstSmartPanel1" Width="600px" LoadOnDemand="True" CaptionVisible="True" Caption="My Command" Key="Commands" CancelButtonID="CstButton8">
    <px:PXGrid runat="server" ID="CstPXGrid6" SyncPosition="True" Height="150px" SkinID="Details" Width="100%" MatrixMode="True" DataSourceID="ds">
        <AutoSize Enabled="True" MinHeight="50" />
        <Levels>
            <px:PXGridLevel DataMember="Commands" DataKeyNames="CommandID">
                <Columns>
                    <px:PXGridColumn DataField="CommandID" Width="70" CommitChanges="True" />
                    <px:PXGridColumn DataField="CommandData" Width="70" CommitChanges="True" />
                </Columns>
            </px:PXGridLevel>
        </Levels>
    </px:PXGrid>
</px:PXSmartPanel>

 And as outcome, you'll see that if Command 1 is selected, then dropdown gives you list of fixed assets:

and if you select Command 2, you'll see list of Accounts:

Summary

If you need to have different selectors in scope of single line, you'll need to define a view in your graph or graph extension, add FieldSelecting event, add pop up panel, and here you go, different selectors for different rows. A bit complicated, but for some tricky cases it may work. Also it shows how flexible Acumatica really is.

 

 

 

 

 

How to send attachment of Acumatica to printer

Hello,

recently I was asked on how to send attachment to printer via device hub.

For example some entity may have attached pdf files, and you may need to send them to printer. How to achieve that? Code below demonstrates:

public PXAction<APInvoice> PrintPDF;
 
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Print PDF")]
protected void printPDF()
{
    SMPrinter printer = PXSelect<SMPrinter,
        Where<SMPrinter.printerNameEqual<Required<SMPrinter.printerName>>>>.Select(Base, "P1");//here should be name of your printer, which is known to device hub
 
    SMPrintJobMaint printJobMaint = PXGraph.CreateInstance<SMPrintJobMaint>();
 
    foreach (var fileID in PXNoteAttribute.GetFileNotes(Base.CurrentDocument.Cache, Base.CurrentDocument.Current))
    {
        var printSettings = new PrintSettings
        {
            PrintWithDeviceHub = true,
            DefinePrinterManually = true,
            PrinterID = printer.PrinterID,
            NumberOfCopies = 1
        };
 
        printJobMaint.CreatePrintJob(printSettings,
            null,
            new Dictionary<stringstring> { { "FILEID"fileID.ToString() } },
            PXMessages.LocalizeFormatNoPrefix("Print PDF {0}"fileID.ToString()));
    }
}

 

 With provided code snippet you can print to device hub pdf files. And also not only pdf files.

How to track changes to any kind of Entity in Acumatica

Hello everybody,

today I want to share with you one very cool technique. 

Imagine following scenario: you need to track changes to some entity in Acumatica, and each time that entity changes, execute some kind of logic.

For example each time, when inventory item ( a.k.a. ) stock/non stock item, execute some kind of business logic ( for example send API request call ) to some 3-rd party service. 

You can extend all Acumatica graphs which deal with that entity via overriding Persist method for those graphs.

But how to deal with graphs, written by not Acumatica people, but 3-rd party vendors? How to execute some code for those kinds of events? Code snippet below shows how to achieve this:

public class BusinessEvents : PXGraphExtension<PXGraph>
{
    [PXOverride]
    public void Persist(Action del)
    {
        var exists= Base.Caches[nameof(PX.Objects.IN.InventoryItem)];
        if (exists != null)
        {
            if (exists.IsDirty)
            {
                //Do some kind of logic, related to your needs
                //For example send api refresh call
            }
        }
 
        del();
    }
}

Few explanations for this code. 

1. PXGraphExtension<PXGraph> gives you a way of apply some logic to all graphs

2. As a result, Persist will be executed for persists for entire system. That piece of code should be as efficient as possible

But overall, if you need to track changes to to any of the entities and stay in sync, use that suggestion.

How to show tab and grid always in Acumatica

Hi everybody,

today want to mention following use case:

1. Created Tab or Grid or element in Splitter

2. If View returns zero values

3. Element created at step 1 doesn't appear

How to deal with that?

Set AllowAutoHide to false, and Visible to true and element will not hide automatically.

How to find PXProjection which has SOOrder in the next line in Acumatica source code

Hello everybody,

today I want to speak about very useful feature in Visual Studio.

Sometime you may need some kind of source of inspiration from Acumatica source code. But quite often that source of inspiration have text, which is scattered over multiple lines of code.

For example, you want to find file which has PXProjection text in one line, and word SOOrder in the next line. How to make such a search? Window below appears once you click on Ctrl + Shift + F:

with help of .*\r?\n.* you can make search over multiple files. Take a note of what Visual Studio showed to me in output results once I've clicked on Find All:

and then, you can double click on any of those lines, and make sure, that you found something, that is PXProjection, with SOOrder in some of the next lines:

Summary

With such simple technique you can easily hunt for any lines of code in Acumatica framework, and enhance your search results.

 

 

A DAC extension must include the publis static IsActive method

Hello everybody,

today  I want to share one line of code for Acuminator for error message:

PX1016 A DAC extension must include the public static IsActive method with the bool return type. Extensions which are constantly active reduce performance. Suppress the error if you need the DAC extension to be constantly active.

In case if you don't want to suppress Acuminator with a comment, you can do something like this inside of your extension:

public static bool IsActive() => true;

Certainly it is not the most elegant way of doing that, as better way could be usage of some attribute for this purpose, or for example use inheritance, but as of now, the smallest amount of code, you can use that line of code which is presented here. 

 

How to get key fields of DAC class in Acumatica

Hello everybody,

recently for me it was needed to find out all key fields of DAC class. Code below does this:

 

public List<string> GetKeyFieldsOfDAC(Type dacClass)
{
    var result = new List<string>();
    var properties = dacClass.GetProperties().ToList(); 
    
    foreach (PropertyInfo info in properties)
    {
        var attrs = info.GetCustomAttributes(true);
        foreach (object attr in attrs)
        {
            if (attr.HasProperty("IsKey"))
            {
                dynamic typedAttribute = attr;
                if (typedAttribute.IsKey)
                {
                    result.Add(info.Name);
                }
            }
        }
    }
 
    return result;
}

 

And HasProperty method implementation goes below:

 

public static bool HasProperty(this object objectToCheck, string property)
{
    try
    {
        var type = objectToCheck.GetType();
        var prop = type.GetProperty(property);
        if (prop != null)
        {
            return true;
        }
    }
    catch (AmbiguousMatchException// it means we have more then one property
    {
        return true;
    }
 
    return false;
}

 That is not the most elegant solution in my life, and if you want to suggest a better one, please feel free to suggest.

How to avoid copy/paste with help of Attributes in Acumatica

Hello everybody,

today I want to leave a short note on how to avoid Copy/paste with help of custom attributes. 

Imagine following scenario. You have some set of duplicated code, which you need to apply at FieldSelecting . One of the ways of achieving this can be creation of some class and method within this class, which will handle that functionality, and then just copy/paste creation of the instance of the class at any place, where you need to have that business logic applied.

But you can use another way. You can use custom attributes. You can create your attribute, and then use that attribute over all places, where you may have a need for calling your business logic. In order to work that properly you'll need to inherit your Attribute from PXEventSubscriberAttribute, IPXFieldSelectingSubscriber . For example you can accomplish it like this:

 

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Parameter | AttributeTargets.Method)]
public class SomeAttribute : PXEventSubscriberAttributeIPXFieldSelectingSubscriber
{
    protected Type _TargetField;
    protected Type _DacType;
    
 
    public SomeAttribute(Type dacType, Type targetField)
    {
        _DacType = dacType;
        _TargetField = targetField;
    }
 
    public virtual void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
    {
var graph = sender.Graph;
//and some other logic
}

Also this can be applied to the field like this:

 public class YourDacClass

{

    [Some(typeof(DacClass), typeof(DacClass.someField))]
    [PXUIField(DisplayName = "Value")]
    public virtual string SomeField { getset; }

}

 

and that's it. Now you can have move your duplication logic to attribute SomeAttribute, and enjoy smaller amount of code.

 

Purpose of RowPersisting event

Hello everybody,

today I want to leave a note on usage of RowPersisting event.

Quite often I see situations, when RowPersisting is used for making additional insertions to database. Also quite often I see cases when some additional inserts being performed to database. 

I want to warn against such an approach. Reason for that is that during RowPersisting event, Acumatica opens transaction scope. Because of that, additional readings from db, or additional persists to db in scope of RowPersisting may lead to performance degradation and even deadlocks. 

Purpose of RowPersisting event is kind of latest resort, in which you can modify your record before putting it to database. And it shouldn't be used for some other purposes. Other purposes of RowPersisting event is validate record before it was putted to database, or cancel commit operation through throwing of an exception.