Stephen Smith's Blog

Musings on Machine Learning…

Posts Tagged ‘asp.net mvc

Accessing Sage 300c’s Business Logic from the Web UIs

with 5 comments

Introduction

In the Sage 300 VB UIs, a user would do something in the UI (press a button or tab out of a field) and then the VB UI would be notified of this and would possibly execute a number of Sage 300 Business Logic (View) calls and based on their results update various other fields and possibly provide user feedback via a message box.

In the Web UIs we want to do similar processing since we want to re-use the tried and true Sage 300 Business Logic, but we have to be careful since now the Web UI is half running as JavaScript in the Browser and half running as .Net assemblies on the server. We have to be careful of the communication between the Browser and the server since there will be quite a bit of latency in each call over the Internet. Generally, we never want one user action to result in more than one call to the server (and ideally most user actions shouldn’t result in any calls to the server).

This blog post talks about where you put your code to access the Sage 300 Business Logic and how a UI interaction in the Browser flows through the system to execute this business logic.

Architecture

In the new Web UI architecture, we access the Sage 300 Business logic from our Business Repository classes. The base classes for these provide a wrapper of the Sage 300 .Net API to actually access the Views, but hiding the details of things like session and database link management. Then above this layer are the usual ASP.Net MVC Models and Controllers.

cna2arch

The Sage 300c Web UI Architecture

Generally, we want to put all this logic in the Business Repository so it can be used by multiple higher level clients including the Web UIs, our new RESTful WebAPI and services which are available for other applications to utilize.

Some of the layering is in place ready for additional functionality like customization. We need provide the common interfaces that can act as the basis for programmatic customization by inserting custom modules into the processing flow via Unity Interception.

Moving VB Code

In VB we often make lots of Business Logic (View) calls all interspersed with lots of interactions with various UI controls. This code has to be separated where the Business Logic (View) calls will go in the Business Repository which runs on the server and then the part that interacts with the controls has to move to the JavaScript code running in the Browser. The Business Repository has to provide the necessary data in a single payload which the mode/controller will transport to the Browser for processing.

The easiest way for the repository to transfer data is to have the model provide extra fields for this communication. This way no extra layers need to be involved, the business repository just populates these fields and the JavaScript layers pull them out of the returned JSON object and use them.

But you only want to add so much to the model, since you don’t want it to be too cumbersome to move around and you might want more focused calls. For these we usually define special calls in the controller and these go through a services layer to execute the code in the repository. The service call only passes the exact data needed (like parameters to a function) and knows what data to expect back.

Example

Adding extra fields to the model is fairly straight forward, so let’s trace through the logic of making a services call. In this example we’ll look at the simple case of checking a customer’s credit limit in A/R Invoice Entry (which is using a stateful business repository). We’ll start up in the JavaScript code and work our way down through the layers to get an idea of who does what.

So let’s start near the top. In the A/R Invoice Entry UI there are various times when the credit limit needs to be looked up. So the JavaScript code in the InvoiceEntryBehaviour.js file has a routine to initiate this process. Note that server calls are asynchronous so the response is handled in a callback function.

    showCreditLimit: function (result) {
        // Open Credit Check pop up window
        if (result) {
            var jsonResult = JSON.parse(result);
            if (jsonResult.ShowCreditCheck) {
                arInvoiceEntryRepository.getCreditCheck(jsonResult.id,
                    sg.utls.kndoUI.getFormattedDate(jsonResult.docDate),
                    sg.utls.kndoUI.getFormattedDate(jsonResult.dueDate),
                    "n" + invoiceEntryUI.CurrencyDecimals, jsonResult.totalPaymentAmountScheduled,
                    jsonResult.prepaymentAmount);
            } else {
                onSuccess.onCreditClose();
            }
        }
        invoiceEntryUI.ModelData.isModelDirty.reset();
    },

This calls a function in the InvoiceEntryRepository.js file to actually make the call to the server:

    getCreditCheck: function (customerNumber, documentDate, dueDate, decimals, invoiceAmount,
        prepaymentAmount) {
        var data = {
            id: customerNumber,
            docDate: documentDate,
            dueDate: dueDate,
            decimals: decimals,
            totalPaymentAmountScheduled: invoiceAmount,
            prepaymentAmount: prepaymentAmount
        };
        sg.utls.ajaxPostHtml(sg.utls.url.buildUrl("AR", "InvoiceEntry", "GetCreditLimit"), data,
              onSuccess.loadCreditLimit);
    },

This will initiate the call to the server. The URL will be built something like servername/Sage300/AR/InvoiceEntry/GetCreditLimit. The ASP.Net MVC infrastructure will use configuration by convention to look for a matching entry point in a loaded controller and hence call the  GetCreditLimit method in the InvoiceEntryController.cs file:

        [HttpPost]
        public virtual ActionResult GetCreditLimit(string id, string docDate, string dueDate,
               string decimals,decimal totalPaymentAmountScheduled, decimal prepaymentAmount)
        {
            try
            {
                return PartialView(AccountReceivable.ARInvoiceCreditCheck,
                      ControllerInternal.GetCreditLimit(id, docDate, dueDate, decimals,
                      totalPaymentAmountScheduled, prepaymentAmount));
            }
            catch (BusinessException businessException)
            {
                return JsonNet(BuildErrorModelBase(CommonResx.NotFoundMessage, businessException,
                    InvoiceEntryResx.Entity));
            }
        }

