T190 Quick Start in Customizations

Acumatica updates their training material periodically, and the newest wave of updates doesn’t disappoint. Having completed the earlier version of developer training, I find this new structure refreshing and much more understandable as a new developer. As a reminder, you can find these courses at https://openuni.acumatica.com/ for free.

Each course should take about 6 hours to complete, but if this first one is any indicator, it could take me as much span time as 3 days to complete one as I pick it apart and get side-tracked with other duties. In this series of posts, I am going to provide some of my own perspective, but the primary purpose is to condense the material into key learning points that I don’t want to forget.

T190 Quick Start in Customization

Lesson 1: Creating a Customization Project

This lesson shows how to:

  • create a customization project,
  • load items into the project,
  • bind the extension library, and
  • publish the customization project

An Acumatica developer will work in Customization Projects extensively, but I don’t load a customization project from a folder often as I don’t share source code with a team. Our source control is handled via periodic archiving of the project and source code since we don’t utilize a large developer staff for Acumatica. We pass customization projects from development to testing environments, and on up the line towards production in “2 steps easy”:

  • On the origin server, export a customization project in .zip format from the File – Export Project Package menu option.
  • On the destination server, in the Customization Projects (SM204505) screen, use Import to add the project as a new project or to overwrite an existing project.

The project should be ready to publish.

If working with normal source control, you may need to work with different versions of the project, such as if supporting multiple versions of Acumatica. The Source Control menu makes working with multiple versions of source code very easy, so check out the training material in lesson 1 if you need to do that.

Lesson 2: Creating Custom Fields

This lesson shows how to add a custom field to an existing table and create a DAC extension to support the new field.

Step 1 explains to use Customization – Inspect Element to click on a field or part of the form and get the information you will need to proceed. A shortcut to this is Ctrl + Alt + Click. If you have permissions to view this information, the shortcut will become second nature quickly.

Three ways to add a custom field (and one that is discouraged):

  • On the Screen Editor page of the Customization Project, select the node where you want the field added and click on Add Data Field and then New Field.
    • Field is defined (not created until the project is published) and available to place onto the screen after publishing the project.
    • Data Class is added/updated in the Customization Project to add the new field (This can be converted to and extension and moved to your Visual Studio code.)
    • Database Script is created to add the field to the database if it does not exist.
  • Directly via Database Script
    • In the Database Scripts node, Click Add – Custom Column to Table
    • Manually add the field to the DAC extension, creating a new DAC extension if necessary via the Customization Project or directly in the Visual Studio extension library.
  • Manually Import from Custom Database Table (not in training guide)
    • If your database structure is defined in the database already (i.e. added by a SQL Administrator), you can import the custom table via the Database Scripts node by using Add – Custom Table Schema.
    • Changes to the database table are captured via the RELOAD FROM DATABASE button.
  • Custom Script (not in training guide)
    • Adding tables and fields via a custom script is highly discouraged, but the option is available.

Controlling access to a field is handled via code or within the Customization Project. To control via code, use the RowSelected event. PXUIFieldAttribute.SetEnabled<>() is used to enable/disable a field. In the Customization Project, set the Enabled property. Disabling a field via the Customization Project field property will override control defined in the RowSelected event.

If a field value needs to trigger an immediate evaluation, the field must be set to CommitChanges = true in the Customization Project. Otherwise, the field will not be evaluated until the next postback is performed via the save button or changes on another field marked with CommitChanges = true.

An existing event handler can be modified via a Graph Extension. While these can be written entirely in Visual Studio, the Customization Project provides a view of event handlers with the ability to easily create the override. Within the field node of the Screen in Customization Project, just select Add Handler – Keep Base Method, and add your own code.

Lesson 3: Implementing the Update and Validation of Field Values

Updating another field of the same record based on the update of a field can be done via the field’s FieldUpdated event handler. To set the value, simply create a typed object of the record’s type and set it equal to e.Row, assuming e is the parameter name used by the event handler’s signature. Then assign row.[MyFieldToSet] = [MyValueToSet]. In the example used in the training guide, we use PXSelectorAttribute.Select to tap into the DAC field’s defined PXSelector to retrieve the value. We can use BQL or FBQL to do more complex selection from the database if needed as well as other business logic needed. In the example, we see how to use GetExtension<>() to access custom fields added via a DAC extension.

Validation of a field is an area I have struggled with for a long time because of one little detail that I only learned when completing the new T190 course… The FieldVerifying event is not used to validate the field value using data in other fields of the same record. It is used to validate against specific values or data found in OTHER records. I often want to ensure that the field value is acceptable in combination with other field values of the same record, and I always struggled with why this event handler would not handle such validation. Now I know, and so do you!

