Open source address validation of Nordic addresses for Infor M3

As part of the open source address validation project for Infor M3, I just uploaded to the GitHub repository two sample scripts for Infor Smart Office to do address validation in Nordic countries: Sweden (, Denmark (, and Norway ( I provide the scripts as proof-of-concept for the interested reader to complete to suit their needs.

Eniro Geocode

The script TestEniroGeocode.js uses the Eniro geocode API. This API seems to be best for address validation, and you don’t need an account for it. But it seems to be deprecated, and I was only able to find an old copy of the documentation.

Eniro API

The script TestEniroAPI.js uses the Eniro API. This API seems to be for searching places only, like “restaurants in Stockholm”, and doesn’t seem usable for address validation for M3. Also, you will need an account with Eniro, and you will need to sign in to to see your account profile, key, and documentation.


Those were two quick proof-of-concepts scripts for Infor Smart Office to illustrate how to use Eniro to do address validation for Infor M3.

That’s it! Please comment, like, share, follow, author, contribute to the project, donate your source code. Thank you.

Hello Google Glass from Infor Process Automation

As a continuation of my Google Glass project, my next step is to write Hello World on Google Glass using Infor Process Automation (IPA).

As a reminder of my project, I’m writing an application to show M3 picking lists on Google Glass using Event Analytics and Infor Process Automation to have rich interactive picking lists in Glass with a list of items to pick, quantities, stock locations, item image from Infor Document Archive, and walking directions on a warehouse plan.


From a software architecture point of view, we have the following tiers:

  • I’m wearing Glassware and they are connected to the Internet
  • Google’s servers periodically communicate with my Glass
  • My IPA server is located in a Local Area Network (LAN) at the office behind a firewall.

Timeline card

To keep the proof-of-concept simple, I use the Google Mirror API; that’s simpler than using Glass Development Kit (GDK). A sample HTTP Request to insert Hello World in a static card in my Glass timeline looks like this with the proper OAuth 2.0 token:

Authorization: Bearer ya29.HQABS3kFLP2b-BsOWMFyGUpUv4JPAhKeEnDbLcjDUAHREBK6mYYVAadIa68S6A
Content-Type: application/json
Content-Length: 29

{ "text": "Hello from IPA" }

Note: For the purposes of this proof-of-concept I bootstrapped the OAuth 2.0 token manually by copy/pasting it from another computer where I already had the Android Development Tools (ADT) and the Glass Java Quick Start Project.

The resulting timeline card looks like this where the black pixels are see-through pixels in Glass:

Process Automation

IPA can make HTTP Requests using the Web Run activity node. With WebRun, we can specify the following parts of an HTTP Request: scheme (HTTP/HTTPS), host, port number, method (GET/POST), path, query, Basic Authentication, HTTP Request Body, Content-Type, and HTTP Request Headers.

Here is my sample WebRun for Glass:

Problems and rescue

At first, I ran into the following problems:

  1. I didn’t know where to set the scheme (HTTP/HTTPS); the Web Root seemed to me like it was only used to set the host and port number.
  2. The User id and Password fields seem to support Basic Authentication only, not OAuth 2.0.
  3. I need to set the OAuth 2.0 token as a variable because it expires and must be renewed every hour, but the WebRun activity node doesn’t support variable substitution in the Authorization header (CTRL+SPACE and <!var1> are not supported).
  4. I didn’t see the Header string field at first because when the input field is empty it has no border which makes it hard to spot.
  5. The Header string occupies only one line of height so it seems to indicate that it only accepts one HTTP Request Header.
  6. The Content-Type field didn’t propose application/json in the drop down list, so I had to hack into the LPD file with Notepad and write it and XML-encode it manually.
  7. The WebRun failed to execute and returned an HTTP 400 error without additional information.
  8. We cannot set a proxy like Fiddler in the WebRun configuration to intercept the HTTP Request (header and body) and HTTP Response (header and body) which makes it hard to troubleshoot.
  9. The WorkUnit log only shows the HTTP Response body which is only a quarter useful.

I sent an email with the problems to James Jeyachandran (j…, the Product Manager for IPA at Infor Product Development whom I know from my previous work on Lawson ProcessFlow Integrator (PFI) and IPA. It has always been a pleasure to work with James for his availability and responsiveness. Once more he impressed me as he called me back within four minutes. He was interested in my Glass project, he addressed my questions, and to assist me he graciously made available Samar Upadhyay s…, one of the software engineers of IPA. After troubleshooting together, here are the respective answers to my problems:

  1. The scheme can be set in the Web Root (as shown in the screenshot above).
  2. The OAuth 2.0 token can be set in an Authorization header in the Header string (as shown in the screenshot above).
  3. Samar said he will add variable substitution to the Header string.
  4. Samar said he will make the Header string field wider.
  5. Samar said he will make the Header string field multi-line.
  6. Samar said there is a new version of IPA that adds application/json as a possible Content-type.
  7. Samar said that’s a known problem with Content-type application/json and there is a bug fix available to correct it. Meanwhile, James said I can add it as an additional Header string; for that I had to use Notepad again to add the two Header strings on two lines. Also, Samar said he will attempt to upgrade my server with the new version of IPA.
  8. Samar said he will look into adding an optional proxy configuration for host and port number like we can do in web browsers.
  9. Samar said he will look into adding an option to log the full HTTP Request and Response.

After that collaboration we had a working proof-of-concept within 20mn.


Here is the resulting WorkUnit log:

And here is the resulting vignette in my Glass:

Future work

The next step will be to get the OAuth 2.0 token automatically, and to display the M3 picking list onto Glass.


That was a proof-of-concept to write Hello World from Infor Process Automation onto Google Glass using the WebRun activity node to make an HTTP Request to the Mirror API.

That’s it!

I want to specially thank James Jeyachandran and Samar Upadhyay for their support and responsiveness, and for their enthusiasm with this project. They will be at Inforum 2014.

Like. Comment. Share. Subscribe. Enjoy.

Open source release of Address Validation for M3

I’m please to announce I finally released the first free and open source version of the Address Validation for M3, and I published the code on GitHub at . It’s a combination of a Mashup and a Script for Infor Smart Office, so it’s easy for you to modify, deploy, and run. Currently, it only supports the Google Geocoding API with more address providers to be added later.

I had implemented the first proprietary version of the script for Lawson Software in 2009. Then I had implemented several variants for various customers while working at Lawson Software and Infor. I’ve always wanted it to become available to more customers, so I decided to make it free and open source. But in order to not infringe any intellectual property and copyrights over the previous source code, I had to re-write everything from scratch. The opportunity came up when I quit Infor, and before I joined Ciber, in between the two, so there was no question on the ownership of the source code, and I had made an announcement. It took me a while but here it is. And I made some improvements over to the previous proprietary code.


The script is self-configurable for the following M3 Panels, i.e. just add the script to one of the supported M3 Panels and execute, without any modifications nor arguments:

  • Customer. Open – CRS610/E
  • Customer. Connect Addresses – OIS002/E
  • Supplier. Connect Address – CRS622/E
  • Customer Order. Connect Address – OIS102/E
  • Internal Address. Open – CRS235/E1
  • Company. Connect Division – MNS100/E
  • Ship-Via Address. Open – CRS300/E
  • Service Order. Connect Delivery Address – SOS005/E
  • Shop. Open – OPS500/I
  • Bank. Open – CRS690/E
  • Bank. Connect Bank Branch Office – CRS691/E
  • Equipment Address. Open – MOS272/E

Deploy locally and test

To start using the script, download the Mashup’s Manifest and XAML and the Script from the GitHub repository. Save the three files somewhere temporary on your computer. Then, install the Mashup locally using the Mashup Designer at designer://mashup (watch the video below), and run the Script with the Script Tool at mforms://jscript (watch the video below). Then, enter an address in M3, click the validate button (the little globe icon), you can also use the TAB key from Address line 1 to focus the button and press SPACE to click it, then the Mashup will pop-up with a list of possible matches, and select an address by pressing ENTER or double-clicking the address.


Here are some screenshots:

1 2 3 4 5


Here are some videos (watch in full-screen and high-definition for better view):

  • How to deploy the Mashup locally
  • How to test the Script
  • Sample address searches:

Then, you can deploy the Mashup globally with LifeCycle Manager, and set the script to everybody with the Smart Office Personalization Manager in the Navigator widget > Administration tools.

Future work

There’s still more work to do. For instance, it appears the Google Geocoding API doesn’t follow the same format for all addresses, they’re local to each country, so right now we have to manually change the address layout based on the country, and I would like to improve that.

Also, I want to add a WebBrowser control to show the addresses in Google Maps.

Also, this first release only supports the Google Geocoding API. I want to add support for other address providers, like Experian QAS, FedEx, Microsoft Bing Maps, UPS, United States Postal Service, and local address providers like Pages Jaunes in France, and Eniro in Sweden.

If you like it, join the project and become a contributor!

Thibaud Lopez Schneider

Open source project: address validation for M3

I’m announcing the start of an open source project: address validation for M3.

Address validation is the ability for the user to enter a partial or incorrect address, get a list of possible matches, chose the valid address, and save it in M3. The goals are: reduce data entry time, ensure goods will reach their destination, minimize shipment returns, accurately calculate taxes, etc.

I implemented address validation for several customers in the past years while at a previous job, and I proposed that the source code becomes a product available to every customer. I believe address validation should come standard with M3 as it is of great service. The project was ready for distribution in 2009 but it got stuck in the legal department because of a conflict with the licenses of the respective address providers. For example, it seems the company couldn’t sell software that uses the free Google Maps API. So now that I quit my last job and haven’t started a new one, I decided to re-ignite the idea as free and open source software. In that way there are no legal conflicts.

Also, I cannot re-use any of the source code nor material I wrote while at any previous job since all the data is intellectual property of that company, so I will have to re-write everything from scratch. And I need your help.

The goals are to provide address validation for M3 with choice of the following address providers:

  • Bing Maps
  • Eniro
  • Experian QAS
  • FedEx
  • Google Maps
  • Google Maps Premier
  • UPS
  • USPS

I’m looking to include more local address providers in: Belgium, Denmark, France, Germany, Norway, The Netherlands, etc. If you know of any, let me know.

The product will be self-configurable, starting with the following M3 programs :

  • Customer. Open – CRS610/E
  • Customer. Connect Addresses – OIS002/E
  • Supplier. Connect Address – CRS622/E
  • Customer Order. Connect Address – OIS102/E
  • Internal Address. Open – CRS235/E1
  • Company. Connect Division – MNS100/E
  • Ship-Via Address. Open – CRS300/E
  • Service Order. Connect Delivery Address – SOS005/E
  • Shop. Open – OPS500/I
  • Bank. Open – CRS690/E
  • Bank. Connect Bank Branch Office – CRS691/E
  • Equipment Address. Open – MOS272/E

It will be a client-side implementation for Smart Office using:

  • Script assemblies for Smart Office (C#)
  • Mashups (XAML)

With plans to support H5 Enterprise (HTML5/JavaScript) in the future.

It will be made available as free software under the GNU General Public License V3.0 license. It permits commercial use, distribution, and modification. And it requires source be made available, license and copyright notice be included, and changes be indicated. It’s copyleft instead of copyright.

Also, the resulting code will be subject to the licenses of the respective address providers: Google Maps, etc.

Also, this project is a good opportunity for me to contribute to the community, and to learn Git revision control.

I started a repository on GitHub here:

Send me feedback. Let me know what you think. Tell your colleagues. And if you want to be a contributor, come help us.


Route optimization for MWS410 with OptiMap

Here is a script for Lawson Smart Office that integrates the Delivery Toolbox – MWS410/B with OptiMap – Fastest Roundtrip Solver to calculate and show on Google Maps the fastest roundtrip for the selected Routes. It’s a solution to the Travelling salesman problem (TSP) for M3 Routes.

This is interesting for a company to reduce overall driving time and cost, and it’s interesting for a driver to optimize its truck load according to the order of delivery.

To solve the TSP on Google Maps, the authors of OptiMap implemented several algorithms, including brute force, nearest-neighbor, and the Ant Colony Optimization, and released the code for the TSP Solver for Google Maps API as open source with an MIT License. Because the TSP is an NP-complete problem the solution only works well for up to 10 cities on current desktop computers. But OptiMap can apparently solve more than 15 cities. You can read more about OptiMap at Behind the Scenes of OptiMap and OptiMap version 4 is here.

To integrate OptiMap with Smart Office I wrote a simple Personalized Script that gets the addresses of the selected Routes in MWS410/B, and that opens OptiMap in a web browser with the delivery addresses in the URL. The GET parameters are explained in OptiMap’s Optimize Your Trips.


To install and use the script:

  1. Deploy this script in the mne\jscript\ folder in your Smart Office server:
  2. Create a Shortcut in MWS410/B to run this script; for that go to MWS410/B > Tools > Personalize > Shortcuts > Advanced, expand Script Shortcut, set the Name to OptiMap, set the Script name to OptiMap, click Add, and click Save:
  3. Create a View (PAVR) in MWS410/B that shows the address columns ADR1, ADR2, ADR3:
  4. Select multiple Routes in the list (press CTRL to select multiple rows), and click the OptiMap Shortcut to run the script:


The script will launch OptiMap for the selected Routes, and OptiMap will optimize the order of delivery:

Source code
Here is the complete source code for the script:

import System;
import System.Web;
import System.Windows;
import Mango.UI.Services.Lists;
import MForms;

Integrates the Smart Office Delivery Toolbox - MWS410/B with OptiMap - Fastest Roundtrip Solver,
to calculate and show on Google Maps the fastest roundtrip for the selected Routes.
This is interesting to reduce driving time and cost, and for a driver to optimize its truck load according to the order of delivery.
1) Deploy this script in the mne\jscript\ folder in your Smart Office server
2) Create a Shortcut in MWS410/B to run this script; for that go to MWS410/B > Tools > Personalize > Shortcuts > Advanced > Script Shortcut, set the Name to OptiMap, and set the Script name to OptiMap
3) Create a View (PAVR) in MWS410/B that shows the address columns ADR1, ADR2, ADR3
4) Select multiple Routes in the list (press CTRL to select multiple rows)
5) Click the OptiMap Shortcut to run this script and launch OptiMap for the selected Routes
For more information and screenshots refer to
Thibaud Lopez Schneider, Infor, October 4, 2012 (rev.2)
package MForms.JScript {
    class OptiMap {
        public function Init(element: Object, args: Object, controller : Object, debug : Object) {
            try {
                // get the list
                var listControl: MForms.ListControl = controller.RenderEngine.ListControl;
                var listView: System.Windows.Controls.ListView = controller.RenderEngine.ListViewControl;
                if (listControl == null || listView == null) { MessageBox.Show('Error: Couldn\'t find the list.'); return; }
                // get the selected rows
                var rows = listView.SelectedItems; // System.Windows.Controls.SelectedItemCollection
                if (rows == null || rows.Count == 0) { MessageBox.Show('Error: No rows selected.'); return; }
                // get the address columns ADR1, ADR2, ADR3
                var column1: int = listControl.GetColumnIndexByName('ADR1');
                var column2: int = listControl.GetColumnIndexByName('ADR2');
                var column3: int = listControl.GetColumnIndexByName('ADR3');
                if (column1 == -1 || column2 == -1 || column3 == -1) { MessageBox.Show('Error: Couldn\'t find the address columns ADR1, ADR2, ADR3.'); return; }
                // construct the URL
                var query: String = '';
                for (var i: int = 0; i < rows.Count; i++) {
                    var row: ListRow = rows[i];
                    var ADR1: String = row[column1];
                    var ADR2: String = row[column2];
                    var ADR3: String = row[column3];
                    var address: String = ADR1 + ',' + ADR2 + ',' + ADR3;
                    query += 'loc' + i + '=' + HttpUtility.UrlEncode(address) + '&';
                var uri: Uri = new Uri('' + query);
                // launch OptiMap
            } catch (ex: Exception) {

That’s it!

Related posts

See also version OptiMap_V2.

Translate M3 with Google Translate API

Here is a solution to automatically translate M3 and user-generated content in 52 languages.

For that, I will use the Google Translate API and a Personalized Script for Lawson Smart Office.

Business advantage

This solution is interesting to translate content that is generated by users, such as:

  • Bill of Materials
  • Work Orders
  • Service Orders
  • Customer Order Notes
  • etc.

Such content is entered in the user’s language and by design is not translated by Lawson Smart Office.

Also, this solution is interesting to translate M3 itself beyond the number of languages that Lawson makes available.

Lawson Smart Office

Lawson Smart Office supports 18 languages: Czech, Danish, German, Greek, English, Spanish, Finnish, French, Hungarian, Italian, Japanese, Dutch, Norwegian, Polish, Portuguese, Russian, Swedish, and Chinese:

It’s a high number of languages given that text is manually translated by professional translators which are probably paid by the word.

The quality is near perfect.

But by design, the user-generated content is not translated.

Google Translate

Google Translate supports 52 languages: Afrikaans, Albanian, Arabic, Belarusian, Bulgarian, Catalan, Chinese Simplified, Chinese Traditional, Croatian, Czech, Danish, Dutch, English, Estonian, Filipino, Finnish, French, Galician, German, Greek, Hebrew, Hindi, Hungarian, Icelandic, Indonesian, Irish, Italian, Japanese, Korean, Latvian, Lithuanian, Macedonian, Malay, Maltese, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swahili, Swedish, Thai, Turkish, Ukrainian, Vietnamese, Welsh, and Yiddish.

It’s a very high number of languages because it uses machine learning and statistical analysis for automatic machine translation of millions of web pages and of official translations done by governments and by international organizations.

It is one of the best machine translations available, considered state of the art, and the quality is improving constantly. [1] [2] [3].

Google is even working on recognizing handwritten text, and text in images.

But even though the quality is good it’s not yet accurate.

It may not be accurate enough in a professional context to translate user-generated content in M3 with the Google Translate API.

But it still gives the user a general idea of the meaning of the text.

And as a pedagogical tool, it serves the purpose of illustrating how to write scripts for Smart Office, and how to integrate M3 to external systems.

Hello World!

To use the Google Translate API you need to register and obtain a key. It is a paid service that will translate one million characters of text for $20.

Once you obtain your key, you need to construct a URL with your API key, the text to translate, and the source and target languages.

Here is a sample URL that translates the text Hello World! from English (en) to French (fr):!&source=en&target=fr

The result is a JSON object like this:

 "data": {
  "translations": [
    "translatedText": "Bonjour tout le monde!"

First script

Then write a Personalized Script for Lawson Smart Office using the Script Tool.

The script will submit the HTTP GET Request to the Google Translate API over HTTPS and will parse the JSON response.

function translate(text: String, source, target) {
     var url = '' + source + '&target=' + target + '&q=' + HttpUtility.UrlEncode(text);
     var request = HttpWebRequest(WebRequest.Create(url));
     var response = HttpWebResponse(request.GetResponse());
     var jsonText = (new StreamReader(response.GetResponseStream())).ReadToEnd();
     var o = eval('(' + jsonText + ')''unsafe');

We can now use this function to translate any piece of user-generated content, for example the Customer Name in CRS610/E (WRCUNM):

var WRCUNM = ScriptUtil.FindChild(controller.RenderEngine.Content, 'WRCUNM');
WRCUNM.Text = translate(WRCUNM.Text, 'en''fr');

Also, we can translate several pieces of text at once by appending as many q parameters to the URL as pieces of text.


With this technique, we can translate all the Controls of our Panel, including the user-generated content: Label, TextBox, Button, ListView, GridViewColumnHeader, ListRow, etc. That will cover Panels A, B, E, F, etc.

Also, we will need to submit the HTTP Request in a background thread to avoid blocking the user interface.

Complete Script

Here is the complete source code of my script that translates all the content of any M3 program, any panel.


Replace the constant YOUR_API_KEY of the source code with your own Google Translate API key.

The script has a limit GOOGLE_MAX_TEXT_SEGMENTS which was applicable when I wrote the script back in March 2010, but Google has since removed the limit so you can remove it from the script as well.

Then deploy the script on each program and each panel that you’d like to translate. The deployment can probably be automated with some custom XML and XSLT.


Here is an animation of the M3 program Work Order – MOS100/B1 with buttons for seven languages. Click on the image to see the animation. Note how the user-generated content in the rightmost column of the list is also being translated.

Future Work

A future implementation should also translate menus, drop down lists, and text panels (T). I still haven’t been able to execute scripts in a T panel.

That’s it!



UPDATE 2012-08-02: Just fixed the line breaks at line 280 which the copy/paste had corrupted + fixed GetType().ToString() + fixed Exception handling in BackgroundWorker.

UPDATE 2012-08-03, Martin Trydal Torp & Thibaud: Adapted listView for newer LSO (new: listView.ItemsSource; old: listView.Items) + change sourceLanguage dynamically

Cash drawer integration with Smart Office Script

Here is a demo video of the integration I implemented for one of our customers between a cash drawer and Lawson Smart Office using a Script. (If you want to skip the talk, jump to time stamp 3:31 in the video to see the cash drawer open.)

The result is a single cash drawer controlled by multiple Points of Sales throughout the showroom. Each Point of Sales runs Smart Office. The solution requires authorization (who can access the cash drawer) and auditing (who opened the cash drawer when) for security purposes. For the user its just a click on a new button Open Register.

I implemented the solution as client/server: the clients are Personalized Scripts in JScript.NET for Smart Office, the server is a custom Java program connected to the cash drawer with JavaPOS and a Serial cable, a custom bridge between .NET and Java made with a tiny HTTP server, and Properties file.

Send SMS from Smart Office with Skype

To send an SMS text message to a mobile phone from a Personalized Script in Lawson Smart Office using Skype do:

skype.SendSms("+18472874945", "Hello World", null)


A simple script that sends an SMS would be:

import System;
import System.Reflection;

package MForms.JScript {
    class SendSms {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             try {
                 var assembly: Object = Assembly.LoadFrom('C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll');
                 var skype = assembly.CreateInstance('SKYPE4COMLib.SkypeClass');
                 skype.Attach(8, false);
                 skype.SendSms('+18472874945', 'Hello World!', null);
             } catch (ex: Exception) {

Note: The programming language for scripts in Smart Office is JScript.NET.


Follow these steps to run the script above:

  1. Download and install Skype on the computer that is running Smart Office (the script must communicate with Skype locally). Then sign in to Skype (the script will not work if you are not signed in). Also, your Skype account must have credit (USD, EUR, etc.) to be able to send SMS.
  2. Download and unzip Skype4COMsomewhere in your computer, for example C:\Program Files\Skype\skype4com-1.0.36\ . Skype4COM is the API used to send/receive Skype commands. Then register the DLL Skype4COM.dll with the following command:
    regsvr32 Skype4COM.dll

  3. Download and install SEHE and place the file Interop.SKYPE4COMLib.dll somewhere in your computer or somewhere on the network so that it is accessible by the Smart Office computer, for example C:\Program Files\Skype\SEHE\Interop.SKYPE4COMLib.dll or http://host/path/Interop.SKYPE4COMLib.dll . That DLL contains the Interop code to be able to call Skype4COM from the .NET framework.
  4. Launch Smart Office, and log in.
  5. For the Script Tool it is necessary to have an M3 program open, so open for example Customer. Open – CRS610.
  6. Open the Script Tool with the following command:
  7. Copy/paste the sample script above into the Script Tool
  8. Change the path to the DLL. In my example I used C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll . Make sure to escape the backslashes in the String with double backslashes.
  9. Change the phone number. It must be in international notation. In my example I used +18472874945.
  10. Click Compile
  11. Click Run
  12. Skype will show the message “LawsonClient.exe wants to use Skype”. Click Allow access; Skype will only ask once.
  13. Skype will now send the SMS. Check in your mobile phone that you received it. That’s it!

Note: I tested this on Windows XP and on Windows 7 32-bit with success. It doesn’t seem to work on Windows 64-bit. Also, I tested this in Smart Office 9.x.

More advanced script

A more elaborated script with an editable SMS text message in a pop-up looks like: