How to read from excel in Acumatica

Hello everybody,

today I want to share with you how to read from attached excel document to Acumatica. The code goes below:

public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
    public PXAction<SOOrder> LoadDataFromAttachment;
    
    [PXButton]
    [PXUIField(DisplayName = "Import from Attachment")]
    protected virtual IEnumerable loadDataFromAttachment(PXAdapter adapter)
    {
 
        var notes = PXNoteAttribute.GetFileNotes(Base.Document.Cache, Base.Document.Current);
 
        foreach (var guid in notes)
        {
            var fm = new PX.SM.UploadFileMaintenance();
            PX.SM.FileInfo fi = fm.GetFile(guid); //fm.GetFileWithNoData(guid);
 
            var bytes = fi.BinData;
 
            var newGr = PXGraph.CreateInstance<SOOrderEntry>();
 
            using (PX.Data.XLSXReader reader = new XLSXReader(bytes))
            {
                reader.Reset();
                Dictionary<String, Int32> indexes = reader.IndexKeyPairs.ToDictionary(p => p.Value.ToUpper(), p => p.Key);
                reader.MoveNext(); // skip the first row
                while (reader.MoveNext())
                {
                    string inventory = reader.GetValue(1);
                    DateTime dt = DateTime.Parse(reader.GetValue(2));
                    string customer = reader.GetValue(3);
                    string item = reader.GetValue(4);
                    string warehouse = reader.GetValue(5);
                    decimal qty = decimal.Parse(reader.GetValue(6));
                    
 
 
                    newGr.Clear();
                    var newSoOrder = new SOOrder();
                    newSoOrder.OrderType = "IN";
                    int?
                        customerID = SelectFrom<Customer>.Where<Customer.acctCD.IsEqual<@P.AsString>>.View
                        .Select(newGr, customer).FirstOrDefault().GetItem<Customer>().BAccountID;
 
                    newSoOrder.CustomerID = customerID;
                    var insertedOrder = newGr.Document.Insert(newSoOrder);
                    if (insertedOrder == null)
                    {
                        return adapter.Get();
                    }
 
                    var soline = newGr.Transactions.Insert();
                    newGr.Transactions.Cache.SetValueExt<SOLine.inventoryID>(soline, item);
                    newGr.Transactions.Cache.SetValueExt<SOLine.siteID>(soline, warehouse);
                    newGr.Transactions.Cache.SetValueExt<SOLine.orderQty>(soline, qty);
 
                    newGr.Actions.PressSave();
                }
            }
 
        }
 
        return adapter.Get();
    }

 Excel spreadsheet looks like this:

Summary

If you need to read from excel spreadsheet you may use standard Acumatica library which lives in PX.Data namespace.

 

 

Optimization of view delegate with help of PXDelegateResult and SelectWithViewContext

Hello everybody,

today I want to share with you very important and I'd say interesting use case of Acumatica views and delegates. 

As you may know, Acumatica views have precedence in sorting of results over Acumatica view delegates overloading.

 

It means that if your View has sorting by Column1, and view delegate has sorting by Column2, then final result will be sorted by Column1 disregarding of results of reading in delegate override.

How to do with cases if you have requirement to sort by column2 in your view delegate? You can achieve that with combination of PXDelegateResult and SelectViewWithContext. Full code sample is shown below:

public class SomeGraph : PXGraph<SomeGraph>
{
    private PXSelect<BalancedARDocument> Ardocumentlist;
 
    protected virtual IEnumerable ardocumentlist()
    {
        PXSelectBase<BalancedARDocument> cmd =
            new PXSelect<BalancedARDocument>(this);
        PXDelegateResult delegResult = new PXDelegateResult
        {
            IsResultFiltered = true,
            IsResultTruncated = true,
            IsResultSorted = true
        };
        foreach (PXResult<BalancedARDocument> resRecord in
            cmd.SelectWithViewContext())
        {
            // add the code to process res_record 
            delegResult.Add(resRecord);
        }
        return delegResult;
    }
}

 Another facet, which is achieved by such combination is amount of data, which will be read from database. SelectWithViewContext will read one page of data instead of whole data set in comparison with PXSelect or SelectFrom

Summary

If you need to have your sorting with limited reading from database, then SelectWithViewContext + PXDelegateResult is combination, which you may definitely need in your toolbelt. Use it for your own benefit.

 

 

How to debug PXCheckUnique attribute

Hi everyone,

today I want to tell you a story about PXCheckUnique attribute.

I had a task to customize PXCheckUnique, and for my disappointment for two tabs inherited attribute worked perfectly fine, but for two others it didn't. 

As one of the ways, I wanted to override method ValidateDuplicates, but soon I found that it is private, I was kind of disappointed, as it means I can't neither debug it, nor override it.

As workaround I've decided to copy/paste all it's internals with help of dnSpy, and debug it. For such a purpose following code was born:

 

