How to publish customization with custom dll

Hi everybody,

today I want to share with you how to publish Acumatica customization with dll, which gives you errors during publish. For example, recently I've added Accord net library, and during publish got following errors:

Accord.Math.dll Failed to resolve method reference: System.Numerics.Complex& System.Numerics.Complex[0...,0...]::Address(System.Int32,System.Int32) declared in System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Accord.Math.dll Failed to resolve method reference: System.Void System.Numerics.Complex[0...,0...]::Set(System.Int32,System.Int32,System.Numerics.Complex) declared in System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Accord.Math.dll Failed to resolve method reference: System.Numerics.Complex System.Numerics.Complex[0...,0...]::Get(System.Int32,System.Int32) declared in System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Steps to deal with that are following:

1. Copy into clipboard error messages.

2. Create file cstValidationIgnore.txt

3. Paste content in the step 1 into file cstValidationIgnore.txt in the folder App_Data

4. Include that file into customization:

 

5. Publish.

 

After these steps I was able to get needed features of Accord.Net into Acumatica.

 

ConsentDate has been expirated in Acumatica

Today I will tell you how to fix the import error for CROpportunity "No consent date has been specified".

In order to import CROpportunity you need to create DataProvider and ImportScenario

Data Provider (SM206015)

Import Scenario (SM206025)

Now you have to go to the Import by Scenario (SM206036) screen and do the Import data.

After the successful import, you can see that a new Opportunity was created, but the Contact and Owner fields were not filled in.

This can easily be corrected by making changes to the Import scenario for

  • «Contact» - «ContactID!DisplayName»
  • «Owner» - «OwnerID!DisplayName»

Let's repeat the import on the page Import by Scenario (SM206036)

Import result as an example of one of the records

Good luck with your imports!

 

Good luck with your imports.

 

 

 

 

Display values from 3-rd party API in Acumatica

Good day everyone!

Today I want to share with you one experience with dynamic (virtual) data in Acumatica.

Imagine that you want to have a custom virtual view in Acumatica, and use it to get and update records in the cache.

It can be a lot of different situations such as getting data from a file and putting it into view or getting data from another view, updating the records and so on.

For example, let take a popular screen Sales Order (SO301000).

On updating the Document Details (SOLine) row, I want to have a custom control Availability of selected Item.

What does it look like in the code?

I created a SOOrderEntryExt graph extension and my virtual DAC:

 Snippet

[PXVirtual]

 

public class CustomVirtualDAC : IBqlTable
{
 
    #region InventoryID
    public abstract class inventoryID : PX.Data.BQL.BqlInt.Field<inventoryID> { }
 
    [PXInt(IsKey = true)]
    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    [PXUIField(DisplayName = "InventoryID")]
    public virtual int? InventoryID
    {
        get;
        set;
    }
    #endregion
 
    #region SiteID
    public abstract class siteID : PX.Data.BQL.BqlInt.Field<siteID> { }
 
    [PXInt(IsKey = true)]
    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    [PXUIField(DisplayName = "SiteID")]
    public virtual int? SiteID
    {
        get;
        set;
    }
    #endregion
 
    #region LocationID
    public abstract class locationID : PX.Data.BQL.BqlInt.Field<locationID> { }
    [PXInt(IsKey = true)]
    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    [PXUIField(DisplayName = "LocationID")]
    public virtual int? LocationID
    {
        get;
        set;
    }
    #endregion
 
    #region AvailQty
    public abstract class availQty : PX.Data.BQL.BqlDecimal.Field<availQty> { }
    [PXDecimal(2)]
    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    [PXUIField(DisplayName = "AvailQty")]
    public virtual decimal? AvailQty { getset; }
    #endregion
}

 After in SOOrderEntryExt I added my view and event:

 

public SelectFrom<CustomVirtualDAC>.View CustomVirtualView;
 
public virtual void _(Events.RowUpdated<SOLine> e)
{
    SOLine oldRow = e.OldRow as SOLine;
    SOLine row = e.Row as SOLine;
    CustomVirtualDAC currVirtualRez = null;
 
    if (row != null)
    {
        currVirtualRez = ReturnVirtualDACRez(row.InventoryID,
            row.SiteID, row.LocationID);
 
        if (row.OrderQty > oldRow.OrderQty)
        {
            //do what needed
            currVirtualRez.AvailQty -= row.OrderQty - oldRow.OrderQty;
            CustomVirtualView.Update(currVirtualRez);
        }
    }
 
}

 And one more method where I can return the needed row or add if it does not exists:

 

