Creating a New Notification in Acumatica

I recently received a new request from a client who wanted additional notification functionality on the sales order screen. They manufacture items for their customers on a made-to-order basis. In the manufacturing process, every item must be sent off to a single vendor for the finishing step. Therefore, it is important for them to make sure that the vendor knows about all incoming sales orders and promise dates so they can plan accordingly. They wanted an action that would send an abbreviated sales order report off to that vendor.  I will share with you how I did it.

In the early days of working with the Acumatica framework, I would have put something together that runs a report, gets a pdf as a byte array, and then attach it to a notification template (see ‘Running a Report’ and ‘Calling a notification template’ ). However, now one of my central design philosophies is to search high and low in the application to use existing functionality that does what I want.

To begin, let’s look at how Acumatica handles similar actions (user clicks an action, a report is run, and a notification template is called), such as “Email Sales Order” or “Email Quote”.

Looking at the code in the SalesOrderEntry graph, all notifications are driven off a single action named ‘Notification’. The Notification Source ID is passed into the action method to call different defined notifications. What makes this slightly tricky is the fact that the method body is protected, so it is not as simple as calling this method. Luckily, the xRP framework allows for these methods to be called from a graph extension.

Creating a New Notification in Acumatica

Note the fact that the graph extension is marked as abstract, and the PXProtectedAccess attributes are tagged on both the graph extension and the abstract method. The abstract method exactly matches the signature of the protected member in the base graph. This will allow this graph extension to call the ‘Notification’ method the same way the “Email Sales Order” and “Email Quote” actions in the base graph call it.

Next, we publish this code and configure the front-end to add the notification source. I created a FuturePO report (SOGP6410) that is essentially the sales order report with the pricing removed.  Additionally, I created a notification template that should be called. The “FUTUREPO” (based on my protected const string ‘NotificationID’ in my graph extension) notification source is added to both Sales Order Preferences and my customer classes.

Creating a New Notification in Acumatica
Creating a New Notification in Acumatica

After completing the front-end configuration, when the action button is pressed, the report is run and the email is generated as expected.

Creating a New Notification in Acumatica
Creating a New Notification in Acumatica

Custom notifications seem to be one of the biggest customization requests I get from my clients. Notifications such as these are a “low tech” sort of integration, where data is not being passed back and forth through APIs, but instead streamlined from human to human. They take only a relatively short time to set up and can make a massive difference in the visibility of different business processes.

I hope that this example helped you perhaps solve a challenge for yourself as an Acumatica user or for your client.  Perhaps you will find that this is a great way of doing simple customization while leveraging the base platform code as much as possible.

In any event, I hope you found this useful, and as always, happy coding!

Enabling Parallel Processing on Acumatica Processing Screens

Acumatica, positioned as a mid-market ERP, can be deployed for a wide variety of companies – large, medium, or small. As companies experience growth, the amount of data it needs to manage grows.  However, in my experience developing custom code on the Acumatica platform, I find a significant number of organizations I work with punching above their weight when it comes to managing and processing data.

Acumatica has a wide variety of processing screens to perform various tasks “en masse”, and additionally has a robust yet easy framework to follow for implementing your own processing screens. Out of the box, however, records are processed sequentially (1,2,3 etc.). Fortunately, it is possible to enable parallel processing for clients with large data processing requirements.

Parallel processing takes advantage of the fact that applications can utilize more than a single processing thread at once. Think of the days before computers, where clerks manually processed transactions by hand. How did you speed up the number of transactions you can record? Either you search high and low for the wonder clerk who is super-fast, or you simply hire more clerks and split the work evenly amongst them. Acumatica can be configured similarly.

To enable Parallel processing, you need to add the following keys to the web.config file under the Configuration/Appsettings node:

To test this, lets add these keys to the web.config file and create a basic processing screen that processes a list of records:

Enabling Parallel Processing on Acumatica Processing Screens

Even though we have added the keys, the processing screen still returns the entire list of GL Batches when “Process All” is selected.