public class PXCheckUniqueYZAttribute : PXEventSubscriberAttribute, IPXRowInsertingSubscriber, IPXRowUpdatingSubscriber, IPXRowPersistingSubscriber
 {
     /// <summary>
     /// The additional <tt>Where</tt> clause that filters the data records
     /// that are selected to check uniqueness of the field value among them.
     /// </summary>
     public System.Type Where;
     protected string[] _UniqueFields;
     protected PXView _View;
     private const string DefaultErrorMessage = "An attempt was made to add a duplicate entry.";
     private string _errorMessage;
     private bool callInprocess;
 
     /// <summary>Initializes a new instance of the attribute.</summary>
     /// <param name="fields">Fields. The parameter is optional.</param>
     public PXCheckUniqueYZAttribute(params System.Type[] fields)
     {
         this._UniqueFields = new string[fields.Length + 1];
         for (int index = 0; index < fields.Length; ++index)
             this._UniqueFields[index] = fields[index].Name;
     }
 
     /// <exclude />
     public bool IgnoreNulls { getset; } = true;
 
     public bool UniqueKeyIsPartOfPrimaryKey { getset; }
 
     public bool IgnoreDuplicatesOnCopyPaste { getset; }
 
     /// <summary>
     /// Gets of sets the value that indicates whether the field value
     /// is cleared when it duplicates a value in another data record.
     /// By default, the property equals <tt>true</tt>.
     /// </summary>
     public bool ClearOnDuplicate { getset; } = true;
 
     /// <summary>
     /// Gets or sets the value of custom error message.
     /// If message is not set, then default message will be shown.
     /// </summary>
     public string ErrorMessage
     {
         get => this._errorMessage ?? "An attempt was made to add a duplicate entry.";
         set => this._errorMessage = value;
     }
 
     /// <exclude />
     public override void CacheAttached(PXCache sender)
     {
         base.CacheAttached(sender);
         sender.Graph.FieldDefaulting.AddHandler(sender.GetItemType(), this._FieldName, new PXFieldDefaulting(this.OnFieldDefaulting));
         this._UniqueFields[this._UniqueFields.Length - 1] = this._FieldName;
         System.Type itemType = sender.GetItemType();
         System.Type type1 = this.Where;
         if ((object)type1 == null)
             type1 = typeof(PX.Data.Where<True, Equal<True>>);
         System.Type type2 = type1;
         for (int index = 0; index < this._UniqueFields.Length; ++index)
         {
             System.Type bqlField = sender.GetBqlField(this._UniqueFields[index]);
             type2 = BqlCommand.Compose(typeof(Where2<,>), typeof(PX.Data.Where<,,>), bqlField, typeof(IsNull), typeof(And<,,>), typeof(Current<>), bqlField, typeof(IsNull), typeof(Or<,>), bqlField, typeof(Equal<>), typeof(Current<>), bqlField, typeof(And<>), type2);
         }
         System.Type type3 = BqlCommand.Compose(typeof(Select<,>), itemType, type2);
         this._View = new PXView(sender.Graph, false, BqlCommand.CreateInstance(type3));
     }
 
     /// <exclude />
     public void RowInserting(PXCache sender, PXRowInsertingEventArgs e)
     {
         if (e.Row == null || !this.IgnoreNulls && ((IEnumerable<string>)this._UniqueFields).Any<string>((Func<stringbool>)(field => sender.GetValue(e.Row, field) == null)))
             return;
         e.Cancel = !this.ValidateDuplicates(sender, e.Row, (object)null);
     }
 
     /// <exclude />
     public void RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
     {
         this.ClearErrors(sender, e.NewRow);
         if (e.Row != null && e.NewRow != null && this.CheckUpdated(sender, e.Row, e.NewRow))
             e.Cancel = !this.ValidateDuplicates(sender, e.NewRow, e.Row);
         if (!this.ClearOnDuplicate || !PXCheckUniqueYZAttribute.CheckEquals(sender.GetValue(e.Row, this._FieldName), sender.GetValue(e.NewRow, this._FieldName)) || !e.Cancel)
             return;
         this.ClearErrors(sender, e.NewRow);
         sender.SetValue(e.NewRow, this._FieldName, (object)null);
         e.Cancel = !this.ValidateDuplicates(sender, e.NewRow, e.Row);
     }
 
     /// <exclude />
     public void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
     {
         if (e.Row == null || e.Operation == PXDBOperation.Delete)
             return;
         e.Cancel = !this.ValidateDuplicates(sender, e.Row, (object)null);
     }
 
     private void ClearErrors(PXCache senderobject row)
     {
         foreach (string uniqueField in this._UniqueFields)
         {
             string error = PXUIFieldAttribute.GetError(sender, row, uniqueField);
             if (!string.IsNullOrEmpty(error) && this.CanClearError(error))
                 PXUIFieldAttribute.SetError(sender, row, uniqueField, (string)null);
         }
     }
 
     protected virtual bool CanClearError(string errorText) => PXMessages.Localize(this.ErrorMessage).EndsWith(errorText);
 
     protected virtual void OnFieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
     {
         if (this.callInprocess || e.Cancel)
             return;
         this.callInprocess = true;
         object newValue = (object)null;
         sender.RaiseFieldDefaulting(this._FieldName, e.Row, out newValue);
         if (newValue != null)
         {
             object copy = sender.CreateCopy(e.Row);
             sender.SetValue(copy, this._FieldName, newValue);
             e.NewValue = this.ValidateDuplicates(sender, copy, (object)null) || !this.ClearOnDuplicate ? newValue : (object)null;
             e.Cancel = true;
         }
         this.callInprocess = false;
     }
 
     private bool CheckUpdated(PXCache senderobject rowobject newRow)
     {
         foreach (string uniqueField in this._UniqueFields)
         {
             if (!PXCheckUniqueYZAttribute.CheckEquals(sender.GetValue(row, uniqueField), sender.GetValue(newRow, uniqueField)))
                 return true;
         }
         return false;
     }
 
     /// <summary>
     /// Checks whether the provided objects are equal, ignoring the case
     /// if the provided objects are strings.
     /// </summary>
     /// <param name="v1">The first object to compare.</param>
     /// <param name="v2">The second object to compare.</param>
     /// <returns></returns>
     public static bool CheckEquals(object v1object v2) => !(v1 is string) && !(v2 is string) ? object.Equals(v1, v2) : string.Compare((string)v1, (string)v2, true) == 0;
 
     private bool CheckDefaults(PXCache senderobject row)
     {
         foreach (string uniqueField in this._UniqueFields)
         {
             bool flag = false;
             foreach (PXEventSubscriberAttribute attribute in sender.GetAttributes(row, uniqueField))
             {
                 if (attribute is PXDefaultAttribute && ((PXDefaultAttribute)attribute).PersistingCheck != PXPersistingCheck.Nothing)
                 {
                     flag = sender.GetValue(row, uniqueField) == null;
                     break;
                 }
             }
             if (flag)
                 return false;
         }
         return true;
     }
 
     private bool ValidateDuplicates(PXCache senderobject rowobject oldRow)
     {
         if (!this.IgnoreNulls || this.CheckDefaults(sender, row) && sender.GetValue(row, this._FieldOrdinal) != null)
         {
             PXView view = this._View;
             object[] currents = new object[1] { row };
             object[] objArray = Array.Empty<object>();
             foreach (object obj in view.SelectMultiBound(currents, objArray))
             {
                 object sibling = obj;
                 Lazy<stringlazy = Lazy.By<string>((Func<string>)(() => this.PrepareMessage(sender, row, sibling)));
                 if (!sender.ObjectsEqual(sibling, row) || sibling != row && this.UniqueKeyIsPartOfPrimaryKey && sender.GetStatus(row) != PXEntryStatus.Inserted)
                 {
                     foreach (string uniqueField in this._UniqueFields)
                     {
                         if (oldRow == null || !PXCheckUniqueYZAttribute.CheckEquals(sender.GetValue(row, uniqueField), sender.GetValue(oldRow, uniqueField)))
                         {
                             PXFieldState valueExt = sender.GetValueExt(row, uniqueField) as PXFieldState;
                             sender.RaiseExceptionHandling(uniqueField, row, valueExt != null ? valueExt.Value : sender.GetValue(row, uniqueField), (Exception)new PXSetPropertyException(lazy.Value));
                         }
                     }
                     return this.IgnoreDuplicatesOnCopyPaste && sender.Graph.IsCopyPasteContext;
                 }
             }
         }
         return true;
     }
 
     protected virtual string PrepareMessage(PXCache cacheobject currentRowobject duplicateRow) => this.ErrorMessage;
 }

 During debugging, I payed special attention to this part:

and I found out, that

view.SelectMultiBound(currents, objArray)

  returns zero elements. It lead me to a conclusion that I have some mistakes in SQL view. After comparing two FBQL views, the one which worked fine with the one, which didn't throw exception, I was able to figure out, that I need to modify FBQL view and DAC classes attributes related to PXDefault.

After that I was able to re-use PXCheckUnique attribute.

Summary

If you will have issues with PXCheckUnique attribute, feel free to go with dnSpy into Acumatica internals, and get it into your code, and thoughtfully debug the code. I bet you'll find some issues with your defaulting attributes or with your DAC class.

 

How to override ReleaseReceipt method in Acumatica

Hi everybody,

today I want to share with you how you can override base method ReleaseReceipt. One of the ways of achieving this is to re-use Action.

For example like this in class, which inherits from POReceiptEntry:

 

[PXOverride]
public void ReleaseReceipt(INReceiptEntry docgraph, AP.APInvoiceEntry invoiceGraph, POReceipt aDoc,
    DocumentList<INRegister> aINCreated, DocumentList<AP.APInvoice> aAPCreatedbool aIsMassProcess,
    Action<INReceiptEntry, AP.APInvoiceEntry, POReceipt,
        DocumentList<INRegister>, DocumentList<AP.APInvoice>, boolreleaseBase
)
{
    // Here you can add your logic
    releaseBase(docgraph, invoiceGraph, aDoc, aINCreated, aAPCreated, aIsMassProcess);
// as well as here }

With such approach you'll be able to save a bit of coding with delegate, and also prepend some steps before release execution, and append after release execution.

 

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.