public CustomVirtualDAC ReturnVirtualDACRez(intinventoryIDintsiteIDintlocationID)
{
    CustomVirtualDAC currVirtualRez = null;
 
    foreach (CustomVirtualDAC mkinItemStatus in
        CustomVirtualView.Select())
    {
        if (mkinItemStatus.InventoryID == inventoryID && mkinItemStatus.SiteID == siteID && mkinItemStatus.LocationID == locationID)
        {
            currVirtualRez = mkinItemStatus;
        }
    }
 
    if (currVirtualRez == null)
    {
 
        InventorySummaryEnq tempGraph = PXGraph.CreateInstance<InventorySummaryEnq>();
        tempGraph.Filter.Current.InventoryID = inventoryID;
        tempGraph.Filter.Current.SiteID = siteID;
        tempGraph.Filter.Cache.Update(tempGraph.Filter.Current);
        foreach (InventorySummaryEnquiryResult record in tempGraph.ISERecords.Select())
        {
            CustomVirtualDAC newVirtualRez = new CustomVirtualDAC();
 
            newVirtualRez.InventoryID = record.InventoryID;
            newVirtualRez.SiteID = record?.SiteID;
            newVirtualRez.LocationID = record?.LocationID;
            newVirtualRez.AvailQty = record?.QtyAvail;
 
            CustomVirtualView.Insert(newVirtualRez);
        }
 
    }
 
    return currVirtualRez;
}

 

Everything seems ready and should work.But not. I receive error message Incorrect syntax near the keyword 'OPTION':

 

This is because Aсumatiсa is still trying to extract data from a table that does not exist. And none of the attributes such as [PXCopyPasteHiddenView] or [PXVirtualDAC] does not help.

To resolve this problem, you must implement a Dataview delegate when using a Virtual DAC.

So, in your graph extension you must add a dataview delegate to return the records that you need:

protected virtual IEnumerable AvailibilityView()

      {

       //fetch your records

         return your records;

      }

In my specific case, I must implement something like that:

 

