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!

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.

Read the rest of the post on the Acumatica Developer Blog here: Enabling Parallel Processing on Acumatica Processing Screens – Acumatica Cloud ERP

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.

Read the rest on the Acumatica Developer Blog here: Creating a New Notification in Acumatica – Acumatica Cloud ERP

Syncing ShipStation data with the Acumatica Commerce Connector

A common integration companies with existing Shopify accounts have is to link Shopify directly to ShipStation for label generation and management. Product availability is managed inside of Shopify. Once they graduate to a more feature rich ERP however, the inventory is managed inside of the ERP instead of Shopify, with Shopify and Shipstation being extensions of the core ERP.

I recently implemented a Commerce Edition install of Acumatica who followed the pattern above. We used the out of the box Shipstation integration for Acumatica, and the Commerce Edition for talking to Shopify. One of the pain points we ran into was getting the ShipStation tracking and carrier information back into Shopify. Acumatica only allows you to map from the following field (as of 2021R2):

The Carrier is synced from the ShipVia field
The tracking number is synced from the Tracking Number screen on the Packages tab

This would be all well and good if the customer was using the out of the box Carrier Integration from Acumatica. However, their shipment info writes here:

Additionally, the ability to map to any shipment fields is not implemented yet (again, as of 2021R2). How I solved this is with a simple SOShipmentEntry customization, when the shipstation fields are updated, it creates a package and assigns the written tracking number to it (be sure all your ship vias have the “DEFAULT” box assigned to them):

Additionally, I created a cross reference between the ShipVia and the carrier field, because unless your ship via is exactly “USPS” “UPS” or “Fedex” it will not tell shopify what the carrier is properly. You should create the substitution list with exactly the same ID:

And now, when your shipments sync using the Commerce connector, the tracking info will populate from ShipStation properly!

Customizing the Acumatica Portal

UPDATE : 2021R1 as implemented a suite of upgrades to Portal Customization and these tricks are no longer required

I just recently got asked to develop a series of modifications to the Acumatica self service portal (2019 R2). After a cursory glance I saw “oh there are customization projects that can be installed, this will be exactly the same as customizing the base version”, however there were a few things that tripped me up that maybe I can pass along and help you.

After a little trial and error, here are the steps I took to successfully adding a new page to the Self Service Portal: (If you already have an existing instance skip the first 3 steps)

1. Install Acumatica ERP normally

2. Setup the Admin password by using username: Admin and password: Setup

3. I cleared out the sales demo customizations just to be safe but this probably isn’t necessary

4. Install the portal using the Acumatica config tool, selecting the database that your main ERP instance is running on, but then choosing the “Create Portal” radio button (prev versions have a checkbox), I personally made a virtual directory under the main instances ERP site

5. Navigate to Administration=>Customization Projects and Add a new project, hit save and click on the link expecting the Customization browser to appear

6. Flip over your desk when it denies you access even when you are logged into ‘the almighty admin account’

7. Thanks to Kurt Bauer and Brendan Hennelly on stackoverflow for providing the following SQL Script that will solve this problem:

INSERT INTO dbo.PortalMap
(

CompanyID,
Position,
Title,
Description,
Url,
ScreenID,
CompanyMask,
NodeID,
ParentID,
CreatedByID,
CreatedByScreenID,
CreatedDateTime,
LastModifiedByID,
LastModifiedByScreenID,
LastModifiedDateTime,
RecordSourceID

)

SELECT CompanyID,
Position,
Title,
Description,
Url,
ScreenID,
CompanyMask,
NodeID,
ParentID,
CreatedByID,
CreatedByScreenID,
CreatedDateTime,
LastModifiedByID,
LastModifiedByScreenID,
LastModifiedDateTime,
RecordSourceID

FROM dbo.SiteMap
WHERE ScreenID = 'AU000000'
AND NOT EXISTS
(

SELECT *
FROM dbo.PortalMap
WHERE CompanyID = dbo.SiteMap.CompanyID
AND ScreenID = dbo.SiteMap.ScreenID

);

For your information: the ERP and Portal share the same database, but when it comes to their respective site maps, they use two different tables: ERP => SiteMap Portal => PortalMap. This will cause an additional problem that we will explore later.

8. Close all your browser windows, open up cmd and use the iisreset command to reset the application if you are installing this on a local development machine like me. Otherwise, reset the application in however other way you need to

9. Now the customization project browser opens normally!

10. Go to Pages=>New Page and you will see the familiar popup appear.

11. However after filling out the popup as normally and clicking ok, you will notice the page does not appear properly in the “Pages” grid

12. Just delete the blank record that appears, and delete the corresponding sitemap entry as well in the sitemap section
– From what I gather, the entire sitemap section in the customization project is mapped to writing things into the “SiteMap” table and doesn’t do anything to the PortalMap table. We will need to add things in manually. So this record in the grid does nothing for the portal. Additionally you cant manage the sitemap using the manage site map button here anyway, so really its better if we just pretend this whole section doesn’t exit.

13. Go to Administration=>Portal Map and manually add your new page to the “Portal Map”

14. Now you can add the page normally to the Modern UI and it will work properly

Its highly likely that this is only the start of the wrinkles that one must face when customizing the portal, so I will continue to fill you in as I go along.

Report History

Just recently a VAR that I have been working with began training one of his resellers’ salespeople on the the custom price multiplier I have been developing. Naturally, the salespeople have lots of wants and desires, and there were a few small features missing that had to be added.

This highly specific customer pricing required a “Price List” report generated from items chosen off the price multiplier screen. These reports would be emailed to the customer contact.

But what if the salesperson was out for vacation and the customer had lost the PDF? Its obvious that a medium-term cache of these price lists was needed.

The sales person generates the Price List as one would create a report normally, using the Reports drop down

When the Report is generated, a record in this table is created, and the file is attached using the PXNoteAttribute functionality so that it is tied to the document management system

Thanks to Sergey for providing extremely helpful explanation on how to do this

Get Report PDF file Programatically
How to pass a Report Parameters from a Screen