Which will call the InvoiceControllerInternal.cs GetCreditLimit method:

        internal ViewModelBase<CustomerBalance> GetCreditLimit(string customerNumber,
            string documentDate,
            string dueDate, string decimals
            , decimal totalPaymentAmountScheduled, decimal prepaymentAmount)
        {
            var creditBalance = Service.GetCreditLimit(customerNumber, totalPaymentAmountScheduled,
                 prepaymentAmount);

            if (creditBalance.CalcCustomerOverdue == CalcCustomerOverdue.Yes &&
                creditBalance.CustomerBalanceOverdue > creditBalance.CustomerAmountOverdue)
            {
                creditBalance.CustomerCreditMessage = string.Format(
                        InvoiceEntryResx.CustCreditDaysOverdue,
                        creditBalance.CustomerDaysOverdue,
                        creditBalance.CustomerBalanceOverdue.ToString(decimals),
                        creditBalance.CustomerAmountOverdue.ToString(decimals));
            }

            if (creditBalance.CalcNatAcctOverdue == CalcNatAcctOverdue.Yes &&
                creditBalance.NatAcctBalanceOverdue > creditBalance.NatAcctAmountOverdue)
            {
                creditBalance.NationalCreditMessage = string.Format(
                        InvoiceEntryResx.NatCreditDaysOverdue,
                        creditBalance.NatAcctDaysOverdue,
                        creditBalance.NatAcctBalanceOverdue.ToString(decimals),
                        creditBalance.NatAcctAmountOverdue.ToString(decimals));
            }

            creditBalance.CustomerCreditLimit =
                 Convert.ToDecimal(creditBalance.CustomerCreditLimit.ToString(decimals));
            creditBalance.CustomerBalanceVal =
                 Convert.ToDecimal(creditBalance.CustomerBalanceVal.ToString(decimals));
            creditBalance.PendingARAmount =
                 Convert.ToDecimal(creditBalance.PendingARAmount.ToString(decimals));
            creditBalance.PendingOEAmount =
                 Convert.ToDecimal(creditBalance.PendingOEAmount.ToString(decimals));
            creditBalance.PendingOtherAmount =
                 Convert.ToDecimal(creditBalance.PendingOtherAmount.ToString(decimals));
            creditBalance.CurrentARInvoiceAmount =
                 Convert.ToDecimal(creditBalance.CurrentARInvoiceAmount.ToString(decimals));
            creditBalance.CurrentARPrepaymentAmount =
                 Convert.ToDecimal(creditBalance.CurrentARPrepaymentAmount.ToString(decimals));
            creditBalance.CustomerOutstanding =
                 Convert.ToDecimal(creditBalance.CustomerOutstanding.ToString(decimals));
            creditBalance.CustomerLimitExceeded =
                 Convert.ToDecimal(creditBalance.CustomerLimitExceeded.ToString(decimals));
            creditBalance.NatAcctCreditLimit =
                 Convert.ToDecimal(creditBalance.NatAcctCreditLimit.ToString(decimals));
            creditBalance.NationalAccountBalance =
                 Convert.ToDecimal(creditBalance.NationalAccountBalance.ToString(decimals));
            creditBalance.NatAcctOutstanding =
                 Convert.ToDecimal(creditBalance.NatAcctOutstanding.ToString(decimals));
            creditBalance.NatAcctLimitLeft =
                 Convert.ToDecimal(creditBalance.NatAcctLimitLeft.ToString(decimals));
            creditBalance.NatAcctLimitExceeded =
                 Convert.ToDecimal(creditBalance.NatAcctLimitExceeded.ToString(decimals));

            return new ViewModelBase<CustomerBalance> { Data = creditBalance };
        }

This routine first calls the GetCreditLimit service in InvoiceEntryEntityService.cs:

        public virtual CustomerBalance GetCreditLimit(string customerNumber,
            decimal totalPaymentAmountScheduled, decimal prepaymentAmount)
        {
            var repository = Resolve<IInvoiceEntryEntity<TBatch, THeader,
                 TDetail, TPayment, TDetailOptional>>();
            return repository.GetCreditLimit(customerNumber,
                 totalPaymentAmountScheduled, prepaymentAmount);
        }

Who then calls the repository GetCreditLimit routine in InvoiceEntryRepository.cs. This routine then appears to do regular View processing using the base repository wrapper routines that insulate us from the session/dblink handling logic as well as do some basic error processing:

        public virtual CustomerBalance GetCreditLimit(string customerNum,
            decimal totalPaymentAmountScheduled, decimal prepaymentAmount)
        {
            _header.Read(false);
            _creditCheck.SetValue(CustomerBalance.Fields.CustomerNumber, customerNum);
            _creditCheck.SetValue(CustomerBalance.Fields.CurrentARInvoiceAmount,
                totalPaymentAmountScheduled);
            _creditCheck.SetValue(CustomerBalance.Fields.CurrentARPrepaymentAmount,
                prepaymentAmount);
            _creditCheck.Process();
            return _creditCheckMapper.Map(_creditCheck);
        }

Finally, down in the business repository, the code should look fairly familiar to anyone you has done any C# coding using our Sage 300 .Net API. Further this code should also appear somewhere in the matching VB code, and besides being translated to using the .Net API, its become quite separated from the UI control code (in this case the JavaScript).

At the end of this all the calls return propagating the returned data back to the Browser in answer to the AJAX call that it made.

It might look like a lot of code here, but remember the business repository and JavaScript bits have corresponding VB code. Then the other layers are there to make all the code more re-usable so that it can be used in contexts like WebAPIs and allow interfaces to provide the hooks needed for customization.

Summary

This article is intended to give you an idea of where to put your code that accesses the Sage 300 Business Logic and then how to call that from the Web UIs. There are a lot of layers but individually most of the layers are fairly simple and most of the code will appear in the Business Repository and the JavaScript behavior code.

Advertisements

Written by smist08

February 12, 2016 at 3:27 am

Sage 300 Web UI Internals

with 10 comments

Introduction

At Sage Summit 2015 we introduced our new Web UIs for Sage 300. I’ve blogged a bit on the various user facing parts and a bit on the technologies used, but I haven’t had a chance yet to get into the internals of how they work. We’ve released the Web UIs for the Financial Modules and will be releasing the Operations Modules early in 2016. With these we will be releasing our SDK as well. Over time I’ll blog quite a bit about the details of all the components, but first we need to layout the major building blocks. Below is the architecture diagram I’ve shared before which is the architecture for the main Web UIs and a block for RESTful Web APIs, but there are some other components that need mentioning as well.

cna2arch

I’m going to start at the bottom of this diagram and work up. However there are some other components that aren’t mentioned here that we will bring in.

Within our framework we provide a lot base classes that you can inherit from, so generally the only code you need to provide is where something is different than the standard protocols or behaviors. When looking for framework components to help you, besides looking for services to call, look for classes that do 90% of what you need that you can extend (inherit from). ASP.Net MVC also makes extensive use of “convention over configuration”, so for a lot of things if you follow the standards then it saves you a lot of work and makes a number of things just work magically. Similarly we do the same things, so we can find and use your components as long as you follow the standards as outlined.

Business Logic

The Sage 300 Views are still the same. We didn’t even update the C View template for supporting this new framework. All the regular Sage 300 Business Logic (Views) is accessed through our .Net API.

Business Repositories

The Business Repositories convert our View API into something more like an ASP.Net MVC API. The Business Repositories are similar to the definitions that we generate from ViewDoc for different systems to make accessing all the various View fields more natural in the given framework. They provide support for all the usual View UI related items like presentation lists and field masks. Generally this is where all the View programming is placed that is needed by the UI. This makes all this programming available to everyone including UIs, standard components and Web APIs.

Models

The models are true ASP.Net MVC Models. They are typically built on a given business repository. The reason for the separation between the Business Repositories and the Models is that the Business Repositories are more general use and can easily be consumed by UI elements whose Models are more generic like for Finders or Import/Export.

State-full Versus Stateless

There are two basic ways that our UIs operate, stateless and state-full. Ideally everything should be stateless, but this becomes somewhat impractical when dealing with large accounting documents. Basically in stateless operation, each RPC operation is completely independent and can be processed with no knowledge of what’s gone before. For state-full transaction a document is built up in the Sage 300 Business Logic as the user enters the document, this is more similar to how the VB UIs work. Generally only the bigger document entry UIs are state-full now. Use of the two is similar and most of the details are hidden by which base class you inherit from (whether a state-full or stateless one), but you must beware of the context as you do your programming.

Controllers

The controllers mediate the translate the RPC calls from the JavaScript components running in the Browser to making calls the Models, Services and other API calls. Sounds like a lot of work, but typically these classes are quite compact.

Services

Often in VB programming, you handle events from the user and as a result of a UI event you do a certain amount of View calls (perhaps say 20 of these). This sort of thing also happens in the Web UI, but we certainly can’t do 20 or so RPC calls to the server as a result of a single UI interaction. This is where services come in. Then when such a UI event is triggered, a single RPC call is made to the server, the controller dispatches this to the service who calls the correct method in the Business Repository which then makes the 20 or so View calls to do the real work. The service then marshals any returned data in the response. Of course this all happens asynchronously. Generally if you are wondering where to move your processing code from a VB UI it would be into one of the Business Repositories and then wire up a service to allow it to be called from the Browser. Services are also used to initiate long running processes which we’ll talk about later.

Views

We use ASP.Net MVC Razor Views for our View components. This is a templating technology that allows us to dynamically generate the HTML using C# (which is embedded in the original HTML). For instance we allow translations to many languages, so rather than having embedded text strings, and then needing a separate copy of the HTML for each supported language, we have embedded C# functions that look up the correct language string which are processed and evaluated when we go to render the HTML. This is very handy and powerful in generating the HTML for each screen based on the various user and application contexts. There is also quite a bit of JavaScript associated with each screen to handle the various dynamic parts of the UI. Much of this is just a matter of wiring up the components like the screen UI elements to data binding or standard UI controls like the Kendo ones to our system JavaScript framework.

Sage.CNA.Windows.Service

We don’t want to run long running processes like I/C Day End or Posting Batches directly from IIS, since even with full multi-threading support this can adversely affect the responsiveness of the UIs for other users. So we run a Windows Service and when a long running process is initiated we ask the Windows Service to run it, and just return from IIS. Then the UI can inquire periodically for meter status updates and to notify the user when it’s done. There is a lot of standard framework support for doing this and you just need to setup a service to initiate and monitor the process. Generally we do this for any Sage 300 process that pops up a progress meter.

KPIs