Enabling Parallel Processing on Acumatica Processing Screens

This is due to the keys only setting a global rule on processing. It still needs to be enabled on your custom screen itself. The code below demonstrates how:

Now let’s try the process the all button:

Enabling Parallel Processing on Acumatica Processing Screens

When you look at the result of the processing screen, you can see that all records have been processed even though that thread we caught only saw 10:

Enabling Parallel Processing on Acumatica Processing Screens

Here is a diagram illustrating the difference between the two, and where the breakpoint is caught in the code:

Enabling Parallel Processing on Acumatica Processing Screens

During sequential processing, the thread (aka the clerk) saw the entire list of items to process. In parallel processing, the thread only saw the max number of items (specified at a max level in the web.config file and additionally in the parallel processing options at the screen level).

Parallel processing, while not a silver bullet, can significantly increase the processing capability for Acumatica users working with large amounts of data. With just a few changes to the web.config file, you can easily enable it on both the base ERP code and your custom code. There is no need to manage your own threads and the other complexities that come with it.

I hope that this article will help you with the data processing volume that is ever increasing at a faster and faster pace in this digitally transforming world.

Happy Coding!

Enabling Acumatica’s Modern UI

In this blog post, I will show you how to enable the developer preview of the new Acumatica UI, released in the 23R1 release. After enabling the UI, I will show you how one rebuilds the site so that after you make changes to files, you can take a look at the changes you have made during development. 

I have been exceedingly excited about the new Acumatica Modern UI and have been waiting with bated breath for it to be released. In Acumatica 23R1, released on 4/4/2023, it is finally available to preview. I am going to show you how to enable it so you can see it too. 

Enabling the Modern UI

Step 1: Edit the web.config file  

Under the <Appsettings> tag of the web.config file, add the following: 

Developer Preview of Acumatica's New Modern UI
Step 2: Enable the modern UI for certain pages 
 
The modern UI is only available for certain pages. You can see which pages under the site path \FrontendSources\screen\src\screens. Navigate to the “Site Map” screen and make sure the “UI” column is visible. 
Developer Preview of Acumatica's New Modern UI
Step 3: View the Page
Developer Preview of Acumatica's New Modern UI

Voila, the screen is rendered using the new UI!

Developer Preview of Acumatica's New Modern UI

Structure of the Modern UI

Location

Under the path {siteRoot}\FrontendSources\screen\src\screens, you will see a collection of folders. Each folder represents a single screen, and inside the folder has the files that drive the screen. So inside path {siteRoot}\FrontendSources\screen\src\screens\SO\SO301000 exists the files that drive the ‘Sales Order’ screen.

Developer Preview of Acumatica's New Modern UI

