Tuesday, 27 June 2017

Redirect On Page Load

Hi There,

You know that you can embed custom pages right inside Acumatica. Best examples here are YouTube, Binq, Stack Exchange, Currency Rates or something else

But what if you need something like this, but dynamic - have a special URL depend on user, screen or other environment variables. In that case you cannot just hard-code URL in sitemap, as it will be always the same.

Here we need to be able to redirect user to special URL form code.
And I can suggest you 2 possible options to do that:

PXRedirectToUrlException
When page is shown and graph is loaded, you can throw a redirect exception that will be handled by base code and user will be redirected.
Note that using WindowMode you can control should new URL be opened in new or same window.
However, here we have a small problem, empty screen will be shown for a second before redirect takes places. Somethings it might be not acceptable.

Redirect on PageLoad
Another way is to have a dummy page and register it in the sitemap. As soon as page is loaded we can do a redirect.
In that approach you already cannot use PXRedirectException, as there is no base code that can handle that, but we can use other ways to do redirect. And actually it should be different on is current call Callback or GET/POST.

  • In case of callback we can provide special redirect instruction to response - eRedirect0 - that will be handled by Acumatica JavaScript on page
    • Number in redirect keyword means special configuration of redirect, for example redirects 0,1,5,6 will be in the same window; 3,4 in new tab; 7,8 in new window; Other numbers can suppress frame-set of keep session.
  • In case of GET/Post we can use JavaScript that will open a separate page
Here I have an example on how to do redirect to dynamic URL from PageLoad:
protected void Page_Load(object sender, EventArgs e)
{
       System.Web.HttpContext context = System.Web.HttpContext.Current;
       String url = "http://acumatica.com";

       Boolean iscallback = context.Request.Form["__CALLBACKID"] != null;
       Boolean isget = String.Equals(context.Request.HttpMethod, "GET",
StringComparison.InvariantCultureIgnoreCase);
       Boolean ispost = String.Equals(context.Request.HttpMethod, "POST",
StringComparison.InvariantCultureIgnoreCase);

       context.Response.Clear();
       if (iscallback)
       {
              context.Response.Write("eRedirect0:" + url);
       }
       if (isget || ispost)
       {
              context.Response.Clear();
              context.Response.Write("<script>");
              context.Response.Write(
                     String.Format("window.open(\"{0}\",\"{1}\");", url, "main"));
              context.Response.Write("</script>");
       }
       context.Response.Cache.SetNoStore();
       context.Response.Cache.SetNoServerCaching();
       context.Response.End();

}

Testing
If you add that code to your page code-behind, you will be able to see Acumatica page right when you click on the sitemap node.


Have a nice integration!

Wednesday, 21 June 2017

Acumatica Cloud xRP Summit

Hi All,


Join us for our inaugural interactive & engaging Virtual Developer Conference, where we have lined up some great speakers & content that will help you, the developer – whether you’re a seasoned veteran or new to the platform. Learn a new tip or trick in the advanced framework session or learn about new features in our roadmap session.

http://adn.acumatica.com/devcon/

And I am one of panelists!
Join us and we will have great fun there!

Monday, 19 June 2017

Maintain Table Schema within Customization Project

Hi There,

Today I want to stop your attention on how Acumatica handles updates for custom tables/columns.
In Case you doing medium to complex customization to Acumatica, you most probably will have a lot of custom columns.
You know you can add all these custom columns to the standard tables in Acumatica, but in this case you several issues

  • Each new column has to have an Usr prefix to each column. That is required to split standard and custom columns in one table
  • You columns depend on Acumatica table. In case Acumatica have any changes in the tables, you columns may also be affected. But do not worry, they are never be deleted.

So in case of multiple custom columns it might be good idea to split them in custom table. Using Acumatica framework you easily can create as many custom tables as you wish.
Also even if you need to link new columns to original records, you always can use Extension Tables, that actually give you ability to have benefits of custom columns and new table.

Ok, now we have a new table, but how to support it in case we need to add a new column there?

Tuesday, 6 June 2017

Validating AP/AR Accounts Balances before Releasing Imported Invoices

Hi There,

If you ever was involved in any implementation project and data migration process you have seen how complicated is to validate data after migration.
It is especially important if you have documents that have to be released - if you release wrong document than there is now way to change document anymore and you have to reverse and create it again, that basically litteres documents history.