Feedback to the user is another area that I struggled with, and this section also mentions use of PXSetPropertyException and RaiseExceptionHandling to handle feedback to the user. We use both in the training example, so refer back to the training guide if you need to review proper usage. From the training guide, the following very important note about use of RaiseExceptionHandling to help with when NOT to use it:

RaiseExceptionHandling, which is used to prevent the saving of a record or to display an error or warning on the form, cannot be invoked on a PXCache instance in the following event handlers: FieldDefaulting, FieldSelecting, RowSelecting, and RowPersisted. For details, see RaiseExceptionHandling in the API Reference.

T190 Training Guide – Step 3.2 – Page 44
Lesson 4: Creating an Acumatica ERP Entity corresponding to a Column Entity

This section shows how to initialize a new graph instance to create an invoice via SOInvoiceEntry from a different graph. An Action is created (with a PXButtonAttribute to present the action to the user) to handle the business logic in response to pressing the button.

From questions recently on StackOverflow, new developers are having a good bit of trouble with initializing a graph. The proper syntax is:

var invoiceEntry = PXGraph.CreateInstance<SOInvoiceEntry>();
-or-
SOInvoiceEntry invoiceEntry = PXGraph.CreateInstance<SOInvoiceEntry>();

Do not use SOInvoiceEntry invoiceEntry = new SOInvoiceEntry() as that will not initialize the graph with all extensions and overrides. Use the CreateInstance method to properly load all business logic.

Another new thing for me to learn from this training is to use CreateCopy in creating a new entity. I have used similar syntax except to simply assign doc = invoiceEntry.Document.Insert(doc) directly. From the training guide, the proper way is shown using CreateCopy below:

var doc = new ARInvoice()
{
    DocType = ARDocType.Invoice
};
doc = (ARInvoice)invoiceEntry.Document.Cache
    .CreateCopy(invoiceEntry.Document.Insert(doc));  
doc.CustomerID = woEntry.WorkOrders.Current.CustomerID;
invoiceEntry.Document.Update(doc);

One more detail that I never learned until this training material is related to updating data directly in the Current property of the cache.

After you have created an invoice, you mark the cache as updated. This needs to be done explicitly because when you assign a value to the Current property of the cache, it is not marked as updated.

T190 Training Guide – Step 4.2.2 – Page 55
// Assign the generated invoice number and save the changes.  woEntry.WorkOrders.Current.InvoiceNbr =
    invoiceEntry.Document.Current.RefNbr;  woEntry.WorkOrders.Cache.MarkUpdated(woEntry.WorkOrders.Current); 

While the training material shows creating an action, the key takeaway is use of PXLongOperation.StartOperation(). Do NOT pass the current graph into LongOperation as this invokes a separate thread to run asynchronously. Instead, create a copy of the graph or a initialize a new graph to pass in. From this example int the training material, notice that the graph instance/copy is created BEFORE the long operation and is passed as parameter to the long operation. In this case, it is used by the CreateInvoice method as well.

var graphCopy = this;
PXLongOperation.StartOperation(graphCopy, delegate () {
    CreateInvoice(graphCopy);
}); 

The last section of lesson 4 shows used of .SetVisible and .SetEnabled to control visibility and access to the action button.

Lesson 5: Deriving the Value of a Custom Field from Another Entity

While lesson 3 showed us how to set a value of a field when another field of the current record is set, lesson 5 shows us how to default the value. While PXDefaultAttribute is a simple way to force a value to be defaulted, getting the value from another DAC requires the field names to be the same or for the value to be constant, such as 0. Anything more sophisticated is better served by the FieldDefaulting event.

The FieldDefaulting event can be used to apply business logic to locate data and then set the value based on that data. We simply set e.NewValue to the value we want used.

Not in the training guide:
If this “Default Value” would differ based on some value in the record, a combination of FieldDefaulting for the field and calling SetDefaultExt from a FieldUpdated event can help. Define the business logic once for how to set a value in FieldDefaulting and then allow multiple fields to derive the new default value for the field by calling SetDefaultExt in each field’s FieldUpdated event.

Lesson 6: Debugging Customization Code

A friend at Hackathon 2019 taught me how to connect to my remote development server to debug my code, but this lesson shows how to debug Acumatica source code. View the training material for detailed instructions, but the key elements to be handled are:

  • Be sure to Install Debugger Tools when installing Acumatica
  • Configure web.config for compilation debug=”True” (Mine was set that way by default)
  • Open the solution in Visual Studio, and clear the Checkbox on Enable Just My Code
    • Tools – Options
    • Debugging – General
    • clear Enable Just My Code
  • Add Debugging Symbols (location of the bin folder with the .pdb files)
  • Open source code from App_Data\CodeRepository\PX.Objects… folder and set breakpoints.
  • Start debugging (if not attaching to a remote server, just run Acumatica from your PC)

Leave a Reply