In this folder you have the following files:

  1. html – An HTML File that defines the screen layout of SO301000
  2. ts – A TypeScript Screen viewmodel file for SO301000
  3. ts – An additional TypeScript file that defines some additional view models (optional, it just makes the main SO301000.ts file less dense to read.

These files together make up what used to be a single SO301000.aspx and SO301000.aspx.cs pair in the current Acumatica UI paradigm. In further blog posts I will detail how these files can be edited.

Creating your own page

If you want to create a brand-new page, follow the following steps:

  1. Copy an entire directory of an existing page
  2. Rename the directory to your site map ID, e.g. LS301000
  3. Rename the .html and .ts file to the site map ID you have chosen in step 2
  4. Build your changes as described in the next section of this post.

Building your changes

If you create a new screen, or update the source files of an existing screen, you have to rebuild the screens. You can perform this as follows:

  1. Make sure that you have node and npm installed. Here is a guide: Set up NodeJS on native Windows | Microsoft Learn
  2. In windows terminal, navigate to the {siteRoot}\frontendsources path
  3. Run the following commands:
    1. npm run getmodules
    2. npm run build
  4. Now, you can either view your changes to the page, or add a site map entry mapping to a new folder.

If you have issues after building, where you still don’t see your changes reflected in the application, you can clear the application caches, which can be performed by pressing a “Clear Caches” button on the “Apply Updates” screen (SM203510), and afterwards your changes should be visible.

Summary

After listening to talks from the platform team about the new UI now for several years, I am happy to finally be able to play with it myself now! This is a major step keeping Acumatica the most modern ERP available.

Happy Coding!

Reusable ASPX Definitions

Introduction

Acumatica has spent a lot of time thinking about making its xRP Framework as developer-friendly as possible. In many different places, efforts have been made to reduce code duplication and to provide quasi “object-oriented” methods for managing elements in the framework. One of those places I will demonstrate to you today is the definition of ASPX Pages.

When one is writing custom ASPX pages in Acumatica ERP, the xRP framework allows for reused ASPX definitions to be referenced from a common file, instead of having to copy and paste a common control over and over across multiple pages. Imagine you have three pages, all with the same popup control.

ASPX-Graphic

This is a bit dangerous because if you one day need to make a change to the popup definition, you have to copy the changes across 3 different files, and remember all the files in which the change needs to be made! Sounds like a recipe for a bug.

What you can do instead, is extract the popup definition out of the ASPX files, and into its own include file (use file extension .inc)

ASPX-Graphic

Acumatica will pull the ASPX definition out of the PopupDefinition.inc file, and combine it with the IN000000.aspx, IN000001.aspx and IN000002.aspx files. Therefore, you only need to make changes to the popup definition in one place, and it will be included wherever it is referenced!

Conclusion

This is a very useful tool for creating common ASPX definitions across multiple Acumatica screens. Along with common business logic extensions, one can write a single definition for business logic and presentation, and use it across many pages.

Cheers to another tool to avoid code duplication!

Hope this has been helpful and always…

Happy Coding!

Popup Layers in Acumatica Side Panels

Acumatica has a very powerful new tool UI Layout: Side Panels. You can see them in action on screens such as “Sales Orders”

You can both enter a Sales Order on the left panel, and check AR Aging on the right panel. But what if the right panel has an action that usually triggers a popup layer? For example of such an action, “Pay Bill” will redirect you to the payments adjustments screen with the bill you had open applied to it. If you don’t redirect properly, it can break the side panel.

On the graph that drives the screen on the side panel, add an action that performs some sort of graph initialization. Usually you would throw a PXRedirectRequiredException to pop up the screen. But instead, use this code to keep things working within the side panel.

        public PXAction<AAMPolicyTransactionFilter> ActionAddActivity;

        [PXUIField(DisplayName = "Add Task")]
        [PXButton(CommitChanges = true)]
        public void actionAddActivity()
        {
            // Initialize Graph Here
            PXRedirectHelper.TryRedirect(graph, PXRedirectHelper.WindowMode.InlineWindow);
        }

Adding Activities and Tasks to a Custom Screen – 23R1

In Acumatica 23R1, the way you add activities and tasks to a custom screen has changed. You can see a demo of this functionality on the Customers (AR303000) Screen

In version 22R2 and earlier, all the functionality was wrapped into a single dataview:

 public CRActivityList<YourMainGraphDAC> ActivityList;

In 23R1 however, this has now been replaced by two graph extensions

public class YourCustomGraph_ActivityDetailsExt_Actions : ActivityDetailsExt_Actions<
    YourCustomGraph_ActivityDetailsExt, YourCustomGraph, YourMainDAC>
{
}

public class YourCustomGraph_ActivityDetailsExt : ActivityDetailsExt<YourCustomGraph, YourMainDAC, YourMainDAC.noteID>
{
}

After adding those two graph extensions, you can also reference the following include file in your ASPX.

<!--#include file="~\Pages\CR\Includes\ActivityDetailsExt\ActivityDetailsExt.inc"-->

That will automatically add all the necessary elements to the ASPX page to recreate what was shown on the ‘Customers’ screen

Using a Single ‘B2C’ Customer in the Acumatica Commerce Connector

The default behavior of the Acumatica commerce connector is to synchronize every customer from the external Ecommerce system. A lot of clients have expressed interest in keeping the customer data from cluttering their ERP and instead mapping all orders to a single “B2C” customer, with each order with an overridden shipto/billto address and contact. I will show you how to set up the entity mapping to make this occur.

Assumption:

we need to use only one customer for all the B2C orders created in Shopify, the Customer ID is B2C, and the Customer Name is B2C customer.

Steps:

1.      Create a new customer in the Acumatica system with customer ID B2C and name it B2C customer (for example)

2.      Go to commerce> Shopify Stores > select the store you want to update.

3.      Go to Entity Settings and select sales order, and then do the following in the import mapping:

The results:

Any new order will be directly assigned to B2C customer, any payment, refund, or return will follow the same customer (B2C) assigned to the order.

Developer Preview of the New Modern UI

I have been exceedingly excited about the new Acumatica Modern UI and have been waiting with bated breath for it to be released. In Acumatica 23R1, released on 4/4/2023, it is finally available to preview. I am going to show you how to enable it so you can see it too.

Step 1 : Edit the web.config file

Under the <Appsettings> tag of the web.config file, add the following:

<add key="EnableSiteMapSwitchUI" value="true" />
EnableSiteMapSwitchUI is added to the <Appsettings> tag

Step 2: Enable the modern UI for certain pages

The modern UI is only available for certain pages. You can see which pages under the site path \FrontendSources\screen\src\screens. Navigate to the “Site Map” screen and make sure the “UI” column is visible.

The colmns window allow you to select which columns are visible in the grid

Set “UI” to “Modern” on the sites you want to see.

Step 3: View the Page

Voila, the screen is rendered using the new UI!

The final modern UI result

After listening to talks from the platform team about the new UI now for several years, I am happy to finally be able to play with it myself now!

Allowing SystemDefaultTlsVersions

Recently, I was integrating Acumatica with an ActiveMQ message queue. I was having a problem connecting and getting the following error:

The specified value is not valid in the 'SslProtocolType' enumeration.
Parameter name: sslProtocolType

   at System.Net.Security.SslState.ValidateCreateContext(Boolean isServer, String targetHost, SslProtocols enabledSslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, Boolean remoteCertRequired, Boolean checkCertRevocationStatus, Boolean checkCertName)
   at System.Net.Security.SslStream.AuthenticateAsClient(String targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation)
   at Apache.NMS.ActiveMQ.Transport.Tcp.SslTransport.CreateSocketStream()
   at Apache.NMS.ActiveMQ.Transport.Tcp.TcpTransport.Start()
   at Apache.NMS.ActiveMQ.Transport.WireFormatNegotiator.Start()
   at Apache.NMS.ActiveMQ.Transport.TransportFilter.StartAsync()
   at Apache.NMS.ActiveMQ.Connection.d__225.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Apache.NMS.ActiveMQ.Connection.d__206.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Apache.NMS.ActiveMQ.Connection.CreateSession()

After digging into the pull requests, I saw that the following internal property on the ServicePointManager was what was giving me grief, not allowing Windows to default to the latest TLS version, even though the ActiveMQ .Net client wanted to:

update getAllowedProtocol default return value by PatMealeyTR · Pull Request #21 · apache/activemq-nms-openwire (github.com)

_SslState.cs (microsoft.com)

This property can be enabled in the web.config file, or in the system registry. I chose to add it to the system registry. It is suggested that you also add the following registry keys here to make sure you don’t default to unsecure TLS/SSL protocols. (WARNING, CHANGING THE REGISTRY CAN BREAK YOUR COMPUTER, PLEASE VERIFY THIS WILL WORK IN YOUR SITUATION)

Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' -Name 'DisabledByDefault' -Value '1' -Type DWord

Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' -Name 'Enabled' -Value '1' -Type DWord

Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' -Name 'DisabledByDefault' -Value '1' -Type DWord

Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' -Name 'Enabled' -Value '1' -Type DWord

The key you need to add to also allow the system to default to a TLS protocol (for .NET Framework 4.8) is here:

Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft.NETFramework\v4.0.30319

Add a DWord (32 bit) called SystemDefaultTlsVersions and set its value to Hexidecimal 1, here is a powershell script to do it

Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value '1' -Type DWord

Voila, error resolved!

Simple Field Changes Using The Workflow Engine

Recently, an ISV product I was working on required the External Ref Nbr. Field to be enabled at all times regardless of the document status. I wanted to share with you a few walls that I bounced off of so perhaps you dont have to.

Firstly, make a graph extension for the sales order screen, and override the configure method. Here is the code snippet the configure method. the fs.AddField<>()

        public override void Configure(PXScreenConfiguration configuration)
        {
            var context = configuration.GetScreenConfigurationContext<SOOrderEntry, SOOrder>();
            context.UpdateScreenConfigurationFor(
                c => c.WithFlows(flowContainer =>
                {
                    flowContainer
                        .Update(SOBehavior.SO, df => df
                            .WithFlowStates(fls =>
                            {
                                fls.Update<SOOrderStatus.completed>(flsc => flsc
                                    .WithFieldStates(fs => fs.AddField<SOOrder.customerRefNbr>()));
                                fls.Update<SOOrderStatus.shipping>(flsc => flsc
                                    .WithFieldStates(fs => fs.AddField<SOOrder.customerRefNbr>()));
                            }));
                }));
        }

Now, the traps start. If you have any experience with the workflow engine, you will probably be like “this is very normal and going to be a very short blog post”. But you will find that simply doing this will not enable the field when the document is “Completed” or “Shipping”. Argghhh, what else?

Gotcha #1 – Using a Second Order Graph Extension

Make sure you are using a second order extension, including a workflow extension reference in your extension. Without this, the workflow will configure properly:

    public class IntercoSOOrderExt : PXGraphExtension<WorkflowSO, SOOrderEntry>
    {
        public override void Configure(PXScreenConfiguration configuration)
        {
            var context = configuration.GetScreenConfigurationContext<SOOrderEntry, SOOrder>();
            context.UpdateScreenConfigurationFor(
                c => c.WithFlows(flowContainer =>
                {
                    flowContainer
                        .Update(SOBehavior.SO, df => df
                            .WithFlowStates(fls =>
                            {
                                fls.Update<SOOrderStatus.completed>(flsc => flsc
                                    .WithFieldStates(fs => fs.AddField<SOOrder.customerRefNbr>()));
                                fls.Update<SOOrderStatus.shipping>(flsc => flsc
                                    .WithFieldStates(fs => fs.AddField<SOOrder.customerRefNbr>()));
                            }));
                }));
        }
}

I am referring to specifically the PXGraphExtension<SOOrderEntry_Workflow, SOOrderEntry> call. Usually, graph extensions are simply marked as PXGraphExtension<BaseGraph>, but in the case of changing workflow via code, one must include the Workflow extension as a type parameter as well.

Gotcha #2 – Non Workflow Considerations

After gotcha #1, you probably would have been thinking all is good. However, there are some non workflow considerations. In the base SalesOrderEntry graph, when the order is completed, the caches are actually set “AllowUpdate” = false. So unless you knew to look for this too, then you would have been frustrated by your lack of progress. Override the rowselected event as follows, and additionally perform this. I thought initially that the workflow would handle everything, however more traditional methods of enabling fields sometimes are also required. Screens like the purchase order screen or other more simple screens usually don’t require this.

        public virtual void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected baseN)
        {
            baseN(cache, e);
            SOOrder doc = e.Row as SOOrder;

            if (doc == null)
            {
                return;
            }
            cache.AllowUpdate = true;
            PXUIFieldAttribute.SetEnabled<SOOrder.customerRefNbr>(cache, null, true);
        }

I hope these code snippets help you override the sales order screen. Happy Coding!