How to send email in Acumatica


today want to share quick code snippet on how to send email from Acumatica. For this purpose, good usage is for NotificationGenerator. See the code below:

public virtual void SendEmailNotification(string emailstring subjectstring emailBody)
    var sender = new NotificationGenerator
        To = email,
        Subject = subject,
        Body = emailBody,
        BodyFormat = EmailFormatListAttribute.Html


Screen templates in Acumatica

Let's take a look at the screen’s templates. There are 6 of them in Acumatica: Form, Grid, Tab, FormTab, FormGrid, TabGrid. Each template is designed to be used for different purposes. Let's look at each of them:

  1. Form

A Form template is used to display and edit a single record or entity at a time. It typically consists of fields and controls that allow users to input or view information related to a specific record.Forms are used for tasks like creating, editing, and viewing individual records, such as customers, vendors, or items.

Use Cases:

  • Creating and editing individual customer profiles.
  • Managing employee records, including personal details and employment history.

Viewing and updating information for a specific inventory item.

2. Grid

A Grid template is used to display a list of records in a tabular format. It allows users to view and manipulate multiple records simultaneously. Grids are commonly used for displaying lists of data, such as sales orders, purchase orders, or inventory items.

Use Cases:

  • Displaying a list of sales orders with details like order date, customer name, and order status.
  • Managing a list of open purchase requisitions.
  • Viewing a list of project tasks with their associated deadlines.

3. Tab

A Tab template is often used to organize and group related fields on a Form. It provides a way to break up a Form into multiple sections, each represented as a separate tab. Tabs help in organizing and presenting information in a structured manner within a single record.

Use Cases:

  • Creating a customer Form with tabs for contact information, billing details, and shipping details.
  • Managing an employee Form with tabs for personal information, job history, and performance reviews.
  • Designing a product Form with tabs for basic details, pricing, and specifications.

4. FormTab

A FormTab template combines the features of both the Form and Tab templates. It allows you to create a Form with multiple tabs, each containing a set of related fields. FormTabs are useful when you need to display and edit a single record with different sections or categories of information.

Use Cases:

  • Creating a comprehensive sales order Form with tabs for order details, shipping information, and payment details.
  • Managing a project record with tabs for project overview, team members, and project milestones.
  • Designing a vendor Form with tabs for contact information, payment terms, and purchase history.

5. FormDetail

A FormGrid template combines the features of both the Form and Grid templates. It allows you to display a Grid within a Form, enabling users to view and edit related records directly within the context of a single record. FormGrids are useful for scenarios where you want to associate and manage related data within a primary record.

Use Cases:

  • Managing a customer Form with a related grid displaying their recent orders.
  • Viewing a product record with a FormGrid showing its associated sales history.
  • Editing an employee record with a FormGrid displaying their past training sessions.

6. TabDetail

A TabGrid template combines the features of both the Tab and Grid templates. It lets you organize and display a Grid within a Tab, making it suitable for presenting lists of related data within specific sections of a Form.

Use Cases:

  • Managing a project Form with tabs for project details, team members, and project tasks displayed in Grids.
  • Viewing a customer record with tabs for contact information, sales history, and support cases displayed in Grids.
  • Designing a vendor Form with tabs for contact details, purchase history, and product offerings displayed in Grids.


Acumatica offers six different screen templates designed for various purposes: Form, Grid, Tab, FormTab, FormGrid, and TabGrid.

  • The "Form" template is for displaying and editing a single record, like a customer or vendor profile.
  • The "Grid" template is designed to show lists of records in a table format, such as sales or purchase orders.
  • The "Tab" template organizes related fields within a Form into separate tabs.
  • The "FormTab" template combines features of Form and Tab, allowing a single Form to have multiple tabs for different categories of information.
  • The "FormGrid" template blends features of Form and Grid, enabling users to view and edit associated records within the context of a primary record.
  • The "TabGrid" template integrates both Tab and Grid, permitting the organization of lists of related data within specific sections of a Form.

Each template comes with its own set of use-cases, offering versatility in data presentation and editing.







Hyper cube of Acumatica modules

Hi everybody,