Ok, we can see the problem, but is there any way to validate balances before releasing of the documents to minimize number of errors?

When I have faced that problem, I have tried to find a solution and basically have found it with Generic Inquiries and Pivot tables.

So I have followed that scenario:
  1. Import all AP/AR Invoice to Acumatica on Hold
  2. Created 2 Generic Inquiries that get AP and AR invoices with following fields: Type, Ref Number, Branch, AP/AR accounts, Amount in Base Currency, Customer/Vendor, Class and Date
  3. Than I have create a Pivot Table based on that GI where I have accounts as columns and document types as rows
Total on that report give me total amount that would be recorded on AP/AR account after releasing.
Basically using that report I can reconcile AP/AR accounts with Trial balance even before releasing of them. 

Thursday, 1 June 2017

Recurring Notifications about Expiring Quotes

Hi There,

Any ERP system is designed to help users as much as it is possible. And one of very common human bug is bad memory. So ERP should solve this issues by recurring notification on multiple possible documents/items.
Just as for example, how cool is to have notification on expiring contract, quote or cases, so we can take an action in advance.

Based on the previous article on notification from templates that I have shared with you few days before, we can design an processing screen that will send notifications for us.

So the main idea around it is - every day Acumatica scheduler will trigger our logic, that will get all Sales Quotes that will expire in some days form now and send notifications to them.

Monday, 29 May 2017

Link Between GL And AP/AR

Hi There,

In case you have not seen that before, Acumatica has links between GL Batch lines and AP/AR Invoices lines - Following conditions should be applied:

  • GLTrain,TranType = ARTran.TranType;
  • GLTrain,RefNbr = ARTran.RefNbr;
  • GLTrain,LineNbr= ARTran.TranLineNbr;
TranLineNbr is populated on release of a document in another module with the number of the corresponding line in that document.

Please note that TranLineNbr can be empty in some cases. For example if you have consolidation posting to GL, than multiple AP/AR trans will be combined in one GL line, in that case TranLineNbr cannot be evaluated. Specially this field is not populated when PX.Objects.GL.GLTran.SummPost is ON.

Using that knowledge you can update something in GL from AP/AR transactions. For Example:

protected void GLTran_TranLineNbr_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
       GLTran row = (GLTran)e.Row;

       if (row != null
&& row.RefNbr != null && row.TranType != null && row.TranLineNbr != null)
       {
              ARTran tran = PXSelect<ARTran,
                      Where<ARTran.tranType, Equal<Required<ARTran.tranType>>,
                             And<ARTran.refNbr, Equal<Required<ARTran.refNbr>>,
                             And<ARTran.lineNbr, Equal<Required<ARTran.lineNbr>>>>>>.
Select(Base, row.TranType, row.RefNbr, row.TranLineNbr);
              if (tran != null && tran.Date != null)
              {
                      row.TranDate = tran.Date;
              }
       }
}

Have a nice Customization.

Friday, 26 May 2017

Filtering Inventory Items by new Field

Hi There,

Have you seen a situation when you want to filter list in the selector by the custom field?
Most probably yes, as that is quite common requirement form many different users.

Here I want to show you how to do that easily based on example with custom field in Inventory Item.

Custom Field
First of all lets add a custom field to inventory item. nothing complicated, just a custom text box.

Showing Field in Selector
By default that field will not be shown in selector, but we can easily add it there by modifying PXUIField Attibute: Visibility = PXUIVisibility.SelectorVisible.

public class InventoryItemExt : PXCacheExtension<PX.Objects.IN.InventoryItem>
{
       #region UsrModuleNumber
       [PXDBString(64)]
       [PXUIField(DisplayName = "Module Number", Visibility=PXUIVisibility.SelectorVisible)]
       public virtual string UsrModuleNumber { get; set; }
       public abstract class usrModuleNumber : IBqlField { }
       #endregion

}

PXUIVisibility.SelectorVisible will make that field visible in all selectors for InventoryItem. But here is important exception - if we have defined columns in selector manually, than this field will not affect anything. But fortunately Selector for aggregate attribute [Inventory()] does not have specified list of selector fields, so Acumatica platform will dynamically generate list of fields from DAC fields where Visibility option is marked as Selector Visible.