KPIs are really just like any other UI. They just have a different size and are run in a different place on the Home Page. There are a few standards to follow to match the other KPIs and a few UI controls that aren’t used anywhere else, but are otherwise just standard UIs.

Web API

Although this component isn’t included in the original Sage 300 2016 release, there is full support for exposing RESTful Web Service APIs. These leverage your Model code to expose the model as an API. Then there is some support for hiding inappropriate methods and adding a bit more attribute information to help users.

Printing

Like previous versions of Sage 300, we use Crystal Reports to print out our reports. We basically use the same report API as we used in VB. We then have support to call this API from our UI framework and to render the report in the Crystal HTML Viewer. Then the rest of the report UI is just the same as any other UI, gathering up data from input fields. Printing might also have to initiate a process on the Windows service before printing, like generating the data for an aging report, before initiating the report.

Other System Components

There are system components for things like Finders and Import Export. These just need to be setup and wired in correctly. Then there are components like the editable grid which requires a certain amount more support in your UI. And then higher level components like the Optional Fields Control that is built on the Grid. There are a few other controls and wrappers for things like dates, fiscal year/periods, masked edit control, drop down list based on presentation strings, etc.

Summary

This article is the start of drilling down into the internals of how our new Web UIs work. Hopefully it gives a flavor of the components that combine to make a Web UI. The UIs are fully Ajax Web Applications, so there is work to do both in the Browser in HTML, CSS and JavaScript as well on the server in C#. There are quite a few frameworks involved from Sage, Microsoft, Telerik and a number of open source libraries. The trick is to not be overwhelmed, but to start with a simple empty screen and one by one learn and add the elements that you need. The good thing is that the base classes you extend (inherit from) provide much of the standard code and often you don’t need to add very much at all.

 

Written by smist08

October 17, 2015 at 10:20 pm

Skills for Developing for Sage 300c

with 6 comments

Introduction

With the Web UIs in Sage 300c rolling out in a couple of weeks, there is a lot of interest in the SDK and how to develop for this platform. We are still putting together the SDK, but in the meantime you can learn the technologies that are involved in developing our new Web UIs. Generally we’ve used off the shelf components both commercial and open source to develop our new UI framework. The good thing about this is that there are lots of resources available to learn the various technologies involved, including books, web sites, samples, videos, courses, etc.

cna2arch

We’ve generally tried to use all these tools in very standard ways. For instance we don’t add large amount of code to custom controls to change their behavior, we’ve kept it standard and only changed their appearance using CSS. We use the ASP.Net MVC framework in a natural way, so what you learn from the various standard resources is all applicable.

Due to the nature of programming, you can often do quite a few creative things. There is nothing to stop you using other libraries or tools than mentioned here. However one of the points of listing these is to let you know which we use, which means if you ask DPP support, these are the tools and libraries that we know about and can help answer questions about. You are welcome to use other tools, but we may not be able to provide much help on them.

Languages

C#: All server side programming above the Sage 300 .Net API is written in C#. This is a very powerful object oriented extension to C which is quite similar to Java. It has an extensive standard class library. The tools and environments that support C# are really powerful and productive.

JavaScript: All client/browser programming is in JavaScript. We support newer browsers which all now support fairly standard implementations. The main pitfall of JavaScript is that it’s an interpreted language that will process almost anything, often with surprising results.

Framework

ASP.Net MVC: Do not confuse this with ASP.Net (no MVC). This is a completely different framework which is much more powerful and gives a really solid framework for web development.

Web

HTML: HTML controls the general layout of web pages in the browser, however it isn’t as important as it used to be. Our HTML is generated from Microsoft Razor Views, so a good portion of the HTML is actually represented as C# code. Then layout is largely controlled by CSS and not by HTML elements.

CSS: Cascading Style Sheets (CSS) control the layout and look of all the elements on the HTML page. We provide a global stylesheet which has most of what you need. However many screens need to define custom elements for their own fine grained control. We will provide a style catalog and samples of all the main UI elements.

Tools

Visual Studio 2013: This is the main IDE where we develop and debug our code. This is a very powerful and productive environment to write, build and debug code. Chances are we will be on to VS 2015 by the time the SDK ships, but for now this is what we developed our 2016 release in. We use the premium edition because that is what comes with our particular MSDN subscription, but any edition will probably be fine.

ReSharper: This is an optional tool that we’ve licensed for all our developers. We’ve found it very helpful to improve the quality of our code and to help with refactoring.

GitHub: Although using a source code control system is optional, you should be using one. Using any of Git, Subversion, TFS, etc. is fine, but you should really be using one. We use GitHub because it is very fast and reliable. The real benefit is that it’s fast for everyone when you have large internationally dispersed teams.

TeamCity: This is another optional component. You can just build out of Visual Studio. We use TeamCity, but you can also use other automated build systems like Jenkins. Its generally a good practice to have a continuous build/integration system that is always building things as they are developed, deploying them and running automated tests to ensure that things aren’t broken.

Libraries

Kendo UI: This is the library of UI widgets that we use like the editable grid and date control. When we started this project this was strictly a commercial product. However half way through they created the open source Kendo UI Core which has all the controls we use for creating Accounting Screens except the editable grid control. For the grid control and the graphical controls in the KPIs,  you will need to purchase a license for the professional edition.