today I want to share cube of Acumatica modules. For me it's kind of helpful, if I try to figure out features available in one or the other Acumatica license:



How to work with Excel files in Acumatica

Hello everybody,

Today I want to share with you two approaches for working with Excel files:

  1. How we might create Excel file in Acumatica using standard dll library of Acumatica website.
  2. How we might import data from excel file to Acumatica using XLSXReader from PX.Data.dll library.


Example will be developed on Stock Item screen.

Export data to Excel file:

  1. We will use PX.Export.dll library, so first you need add it to References:

2. Create new Package() object from the PX.Export.Excel.Core namespace and take first sheet from package.

3. Use method Add() on sheet object

Here is full example of source code logic that attach the excel file to Stock Item screen.

using PX.Data;
using PX.Objects.IN;
using PX.SM;
using System;
using System.Collections;
using System.IO;
namespace ExportExcelLib
    public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
        public static bool IsActive() => true;
        public PXAction<InventoryItem> ExportExcel;
        [PXUIField(DisplayName = "Export Excel", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
        public virtual IEnumerable exportExcel(PXAdapter adapter)
            if (this.Base.Item.Current == nullreturn adapter.Get();
            ExportToExcel(this.Base.Item.Cache, this.Base.Item.Current);
            return adapter.Get();
        public virtual void ExportToExcel(PXCache cache, InventoryItem inventoryItem)
            var excel = new PX.Export.Excel.Core.Package();
            var sheet = excel.Workbook.Sheets[1];
            sheet.Add(1, 1, "Inventory ID");
            sheet.Add(1, 2, "Item Description");
            sheet.Add(1, 3, "Item Status");
            sheet.Add(1, 4, "UOM");
            sheet.Add(1, 5, "Default Price");
            sheet.Add(2, 1, inventoryItem.InventoryCD);
            sheet.Add(2, 2, inventoryItem.Descr);
            sheet.Add(2, 3, inventoryItem.ItemStatus);
            sheet.Add(2, 4, inventoryItem.BaseUnit);
            sheet.Add(2, 5, (double)inventoryItem.BasePrice);
            // this logic conver Excel file to Memory Stream and bin format, after you could attache or save file to disk
            using (MemoryStream ms = new MemoryStream())
                UploadFileMaintenance fileUpload = PXGraph.CreateInstance<UploadFileMaintenance>();
                var oneFileInfo = new PX.SM.FileInfo(Guid.NewGuid(), "NewExcelFile.xlsx"null, ms.ToArray());
                fileUpload.SaveFile(oneFileInfo, FileExistsAction.CreateVersion);
                PXNoteAttribute.AttachFile(cache, inventoryItem, oneFileInfo);

 4. Screen shot of the excel file:

PS.: According to response from Acumatica Support the PX.Export.dll library doesn’t allow export images to excel file, method AddPng() will be removed in future:

Import data from Excel file

  1. We need setup and invoke Acumatica’s pop-up screen to load the excel file.

Add PXUploadDialog to the aspx file (IN202500.aspx) to the content that has Form object with DataMember =”Item”, because in source code we will invoke this dialog on Item view (Base.Item.AskExt() method will invoke pop up screen).

<px:PXUploadDialog runat="server" ID="LoadFilePanel" RenderCheckIn="false" AutoSaveFile="false" 
                       SessionKey="ImportStatementFile" Height="120px" Width="560px" Caption="Load Excel File (*.xlsx)" 
                       Key="Item" Style='Position:static;' />


How it looks:

2. Don't forget SessionKey string value, because we will use it to get excel file in bin data from cache of screen (from PXContext) in graph extension.


3. Create object of XLSXReader to read excel file in bin data and then use GetValue() method to parse values from excel file

4. Screen shot of my excel file with new Default Price

5. Last screen shot with new updated

6. Source code logic of Import Excel button and method:

public PXAction<InventoryItem> ImportExcel;
[PXUIField(DisplayName = "Import Excel", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
public virtual IEnumerable importExcel(PXAdapter adapter)
    if (this.Base.Item.Current == nullreturn adapter.Get();
    if (Base.Item.AskExt() == WebDialogResult.OK)
    return adapter.Get();
public virtual void ImportFromExcel()
    PX.SM.FileInfo file = (PX.SM.FileInfo)PX.Common.PXContext.SessionTyped<PXSessionStatePXData>().FileInfo["ImportStatementFile"];
    byte[] filebytes = file.BinData;
    using (PX.Data.XLSXReader reader = new XLSXReader(filebytes))
        Dictionary<stringintindexes = reader.IndexKeyPairs.ToDictionary(p => p.Value, p => p.Key);
        string excelFileDataCell = string.Empty;
        while (reader.MoveNext())
            excelFileDataCell = reader.GetValue(indexes["Default Price"]);
        decimal defaultPrice = decimal.Parse(excelFileDataCell);
        Base.Item.Current.BasePrice = defaultPrice;






One more article about PXProjection in Acumatica

The PXProjection attribute is mainly used to perform complex Select operations by using a fluent BQL query. If you need to join a fluent BQL query that is also a complex joined select query, you should use the PXProjection attribute.

This attribute can also be used in situations where you need to display data from multiple tables on a form or a tab. To do this, you need to declare a DAC with the PXProjection attribute, which implements the projection of data from one table or multiple tables into a single DAC.

This attribute is also used to define a new DAC class derived from the IBqlTable interface or any other existing DAC, with specific columns/fields/properties coming from DAC’s included in a select statement that defines the data managed by this new class. From a certain point of view, we can see this new DAC class as the equivalent of a SQL view where both define a set of data from a statement and both are a virtual table. There is no actual table in the database with their names.

Benefits of PXProjection 

  • Optimized Data Retrieval: When working with screens or reports that require a limited set of fields from a DAC, PXProjection allows you to retrieve only the necessary data. This can lead to reduced database load and improved query performance.
  • Improved Readability: By creating a PXProjection that includes only the fields relevant to the task at hand, you improve the readability of your code. Developers working on the project can quickly understand the purpose of the projection and its underlying data requirements.
  • Minimized Overhead: In cases where the original DAC has a complex structure with numerous fields, using a PXProjection can help you avoid unnecessary overhead associated with loading and managing those extra fields when they are not needed.
  • Enhanced Security: When you want to expose a subset of data to specific user roles or screens, PXProjection provides a way to do so without altering the original DAC's structure. This contributes to maintaining data security and integrity.

PXProjection with a Single DAC

Consider a DAC named InventoryItem, which contains a variety of fields related to inventory items. Now, imagine you are building a report that displays only the item code, description, and base unit. Instead of working directly with the InventoryItem DAC, you can create a PXProjection:

    Where<InventoryItem.itemStatus, Equal<>>>))]
public class InventoryItemProjection : IBqlTable
    #region ItemID
    [PXDBInt(IsKey = true, BqlField = typeof(InventoryItem.inventoryID))]
    [PXUIField(DisplayName = "Item ID")]
    public virtual int? ItemID { getset; }
    #region InventoryCD
    [PXDBString(30, IsUnicode = true, BqlField = typeof(InventoryItem.inventoryCD))]
    [PXUIField(DisplayName = "Inventory ID")]
    public virtual string InventoryCD { getset; }
    #region Descr
    [PXDBString(256, IsUnicode = true, BqlField = typeof(InventoryItem.descr))]
    [PXUIField(DisplayName = "Description")]
    public virtual string Descr { getset; }
    #region BaseUnit
    [PXDBString(6, IsUnicode = true, BqlField = typeof(InventoryItem.baseUnit))]
    [PXUIField(DisplayName = "Base Unit")]
    public virtual string BaseUnit { getset; }

In this example, we've created an InventoryItemProjection class that represents a projection of the InventoryItem DAC. We've included only the fields that are relevant to our report, ensuring better performance and a more focused data representation.

 Multi-DAC Column Exposing

In situations involving multiple DACs, PXProjection allows you to expose specific fields from each DAC and combine them into a single virtual structure. Let's take an example to illustrate this concept.

    public class MultiplyProjection : IBqlTable
        #region InvoiceNbr
        [PXDBString(15, IsUnicode = true, IsKey = true, InputMask = "",
            BqlField = typeof(ARInvoice.refNbr))]
        [PXUIField(DisplayName = "Invoice Nbr.", Enabled = false)]
        public virtual String InvoiceNbr { getset; }
        public abstract class invoiceNbr :
        { }
        #region DueDate
        [PXDBDate(BqlField = typeof(ARInvoice.dueDate))]
        [PXUIField(DisplayName = "Due Date", Enabled = false)]
        public virtual DateTime? DueDate { getset; }
        public abstract class dueDate :
        { }
        #region AdjgRefNbr
        [PXDBString(BqlField = typeof(ARAdjust.adjgRefNbr))]
        [PXUIField(DisplayName = "Latest Payment", Enabled = false)]
        public virtual String AdjgRefNbr { getset; }
        public abstract class adjgRefNbr :
        { }
        #region CuryAdjdAmt
        [PXDBDecimal(BqlField = typeof(ARAdjust.curyAdjdAmt))]
        [PXUIField(DisplayName = "Latest Amount Paid", Enabled = false)]
        public virtual Decimal? CuryAdjdAmt { getset; }
        public abstract class curyAdjdAmt :
        { }


In this code we define a MultiplyProjection class that uses the PXProjection attribute to create a projected view that aggregates data from the ARInvoice and ARAdjust tables. The projection retrieves information such as the invoice number, due date, latest payment reference number, and the latest amount paid. The abstract fields defined within the class correspond to the selected columns from the involved tables. This projection can be used to display summarized payment information associated with invoices.

Also we need to create a view for this such like that:

public SelectFrom<MultiplyProjection>
    .Where<MultiplyProjection.invoiceNbr.IsEqual<RSSVWorkOrder.invoiceNbr.FromCurrent>>.View Payments;

 Then you can add this DAC to the screen that you need by using the screen editor. In my way it looks like this.

Data Updates in the Database

By default, the projection DAC is readonly and  it does not allow persistent changes to the database. However, there is a Persistent property of PXProjection attribute that allows it by setting it as true

[PXProjection(typeof(Select<SOOrder, Where<SOOrder.approved, Equal<True>>>), 
    //Persistent says, make saving to database
    Persistent = true)


This property allows you to persist changes to the database either if they are Inserted, Deleted or in an Updated state.

Also changes can be persisted to all tables involved in the select statement. If the changes in the main table are persisted and if several tables need to be updated in the projection DAC, the fields implementing the relationship between the main and the joined tables must have the PXExtraKey attribute to allow the proper update called by the projection:

#region OrderType
[PXExtraKey]//Take note of usage sample
[PXDBString(2, IsKey = true, IsFixed = true, InputMask = ">aa", BqlField = typeof(SOOrderType.orderType))]
[PXUIField(DisplayName = "Order Type", Visibility = PXUIVisibility.SelectorVisible)]
public virtual string OrderType { getset; }
public abstract class orderType : PX.Data.BQL.BqlString.Field<orderType> { }

When updating changes to the database, not all fields in all DAC’s involved in the select statement are updated: only the fields that are mapped are the ones that will be updated.

 Extending PXProjection

The standard PXProjection might not offer all the data columns you require for your business processes. That's where extending PXProjection comes into. By adding additional columns, you can create a more comprehensive and streamlined view of your data. Let's explore a scenario where we need to achieve precisely that. 

Imagine you're managing a warehouse with various products. Acumatica PXProjection provides you with a pre-built view that combines data from different tables like INItem, InventoryItem, and INLocation. However, this view lacks a crucial piece of information - the shelf life of each product. To make informed decisions about stock rotation, you need to see the shelf life alongside the existing data.

To achieve this, you can extend the existing PXProjection class by creating your own custom class that inherits from it. Let's call it INItemExtension. In this class, you'll define the additional field for the shelf life.

public class INItemExtension : PXCacheExtension<INItem>
    #region ShelfLife 
    [PXUIField(DisplayName = "Shelf Life")]
    public virtual DateTime? ShelfLife { getset; }
    public abstract class shelfLife : PX.Data.BQL.BqlDateTime.Field<shelfLife> { }


Here, we're creating an extension for the INItem DAC and adding a field called ShelfLife.

With the extension in place, it's time to enhance the PXProjection. Locate the PXProjection class that combines the desired tables - INItemXInventoryItemXLocation. We'll add a join to our custom field in this class.

        InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<INItem.inventoryID>>,
                On<INLocationStatus.inventoryID, Equal<INItem.inventoryID>>,
                LeftJoin<INLocation, On<INLocation.locationID, Equal<INLocationStatus.locationID>>>>>>))]
[PXCacheName("Item Location Projection")]
public class INItemXInventoryItemXLocation : IBqlTable
    #region ShelfLife
    [PXUIField(DisplayName = "Shelf Life")]
    public virtual DateTime? ShelfLife { getset; }
    public abstract class shelfLife : PX.Data.BQL.BqlDateTime.Field<shelfLife> { }

In the PXProjection class, we're extending the existing Select2 to include our custom field. The PXDBDate attribute now references the ShelfLife field from our extension. By doing so, we're effectively joining the shelf life information into our PXProjection.

Utilizing the power of PXProjection becomes especially valuable when you're working with a single DAC entity and need to tailor its structure to meet specific requirements. By creating a customized, flattened view of the DAC, you can enhance your code's readability, improve database query performance, and optimize the overall user experience.

PXProjection in Acumatica empowers businesses to create tailored data structures that consolidate information from multiple DACs. This is particularly useful when you need to expose specific columns from these DACs for reporting and analysis.

PXProjection is more than just a data view - it's a canvas on which you can paint a more detailed picture of your business processes. Extending a PXProjection to include additional columns empowers you to mold your data views to match your specific needs.




How can we add a custom field with barcode scanning for acumatica mobile application?

Firstly we need to add our custom field to the screen, for example we will use the Sales Orders screen and add Custom Barcode field to the Details tab.

For adding this field to mobile application with Barcode scanning functionality we should just update Details container on

the SO301000 screen and define the special attribute with value “BarCodeScan”.

Or in the form of text:

update screen SO301000 {
update container "Details" {
   add field "CustomBarcode"{
       formPriority = 1
       special = BarCodeScan
After publishing the package we should be able to see our field with scanning functionality on the Sales Orders screen as shown below.



Override PXLongOperation Messages

Friends, today I want to share with you how we can change the default 'The operation has completed.' or other messages after PXLongOperation has been executed.

The first button will execute the standard logic

The second button will show a custom "Info".

The third button will show a custom "Warning":

The fourth button will show a custom "Error":


Passing values from the sub-report to the main report

Today, I want to tell you and demonstrate how to pass values from a subreport to the main report. We will be creating a simple report from scratch so that you can see each step and notice any potential pitfalls that you might encounter.


For example, we will create a new report named IQ000001.rpx. This will be our main report, where we will display the OrderNbr of Sales Orders pages. Additionally, for demonstration purposes, we will display the OrderQty of all SOLines in the same report and the CuryExtPrice in a separate subreport.


  • let's create the report IQ000001.rpx.


We need to add the DAC that will be used.

Also create a relationship between the tables:

Then add the parameter by which we will display the Order we want to use.

Warning: I've set the Default Value to the OrderNbr that exists in my system. Please be attentive and replace it with your own number.

And finally, let's add a filter based on our parameter. I will hardcode the parameter for the sake of simplicity in this implementation.

Once the work with the Build Schema is complete, we should add the necessary fields to the layout. We will also add SubReports to the layout, but we will implement them a bit later. Then, we can save the report.

We can see the result using the designer.


Configuration of gmail for Acumatica

Hi everybody,

want to leave a short screenshot of gmail configuration for Acumatica. Basically System Email Accounts screen ( screenid = SM204002 ):

Note, this is for Acumatica 23 R1.


How to add handler to graph dynamically

Hello everybody,

today I want to describe how you can add events to your graph dynamically with usage of AddHandler method.

Take a look on a code sample taken from Acumatica approval maps:

public class EPApprovalAutomation<SourceAssignApprovedRejectedHoldSetupApproval> : 
  EPApprovalList<SourceAssign, Approved, Rejected>
  where SourceAssign : class, IAssign, IBqlTable, new()
  where Approved : class, IBqlField
  where Rejected : class, IBqlField
  where Hold : class, IBqlField
  where SetupApproval : class, IBqlTable, new()
  public EPApprovalAutomation(PXGraph graph, Delegate @delegate)
    : base(graph, @delegate)
  public EPApprovalAutomation(PXGraph graph)
    : base(graph)
  private void Initialize(PXGraph graph)
    graph.FieldVerifying.AddHandler(BqlCommand.GetItemType(typeof (Approved)), typeof (Approved).Name, new PXFieldVerifying(this.Approved_FieldVerifying));
    graph.FieldVerifying.AddHandler(BqlCommand.GetItemType(typeof (Rejected)), typeof (Rejected).Name, new PXFieldVerifying(this.Rejected_FieldVerifying));
    graph.FieldUpdated.AddHandler(BqlCommand.GetItemType(typeof (Approved)), typeof (Approved).Name, new PXFieldUpdated(this.Approved_FieldUpdated));
    graph.FieldUpdated.AddHandler(BqlCommand.GetItemType(typeof (Rejected)), typeof (Rejected).Name, new PXFieldUpdated(this.Rejected_FieldUpdated));
    graph.FieldDefaulting.AddHandler(BqlCommand.GetItemType(typeof (Hold)), typeof (Hold).Name, new PXFieldDefaulting(this.Hold_FieldDefaulting));
    graph.FieldDefaulting.AddHandler(BqlCommand.GetItemType(typeof (Approved)), typeof (Approved).Name, new PXFieldDefaulting(this.Approved_FieldDefaulting));
    graph.Initialized += new PXGraphInitializedDelegate(this.InitLastEvents);

 As you can see in the method Initialize, we setup in our own kind of view events for graphs, which will use our view.

In this example we add methods Apporved_FieldVerifying, Rejected_FieldVerifying, Approved_FieldUpdated, etc. 

And if to look on the implementation of the methods, it's quite obvious and have nothing special:

protected virtual void Approved_FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
  boolnewValue = (bool?) e.NewValue;
  bool flag = true;
  if (newValue.GetValueOrDefault() == flag & newValue.HasValue && !this.IsApprover((SourceAssign) e.Row))
    if (sender.GetAttributesReadonly<Approved>(e.Row).OfType<PXUIFieldAttribute>().Any<PXUIFieldAttribute>((Func<PXUIFieldAttribute, bool>) (attribute => attribute.Visible)))
      PXUIFieldAttribute.SetError<Approved>(sender, e.Row, "You are not an authorized approver for this document.");
    throw new PXSetPropertyException("You are not an authorized approver for this document.");
  PXUIFieldAttribute.SetError<Approved>(sender, e.Row, (stringnull);

Which means, that you can use presented template for adding your methods inside of your view, in case if you need to accomplish that.

Also want to mention, that these dynamic methods, while adding to the sequences, will follow the rules below:

When calling the AddHandler<>() method, the collection of event handlers is updated according to the following rules:

  • For events with names ending in "ing" (excluding the RowSelecting event), new event handlers are added to the beginning of the collection.
  • For events with names ending in "ed" and the RowSelecting event, new event handlers are added to the end of the collection. To remove an event handler, you can use the RemoveHandler<>() method.



By using the AddHandler method, specific event handlers are attached to field verifying, field updated, and field defaulting events for different fields. The example demonstrates the implementation of methods such as Approved_FieldVerifying, Rejected_FieldVerifying, Approved_FieldUpdated, and more.

These dynamically added methods follow certain rules when added to the event handler collection. Events ending with "ing" (excluding RowSelecting) have handlers added to the beginning of the collection, while events ending with "ed" and the RowSelecting event have handlers added to the end of the collection. The RemoveHandler method can be used to remove event handlers when necessary.

By understanding this approach, you can apply similar techniques to add custom methods to your views, tailored to your specific requirements. This flexible and dynamic approach allows for efficient event handling within Acumatica.