Note: after applying that parameter you most probably need to restart whole site, as list of columns is cached in memory for all individual selectors.

Now we can see that field.

Filtering by Custom Field
Now to filter by this field we just need to add in in the list of Fast Filter Fields of selector control.
Find PXSelector -> GridProperties -> FastFilterFields
Fields added here will be automatically added for searching conditions during select to DB.

Final Testing
Now we finally ready to test it. And it works perfectly.


Have a nice customization!

Monday, 22 May 2017

Sending Notification Template from Custom Code

Hi There,

Today want to share with you how to send emails from Acumatica custom code based on Notification Templates.
User scenario is very simple - lets assume we want to send an email from Acumatica that informs about contract, quote expiration or something else. But in the same time we want to keep activity linked to our document for the future reference.
Basically, if we do that manually we need to create new Email activity, fill all details there, attach to entity and send it than,

The obvious way to automate it is usage of Notification Templates.
Notification templates are special type of emails, that can be combined dynamically with data from Acumatica. On screenshot above you can see grayed out text, that represents a name of the data view and field in Acumatica.
((Document.OrderNbr)) - Document here is data view name, OrderNbr is field name in the main DAC of the data view. Hopefully you do not need to know the name if view, as you can easily select these fields from special dialog:
Note: Because of names of the views and fields each notification template is linked with Acumatica screen. so be careful when you using notification from code - only specially designed templates (which are linked with exact screen) can work with entity you going to send.

As soon as we have notification template we can link it with any possible setup screen to use from the code.

Good, now we can actually use it from code. To send notification we are going to use TemplateNotificationGenerator class that actually will do all the dirty work for us. And we just need to provide entity and notification template to it

public static void AddEmailActivity(SOOrder order, Int32 notificationTemplateID)
{
       SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();

       SOBillingContact contact = PXSelect<SOBillingContact,
Where<SOBillingContact.contactID,
Equal<Required<SOBillingContact.contactID>>>>
.Select(graph, order.BillContactID);
Notification notification = PXSelect<Notification, Where<Notification.notificationID,
Equal<Required<Notification.notificationID>>>>
.Select(graph, notificationTemplateID);

       bool sent = false;
       string sError = "Failed to send E-mail.";
       try
       {
              TemplateNotificationGenerator sender = TemplateNotificationGenerator
.Create(order, notification.NotificationID.Value);
              sender.MailAccountId = (notification.NFrom.HasValue)
? notification.NFrom.Value
: PX.Data.EP.MailAccountManager.DefaultMailAccountID;
              sender.RefNoteID = order.NoteID;
              sender.Owner = order.OwnerID;
              sender.To = contact.Email;

              sent |= sender.Send().Any();
       }
       catch (Exception ex)
       {
              sent = false;
              sError = ex.Message;
       }
       if (!sent)
       {
              throw new PXException(sError);
       }

}

Please note that generated Email Activity is linked to exact entity (in my case it is sales order) by Note ID reference. Note ID here is a unique identifier of any record in DB, you can read more about it here.

As the result code above, you will see email activity attached to your order.

Profit!
Also note that email is create, but might not be sent automatically. It depends on how your email processing is configured.

Have a nice notifications ;)

Friday, 19 May 2017

End User Training Approach

Hi There,

Some time ago I had a new great experience for me - doing training for ERP end users.
In was interesting and different form all my previous training experience because of following things:
  • Training was half online half on site. That means that half of the team was in the room and half of the team was online, so I had to find a balance between online and offline communication.
  • End users are focused on process and not on configurations or understanding.
  • End users expect system to be fully ready.
  • We need to keep and ensure focus of onsite and remote users
  • We need to understand knowledge of attendees after training
That was interesting and challenging together. But after that training I have done some important conclusions and ideas that i want to share with you:

Friday, 12 May 2017

Calling Base Actions by example of Generating Time Cards

Hi All,

Just a short article where I can share with you customization that can generate time cards from uploaded time activities.
Customization doing following:
  • Selects the first and the last unreported (where no time-cards assigned) activity from currently selected employee.
  • Goes week by week between first and last activities
  • Create a new time card for every week using TimeCardMaint graph
  • Code also automatically submit time card for approval
  • In case there is unreported activity for already submitted or released time card, code can automatically generate time card correction and submit it again.