KnockoutJS: We use knockout for data binding between the UI controls and the MVC models. When we started the project the data binding in Kendo UI didn’t meet our needs so we evaluated alternatives. We found knockout and it did everything we needed so we’ve continued to use it. In the meantime Kendo has improved and AngularJS has become popular, but we’ve stuck with Knockout (which is popular again).

JQuery: Most modern web apps use JQuery. Although its main use of insulating people from browser differences isn’t as important and most browsers have natively implemented its main features, it is still an important library and we use it extensively.

.Net Framework 4.5.1: Since all our server components are written in C# and using the ASP.Net MVC framework, of course we are using the .Net framework. For the 2016 release we are at version 4.5.1. However by the time the SDK ships this will probably be at a higher version.

Sage 300 .Net API: To integrate to the standard Sage 300 Business Logic, we use Sage 300’s .Net API. So when writing code in the MVC models to perform Sage 300 processing, you are writing code to this API.

Crystal Web Viewer: We provide a complete framework for handling Crystal Reports, so you don’t need to directly interact with Crystal. We generate reports using the same code as the desktop version, but then display the result in Crystal’s HTML viewer rather than the ActiveX one.

Unity: This is a library for doing dependency injection in .Net. You probably don’t need to use this directly, but it’s useful to understand how your DLLs are being loaded and why the startup process works like it does.

Summary

This was a quick list of the various tools and technologies we used to create our new Web UIs. Hopefully it gives you a starting point of things to start learning about, if you are interested in Sage 300c development.

Written by smist08

September 19, 2015 at 4:33 pm

Sage 300 Moves into the Browser

with 53 comments

Introduction

Probably one of Sage’s worst kept secrets is that we’ve been working on a true Web version of Sage 300. Now finally at Sage Summit 2015 the cat is out of the bag and the product is officially announced as Sage 300c. You can see the announcement in this video.

We will be including Web UIs for G/L, A/P, A/R, Bank, Tax, C/S and A/S screens in the upcoming Sage 300 2016 release which is slated for October 2015 (where the year in the product roughly matches car model years).

These are true web screens that run in all the main browsers including IE, Edge, Chrome, Firefox and Safari. There are no plug-ins required, everything is pure HTML, CSS and JavaScript. This means you can access these screens from all sorts of devices like Macs, Windows Desktops and Laptops, Chromebooks, iPads, Android Tablets, Linux Workstations, Raspberry Pi’s and many other devices.

The operations modules including I/C, O/E and P/O will follow shortly after early in 2016 as part of a Product Update.

New PDX Treatment

We have a new Home Page (like the traditional desktop) and all redesigned screens.

Sage300Home

There are 12 KPIs that you can choose from to put on your home page. You run all the screens from the menu and can move between open screens using the little Windows widget on the right.

People familiar with Sage 300 ERP will pick up this new treatment very easily since it is still the same application, just re-imagined to fit into a modern Web environment.

cna2arinv

All the familiar elements are there including finders, import, export and printing via Crystal Reports. There are a lot of extra visual cues to help make the screens easier to learn and use. For instance you don’t have to know to hit tab when in a key field to get it processed, you can just hit the little go button next to it (but of course hitting tab still works as well). In the grid you don’t need to know to hit the Insert key to create a new line, there is an explicit “Add Line” button at the top of the grid.

The controls aren’t as packed together as they were in the classic VB UIs. This is to make it easier to use the screens on touch devices like tablets. Now it’s much easier to touch buttons and controls with your fingers when you don’t have the fine control of a mouse. Although these screens work quite well on tablets, it isn’t recommended you use them on a phone since this then involves a painful amount of scrolling and having a more responsive design for phones is still a version or two away.

Hybrid Offering

If you upgrade to this release, you don’t need to run the Web UIs. You can still run all the classic VB UIs. In fact you can have some users running the new Web UIs and some users running the classic VB UIs. Since everything goes through the same business logic (Views) you can mix and match as you like. Of course for modules like PJC where there aren’t any Web UIs yet, then you must run the VB UIs.

There isn’t a full Web based Financial Reporter yet, so Financial Reporting is still done though the usual built in F/R that is included with G/L or via Sage Intelligence. Both of these are based on the on-premise version of Excel.

Many customers rely on customized versions of the Sage 300 screens. There won’t be a customization toolkit with the first release, it will be provided later. Even if it was available with the initial release, not everyone would want to immediately port their customizations to the new technology. It takes time overcome the learning curve and become proficient in the new technologies. To help with this scenario we provide a tool that lets you hide any UIs in the Web home page, so that users won’t accidentally run the Web UI when you want them running the VB UI (probably due to customizations).

New Technology Stack

This new architecture is based on using standard ASP.Net MVC along with re-using the standard Sage 300 Business Logic. Below is an architectural block diagram of the new components that sit on top of our traditional business logic.

cna2arch

To speed bringing the product to market we were careful to stick to standard off the shelf technology components rather than creating out own. For instance the UI widgets like the editable grid, the graphical charts and the date picker are from the Kendo UI toolkit. We use knockout.js for databinding. We make extensive use of jQuery and leverage quite a number of other standard technologies that are all tried and true in many large scale web applications. All the server code is written in C# and all the Browser code is written in JavaScript.

You can see that there is a Wrapper layer than translates the world of Sage 300 Business Logic into the world of ASP.Net MVC. This way our application is a very standard implementation where the Wrapper layer exposes the standard Sage 300 business logic in more modern interfaces allowing the usage of newer languages and technologies like LINQ in the higher layers.