public virtual IEnumerable customVirtualView()
{
    List<CustomVirtualDAClistRez = new List<CustomVirtualDAC>();
 
    foreach (CustomVirtualDAC line in CustomVirtualView.Cache.Inserted)
    {
        listRez.Add(line);
    }
 
    foreach (CustomVirtualDAC line in CustomVirtualView.Cache.Updated)
    {
        listRez.Add(line);
    }
    return listRez;
}

 Because I can`t call CustomVirtualView.Select() in the delegate because this will loop the code.

Now it will work.

 

Summary

In case if you need to read data from some 3-rd party source, and display it on the screen, then you have two ways:

1. Create useless table in Acumatica data base

2. Follow technique described in this article. 

 

 

 

 

 

 

 

 

 

 

 

Export from Acumatica to Excel via export scenario

Hi everybody,

today I want to leave a note on how to export from Acumatica to Excel.

To export data to excel, you need to go to the "Data Providers" page, screen SM206015,

then create a provider, fill in such fields as Name, Provider Type (in the case of Excel, select PX.DataSync.ExcelSYProvider), after which you need to add a file for export (where we we will display the data), and add it to Files,

then go to the SCHEMA tab, and fill in the correspondence of the fields into which we will export the data, 

we can click on the FILL SCHEMA OBJECTS / FILL SCHEMA FIELDS button in order to load ready-made fields with the previously loaded tables, or write your own, pay attention, the Active checkbox must be pressed.

After all this, don't forget to save your data.

 

 

Then we go to the "Export Scenarios" page, screen SM207025,

 

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. 

 

Below goes comment of Sergey Nikomarov:

  I want to notify you that the post has two issues: 1. IsActive method should be declared on graph extensions too. 2. It is completely OK to suppress this alert if your extension should be always active. This is the best practice and it is mentioned in the diagnostic documentation: https://github.com/Acumatica/Acuminator/blob/dev/docs/diagnostics/PX1016.md  > Suppress the error if you need the DAC extension to be constantly active.   In fact, you should never write IsActive() => true; This unnecessarily decreases the performance of your code since the platform will call this check every time.   I just wanted to give you these details although it would be great if you could update the post. I haven't found comments section for posts so I decided to write you here.   Best Regards, Sergey

 

 

 

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.

 

 

 

 

 

 

Lightweight Persist To Database

 

Hello everybody,

today I want to describe following use case. Quite often it is needed to persist to database one or another DAC class, which is filled by some data. 

As usually I see people do this via hard coding of DAC class inside of the Graph. But today I want to share with you a way of persisting DAC class without hardcoding it as a view. 

In order to accomplish this, you can use following graph:

public class ImportEntitiesInsertion : PXGraph<ImportEntitiesInsertion>
{
    public string AddView(Type dacType)
    {
        var viewName = "_DYNAMIC_" + dacType.GetLongName();
        if (!this.Views.ContainsKey(viewName))
        {
            var command = BqlCommand.CreateInstance(typeof(Select<>), dacType);
            var newView = new PXView(thistrue, command);
            Views.Add(viewName, newView);
            Views.Caches.Add(dacType);
        }
        return viewName;
    }
}

 

After that, in some other place of the code, you can use this graph like this:

 

var graphForInsertion = PXGraph.CreateInstance<ImportEntitiesInsertion>();
var dacType = typeof(SOOrder); 
var viewName = graphForInsertion.AddView(dacType);
 
for (int i = 0; i < 10; i++)
{
    var newOrd = new SOOrder();
    graphForInsertion.Views[viewName].Cache.Insert(newOrd);
}
graphForInsertion.Persist();

 

 What I especially like about this approach, is that records will be persisted initially in the cache, and only after you'll call Persist, all bunch of records will be persisted to database.

 

 

How To Override Properly Creatematrixitems

 

Hello everybody,

today I want to leave a short snippet on how to override methods in CreateMatrixItemsImpl graph extension. Below goes code snippet you can use for this purpose:

 

public class CreateMatrixItemsImplExt : PXGraphExtension<CreateMatrixItems.CreateMatrixItemsImpl, CreateMatrixItems>
{
    public override void Initialize()
    {
        base.Initialize();
    }
}

With help of this code fragment you can override and customize a bit more Matrix management of Acumatica.

How To Make Selector For Csanswers

 

Hello everybody,

recently one of the colleagues asked me how to make selector from Attributes values. Also that request seem trivial, but still took some time, especially with usage of FBQL query to build. 

Below goes template you may use if you'll need some kind of selector for attributes by some predefined value:

 

public class SOOrderExt : PXCacheExtension<SOOrder>
    {
        public class Codes
        {
            public const string MediaCode = "MEDIACODE";

            public class mEdiaCode : BqlType<IBqlStringstring>.Constant<mEdiaCode>
            {
                public mEdiaCode() : base("mEdiaCode")
                {
                }
            }

            public const string OrdOrigin = "ORDORIGIN";

            public class ordOrigin : BqlType<IBqlStringstring>.Constant<ordOrigin>
            {
                public ordOrigin() : base("ORDORIGIN")
                {
                }
            }
        }

        [PXSelector(typeof(SearchFor<CSAnswers.value>.Where<CSAnswers.attributeID.IsEqual<Codes.ordOrigin>>))]
        [PXDBString(50)]
        public string SomeValue1 { getset; }

        [PXSelector(typeof(SearchFor<CSAnswers.value>.Where<CSAnswers.attributeID.IsEqual<Codes.mEdiaCode>>))]
        [PXDBString(50)]
        public string SomeValue { getset; }
    }

 

 For me it was also interesting to note, that in the past it was common to use for Selector combination of PXSelector with Search, but in FBQL you'll need PXSelector with SearchFor.

How To Use Const In Fbql For Acumaitca

 

Hello everybody,

I want to leave a quick hint on how to use Const values in Acumatica for FBQL. Below goes sample:

public class someBranch : PX.Data.BQL.BqlInt.Constant<someBranch>
{
    public someBranch() : base(48)
    {
    }
}

Then later on you can use it in your BQL and FBQL queries for filtering