Lots of Questions

I imagine many people have lots of questions on everything that is going on. This is the first of many articles on the new Sage 300 Web UIs where I’ll be going into much more technical detail. But if you leave questions in the comments of this article, I will endeavor to either answer them or make them the topic of a future posting.

Summary

We are really excited to finally be launching our Web UIs for Sage 300 into the real world. This is a first step in fully modernizing the product and providing a strong foundation for future development.

Our R&D department has been operating using Agile development methodologies and a continuous delivery process. After the first release we are looking to release upates very frequently to add value very quickly to this rather large base release.

Written by smist08

July 29, 2015 at 12:40 am

Sage 300 ERP Optional Fields

with 4 comments

Introduction

Optional Fields were added to Sage 300 ERP as the major feature for version 5.3A. They are a great way to add custom data fields to many master and transaction screens in Sage 300. They also have the benefit that we can flow them with the transactions through the system, for instance from an O/E Order to the corresponding A/R Invoice to the G/L Batch. This opens up a lot of power for tracking extra data in the system and to do sophisticated reporting based on it.

In this article we will look at some of the programming consideration when dealing with Optional Fields at the API level. We’ll use the Sage 300 .Net API to explore how to deal with a few things in that come up with Optional Fields.

Many programmers feel they can ignore Optional Fields and their program works perfectly for sample data, but as soon as they install it at a customer site, it fails. A common cause of this is that the customer has required optional fields that cannot be ignored and the API program needs to be updated. (Another common failure at customer sites is caused by not dealing with locked fiscal periods).

optionalfields

Ordered Header/Detail

From my previous article on the View Protocols, any optional field view is an ordered detail where its header is whatever it is an optional field for. An ordered header/detail means that you set the value of the key. But you do have to be careful to setup your optional fields correctly in the application’s setup UI since there is validation on these.

In the sample program I’ll give a complete set of steps. In some situations some of the steps can be skipped, for instance you don’t really need to call RecordClear and RecordGenerate for the header optional fields, but I’ll leave them in since sometimes you do, and it’s easier to just use a formula that always works rather than re-thinking everything each time.

Revision Lists

Since Optional Fields are often sub-details of details that are stored in revision lists, you sometimes need to be careful that things exist before using them. Revision Lists are our mechanism to store things in memory before they are written to the database in a single database transaction. So until you save the header, nothing is in the database and everything is in memory. The Revision List store the list of details that have been manipulated so far. The Optional Fields are themselves stored in Revision Lists until the big database transaction happens.

In the sample program we insert the detail before adding this optional fields. Until the detail is inserted there is nothing to attach the optional fields to, so we need to do that first. The Insert operation only adds the detail record to a revision list in memory, but once that is done we can add sub-details (ie Optional Fields) to it (which are also stored in memory).

Sample Program

For a sample program, I modified the ASP.Net MVC sample ARInvEntry to save a couple of optional fields for the header and a couple of optional fields for the detail. Below is a subset of the code to insert the detail optional fields:

   // 9. Insert detail.
   arInvoiceDetail.Insert();   // Insert the detail line (only in memory at this point).

   // 10. Add a couple of detail optional fields.
   arInvoiceDetailOptFields.RecordClear();
   arInvoiceDetailOptFields.RecordGenerate(false);
   arInvoiceDetailOptFields.Fields.FieldByName("OPTFIELD").SetValue("EXTWARRANTY", false);
   arInvoiceDetailOptFields.Fields.FieldByName("VALIFBOOL").SetValue(true, false);
   arInvoiceDetailOptFields.Insert();
   arInvoiceDetailOptFields.RecordClear();
   arInvoiceDetailOptFields.RecordGenerate(false);
   arInvoiceDetailOptFields.Fields.FieldByName("OPTFIELD").SetValue("WARRANTYPRD", false);
   arInvoiceDetailOptFields.Fields.FieldByName("VALIFTEXT").SetValue("180 Days", false);
   arInvoiceDetailOptFields.Insert();

   // 10.5 Register the changes for the detail.
   arInvoiceDetail.Update();

   // 11. Insert header. (This will do a Post of the details.)             
   arInvoiceHeader.Insert();

Different Field Types

If you look in the database you will see that the Optional Field tables don’t hold that many fields, but any Optional Field View has quite a few fields, but many are calculated. Optional Fields can be all sorts of different types, but in the database all the values are stored in a single VALUE field regardless. So there has to be a conversion to/from this text field and the real type. This is the job of all the VALIFtype fields. Basically you use the VALIF field based on the type of the Optional Field and then the View will handle the conversion to and from the type as stored in the VALUE database field. That is why we used the fields VALIFTEXT and VALIFBOOL above.

Auto Insert

In the sample program I just inserted the optional fields myself. However there is another way. Views that have optional fields usually have a field called PROCESSCMD (or something similar) where you can set a value with a title like “Insert Optional Fields” which will insert the optional fields that are needed. You can set this field and call Process on the View and it will insert these optional fields for you. Then you can read the optional field, set its value and update it. Some people find this an easier way to do things and you get all the optional fields with their default values as a bonus. (Note that this only applies to Optional Fields that are set to auto insert in the applications Optional Fields setup screen).

Similarly for Views that will transfer in Optional Fields (like from an Order to a Shipment), you will see PROCESSCMD’s like “Default and Transfer Optional Fields”. If you are doing API programming and want to preserve the flow of Optional Fields, you will occasionally have to set one of these and call Process.

When dealing with Optional Fields, it’s worth checking out the PROCESSCMD functions of the main View to see if they will help you do your job and save you a fair bit of coding.

Lookup Values

Many optional fields have a list of valid values. If you don’t set it as one of these you will get an error message to this effect. When dealing with optional fields make sure you have an error handler to show the errors after an exception as explained here. If you want to get at these values they are in CSOPTFD CS0012 (a detail of CSOPTFH CS0011). You can read through these views like any other as explained here.

Summary

Optional Fields are a powerful feature in Sage 300 ERP. Many customers use these in a fundamental way to support their businesses. This means that developers working in the Sage 300 world have to be cognizant of these and make sure their programs are compatible with their use.

Written by smist08

January 4, 2014 at 3:54 am

Error Reporting in Sage 300 ERP

with 18 comments

Introduction

A very important aspect of all applications is how they handle errors and how they inform the end user about these errors. When everything is entered properly and people take what is called the “happy path” through the program then there is no issue. But end users will stray from the “happy path” and other circumstances can conspire to cause the need for informing the user of unusual circumstances.

In our previous blog postings on using the .Net API we have been deferring our discussion of error reporting, but now we are going to tackle it and add error reporting to the ASP.Net MVC sample program we started last week. In doing so we will introduce some new concepts including starting to use JQuery and Microsoft’s Unobtrusive Ajax.

Errors

Generally when we refer to errors in this articles we will also mean warnings and informational messages. Often you hit save on a form and if something is wrong then you get a message box telling you what was wrong (and maybe even what to do about it). However this is bit of an oversimplification. Sage 300 ERP is a three tier client server application. Many of the errors originate in the business logic or the database server. These may be running as Windows services and have no way of doing any user interaction. They can’t simply popup a message box. The error message must be transmitted to where ever the UI is running (say in a web browser somewhere on the Internet) and displayed there in a nice form that fits in with the general design of the form.

Further there may be more than one error. It is certainly annoying to have error messages popup one at a time to be answered and it is also very annoying to Save have one error message appear and correct the thing wrong, hit save again and then have a further thing wrong and so on.

To solve these problems we collect errors, warnings and messages up into a collection which is maintained during processing and forwarded up to the higher levels when processing is complete. We see this in the .Net API with the Errors collection on the Session object. As processing proceeds any business logic component can add messages to this collection. This collection can then be processed by the UI and cleared after it has reported the errors.

All the actual error messages referenced from the business logic are stored in Windows resource files and one of these is provided for each language. So the API used by the Business Logic will access the correct language resource file for the language of the Sage 300 user associated with the session object.

Inside the collection, each error has a priority such as Severe Error, Error, Security, Warning or Message. This way we can decide further how to display the error. Perhaps have the error dialog title bar red for errors, blue for warnings and green for messages.

Exceptions

Inside our .Net API we will return a return code for simple things that should be handled by the program. For instance if we go to read a record, we return false if it doesn’t exist, true if it does. But for more abnormal things we will throw an exception. You should always catch these exceptions, since even if you are debugging a program, these messages can be very helpful.

Just because an exception is thrown, doesn’t mean there is an error on the error stack. The exception might be because of some other exception, say from the .Net runtime. Because of this you will see in our exception handler, that if there is no error from Sage 300 then we display the error that is part of the exception (perhaps from .Net).

Sample Program

To add error reporting to our ASP.Net MVC sample programming we are going to start introducing how to program richer functionality in the browser. As we left off you just hit submit, the processing was done and the page was refreshed completely. This is rather old school web programming and a bit better user interaction is expected these days. Now I’ve updated the sample ARInvEntry sample (located here) to do an Ajax request rather than a page submit request. As a result the page isn’t completely redrawn and we can do a bit more processing. In this case we will use JQuery to put up a dialog box with any messages, warnings or errors that were encountered. Normally when you save an Invoice there is no success message displayed, but here we will put up a success message as well.

First off now that we are programming in JavaScript in the Browser, this is an interpreted environment which means there is no compiler to catch dumb programming mistakes and typos. One thing is to watch the syntax highlighting in the editor and use intelli-sense to avoid some typos. The other things is that we are passing data structures from C# on the server to JavaScript on the client. Which means there is no validation that C# and JavaScript have the same understanding of the object. Further JavaScript tends to silently ignore errors and keep on going, so you can be rather mystified when things don’t work. Some good tools to look for errors are Firebug and Fiddler. Trying different browsers can help also.

First I changed the page submit call to an Ajax call using Microsoft’s unobtrusive JavaScript library.

        @using (Ajax.BeginForm("CreateInvoice", "Home", null,
             new AjaxOptions { OnSuccess = "showResult" }))

Now the request will be made with Ajax and any returned data will be passed to the showResult JavaScript function. Pretty easy change, but there is one major missing piece. We must include the JavaScript library for this. The JavaScript file for any library needs to be added to the Scripts section of the project and then an entry needs to be added to BundleConfig.cs. Here we added jquery.unobtrsive-ajax.js to the jquery bundle.

     bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
         "~/Scripts/jquery-{version}.js",
         "~/Scripts/jquery-ui-{version}.js",
         "~/Scripts/jquery.unobtrusive-ajax.js"));

We also added the jquery-ui-{version}.js. The framework will figure out which version to fill in from the js files in the scripts folder. This way we can update these without having to change any C# code. If we didn’t add the unobtrusive library then nothing would happen and we wouldn’t get any errors because nothing would be hooked up to fail. When having trouble with JavaScript always check you have the correct libraries added. Similarly we have added the css and images for JQuery to the project. You could put these in script tags in your HTML, but the bundles help with eventual deployment as we’ll see in a later article. These bundles are referenced in the cshtml files to actually include them in the generated HTML and one gotcha is that sometimes the order of including them matters due to namespace conflicts, for instance the bootstrap bundle must be placed before the jQuery bundle or something in JQuery won’t work due to some naming conflicts.

Now we add the showResult function and what it calls.

function showResult(data)
{
    $.fn.showMessageDialog(data.Warnings, data.Errors, data.Messages);
}
(function ($) {
    $.fn.showMessageDialog = function (warnings, errors, messages) {
        $("#infoWarningsBlock").hide();
        $("#infoSuccess").hide();

        if ((warnings != null && warnings.length > 0) ||
            (errors != null && errors.length > 0) ||
            (messages != null && messages.length > 0)) {

            var text = "";
            var i;
            if (messages != null && messages.length > 0) {
                $("#infoSuccess").show();
                for (i = 0; i < messages.length; i++) {
                    text = text + "<li>" + messages[i] + "</li>";
                }
            }
            $("#infoSuccess").html(text);
            text = "";
            if (errors != null && errors.length > 0) {
                $("#infoWarningsBlock").show();
                for (i = 0; i < errors.length; i++) {
                    text = text + "<li>" + errors[i] + "</li>";
                }
            }
            if (warnings != null && warnings.length > 0) {
                $("#infoWarningsBlock").show();
                for (i = 0; i < warnings.length; i++) {
                    text = text + "<li>" + warnings[i] + "</li>";
                }
            }
            $("#infoWarnings").html(text);

            $("#informationDialog").dialog({
                title: "Information",
                modal: true,
                buttons: {
                    Ok: function () {
                        $(this).dialog("close");
                        $(this).dialog("destroy");
                    }
                }
            });
        }
    }

}(jQuery));

Besides setting up the HTML to display, this code mostly relies on the JQuery dialog function to display a nice message/error/warning dialog type box in the Browser using an iFrame.

This routine requires a bit of supporting HTML which we put in _Layout.cshtml file so it can be shared.

    <div id="informationDialog" style="display: none;">
        <ul id="infoSuccess" style="display: none;"></ul>
        <div id="infoWarningsBlock" style="display: none;">
            <div id="infoWarningsHeader">
                <h4>Warnings/Errors</h4>
            </div>
            <ul id="infoWarnings"></ul>
        </div>
    </div>

Notice it has display set to none so it is hidden on the main page.

From the C# code on the server here is the error handler:

        //
        // Simple generic error handler.
        //
        private void MyErrorHandler(Exception e)
        {
            if (session.Errors == null)
            {
                Errors.Add(e.Message);
                Console.WriteLine(e.Message);
            }
            else
            {
                if (session.Errors.Count == 0)
                {
                    Errors.Add(e.Message);
                    Console.WriteLine(e.Message);
                }
                else
                {
                    copyErrors();
                }
            }
        }

        private void copyErrors()
        {
            int iIndex;

            for (iIndex = 0; iIndex < session.Errors.Count; iIndex++)
            {
                switch (session.Errors[iIndex].Priority)
                {
                    case ErrorPriority.Error:
                    case ErrorPriority.SevereError:
                    case ErrorPriority.Security:
                    default:
                        Errors.Add(session.Errors[iIndex].Message);
                        break;
                    case ErrorPriority.Warning:
                        Warnings.Add(session.Errors[iIndex].Message);
                        break;
                    case ErrorPriority.Message:
                        Messages.Add(session.Errors[iIndex].Message);
                        break;

                }
                Console.WriteLine(session.Errors[iIndex].Message);
            }
            session.Errors.Clear();
        }

It separates the errors, warnings and messages. Also the copyErrors method was separated out so it can be called in the case of success, in case there are any warnings or other messages that should be communicated.

The controller now just passes the variables from the model back to the web browser.

        public JsonResult CreateInvoice(Models.CreateInvoice crInvObj)
        {
            ResultInfo results = new ResultInfo();

            results.Messages = crInvObj.Messages;
            results.Errors = crInvObj.Errors;
            results.Warnings = crInvObj.Warnings;

            crInvObj.DoCreateInvoice();

            return Json(results);      
        }

All the work here is done by the Json method which translates all the C# objects into Json objects. It even translates the C# list collections into JavaScript arrays of strings. So for the most part this handles the communication back to the Browser and the JavaScript world.

error2

Summary

We discussed the Sage 300 ERP error reporting architecture and saw how to integrate that into our ASP.Net MVC sample program. Since this was our first use of Ajax and JQuery, we had a bit of extra work to do to set all that up to operate. Still we didn’t have to write much JavaScript and the framework handled all the ugly details of Ajax communications and translating data between what the server understands and what the browser understands.

Update 2015/01/29: Sometimes View calls will return a non-zero return code for special informational purposes. These will cause the API to throw an exception. To handle these you need to catch the exception, check the View’s LastReturnCode property to see what it is and then continue processing. Perhaps a bit of a pain to do this, but it is the exception rather than the rule.