Importing BUS100 Budgets via the APIs

After spending years looking at the M3 APIs in disdain and grudgingly using them, it looks like I’ve finally been converted.

Over the past 12 months due to a variety of projects for different people I’ve relied upon them more and more and found them to be a lot better than my early encounters. And being able to call them directly within Smart Office 10.x without needing to authenticate has been a real boon.

Now those of you that have long memories will recall that I created a script which added a button to BUS100 which would read a spreadsheet and import the data in to BUS100 budgets, we do this as the manual entry is horrible, especially if you have different monthly amounts during the year. Due to the way the BUS100 worked I couldn’t use the same method I used for the GLS100 imports so I resorted to use the APIs wrapped in a webservice. It worked well for IFL…

Roll on 2014 and Upgrade-X (more of this in another post), and I needed to uplift the webservices. The webservices I originally created were pre-grid and were going to need some tweaking along with some other non-technical challenges I figured I’d spend the time converting the code to use the APIs instead. The structure of the script made it fairly trivial and now means that it’s easier for others to use and when further upgrades are done I don’t need to worry about messing around with webservices.

First off, here is the spreadsheet template, it should be fairly self-evident how to use it…

https://drive.google.com/file/d/0Bxi1gJ1tQyDRdDN4SE8zcExZOTQ/view?usp=sharing

And now the updated script…(I was facing time pressure so it’s not terribly pretty but it is functional)

//
// 20121214 v005    - changed the division so it is no longer statically set
// 20121214 v007    - the name and description of the budget were being ignored and set as TEST
// 20141122 v008	- convert to UpgradeX
//
import System;
import System.IO;
import System.Net;
import System.Windows;
import System.Windows.Controls;
import MForms;

import System.Web.Services.Protocols;
import System.Xml;

import Lawson.M3.MI;
import Mango.UI;

import System.Text;
import Excel;
import System.Reflection;

package MForms.JScript
{
	class BUS100_BudgetImport_008
	{
		var giicInstanceController : IInstanceController = null;    // this is where we will store the IInstanceController to make it available to the rest of the class
		var ggrdContentGrid : Grid = null;          // this is the Grid that we get passed by the Init()
		var gexaApplication = null;                 // Excel.Application object        

		var gwbWorkbook = null;                     // here we will store the Workbook object
		
		var giStartRow : int = 7;                   // the starting row in the Spreadsheet
		var giMaxRow : int = 10000;                 // the end row in the Spreadsheet
		var giCurrentRowBUS101_B1 : int = 7;        // the row we will start from

		var gstrCompany : String = null;
		var gstrDivision : String = null;
		var gstrBudgetNumber : String = null;
		var gstrBudgetVersion : String = null;

		//var gstrUsername : String = null;           // the username that we will use to log in to the WebServices
		//var gstrPassword : String = null;           // the password that we will use to log in to the WebServices
		
		//var gstrBaseURI : String = null;			// this is the URI to the webservice

		var btnImportBudget : Button = null;

		var wndPasswordPrompt : Window = null;      // this is the window we will display requesting the username and password

		var gdebug;

		public function Init(element: Object, args: Object, controller : Object, debug : Object)
		{
			gdebug = debug;
			var content : Object = controller.RenderEngine.Content;

			// add a button to retrieve the free caps of an item
			btnImportBudget = new Button();
			// the button will display "FreeCap"
			btnImportBudget.Content = "Import";

			// set the position of the button
			Grid.SetColumnSpan(btnImportBudget, 10);
			Grid.SetColumn(btnImportBudget, 11);
			Grid.SetRow(btnImportBudget, 0);

			// actually add the button to the panel
			content.Children.Add(btnImportBudget);

			// we want to know about the click and unloaded events, so we register our interest here
			btnImportBudget.add_Click(OnbtnImportBudgetClick);
			btnImportBudget.add_Unloaded(OnbtnImportBudgetClickUnloaded);
		}


		public function OnbtnImportBudgetClickUnloaded(sender: Object, e: RoutedEventArgs)
		{
			// remove the events that we are subscribed to
			btnImportBudget.remove_Click(OnbtnImportBudgetClick);
			btnImportBudget.remove_Unloaded(OnbtnImportBudgetClickUnloaded);
		}

		// this will submit the header to the webservice
		// we statically set the Company & Division in our case
		private function handleHeader()
		{
			// "WETX40" - Description
			// "WETX15" - Name
			// "WEBSPR" - Start Period
			// "WECRTP" - Exchange Rate
			// "WENPAM" - Number of Periods
			// "WEUPDB" - Update Balance File (Checkbox)
			//gstrUsername = retrieveFromActiveSheet("B1");           // retrieve the username from the spreadsheet
			//gstrPassword = retrieveFromActiveSheet("B2");           // retrieve the password from the spreadsheet

			var w1buno : String = retrieveFromActiveSheet(gwbWorkbook, "B3");    // budget number
			var w1bver : String = retrieveFromActiveSheet(gwbWorkbook, "D3");    // version number

			gstrCompany = "100";                                    // statically set our company for us
			// gstrDivision = "IFL";                                   // statically set out division
			gstrDivision = retrieveFromActiveSheet(gwbWorkbook, "F3");    // Division
			gstrBudgetNumber = w1buno;
			gstrBudgetVersion = w1bver;


			var wetx40 : String = retrieveFromActiveSheet(gwbWorkbook, "B4");    // description
			var wetx15 : String = retrieveFromActiveSheet(gwbWorkbook, "B4");    // name
			var webspr : String = retrieveFromActiveSheet(gwbWorkbook, "D4");    // start period
			// var wecrtp : String = "1"; //retrieveFromActiveSheet("B3");
			var wecrtp : String = retrieveFromActiveSheet(gwbWorkbook, "K4");    // exchange rate tp
			var wenpam : String = retrieveFromActiveSheet(gwbWorkbook, "G4");    // number of periods
			// var weupdb : String = retrieveFromActiveSheet("B3");
			var weupdb : String = retrieveFromActiveSheet(gwbWorkbook, "I4");    // should we update the budget
			var strUpdate : String = "1";

			// check to ensure a value for the update budget
			if(true == doWeHaveAValueFromSpreadsheet(weupdb))
			{
				if(0 == String.Compare(weupdb, "true", true))
				{
					strUpdate = "1";
				}
				else if(0 == String.Compare(weupdb, "yes", true))
				{
					strUpdate = "0";
				}
			}
			
			if(wetx15.length > 15)
			{
				ConfirmDialog.ShowWarningDialog("Warning", "Budget name is > 15 characters - it will be truncated");
				wetx15 = wetx15.substring(0,15);
			}
			
			
			var mirRequest = new MIRequest();
			// we only want to filter on the company
			var mirInRecord = new MIRecord();
			mirInRecord["CONO"] = gstrCompany;
			mirInRecord["DIVI"] = gstrDivision;
			mirInRecord["BUNO"] = gstrBudgetNumber;	// budget number
			mirInRecord["BVER"] = gstrBudgetVersion;	// budget version
			mirInRecord["TX40"] = wetx40;	// Description
			mirInRecord["TX15"] = wetx15;	// Name
			mirInRecord["BSPR"] = webspr;	// start period budget
			mirInRecord["CRTP"] = wecrtp;	// exchange rate type
			mirInRecord["NPAM"] = wenpam;	// number periods
			//mirInRecord["ROPP"] = UserContext.CurrentCompany;	// rounding off category
			mirInRecord["UPDB"] = strUpdate;	// Update Balance File
			//mirInRecord["DTMP"] = UserContext.CurrentCompany;	// Allocaton Template
			//mirInRecord["ACGR"] = UserContext.CurrentCompany;	// Object Access Group

			mirRequest.Program = "BUS100MI";
			mirRequest.Record = mirInRecord;
			mirRequest.Transaction = "AddBudgetHeader";
			mirRequest.Tag = gwbWorkbook;
			
			MIWorker.Run(mirRequest, addBudgetHeader_OnComplete);
			
			// handleLines();
		}

		
		private function addBudgetHeader_OnComplete(amirResponse : MIResponse)
		{
			gdebug.Debug("addBudgetHeader_OnComplete start");
			if((null != amirResponse) && (false == amirResponse.HasError))
			{
				handleLines(amirResponse.Tag);
			}
			else
			{
				if(null == amirResponse)
				{	
					ConfirmDialog.ShowErrorDialog("Error", "No response from the APIs");
				}
				else
				{
					ConfirmDialog.ShowErrorDialog("Error", "Error returned from the APIs: " + amirResponse.ErrorCode + " " + amirResponse.ErrorMessage);
				}
				CleanUp();
			}
			gdebug.Debug("addBudgetHeader_OnComplete end");
		}
		
		// this function will go through the spreadsheet looking for values to submit
		// to the SOAP interface
		private function handleLines(workbook)
		{
			gdebug.Debug("handleLines start");
			
			var argumentArray = new Array();
			var iArrayPosition = 0;
			gdebug.Debug("handleLines MIMultWorker created");
			
			while(giCurrentRowBUS101_B1 <= giMaxRow)    // we will loop through until we hit MaxRows
			{
				gdebug.Debug("Dimension 1, " + "A" + giCurrentRowBUS101_B1);
				var w1ait1 : String = retrieveFromActiveSheet(workbook, "A" + giCurrentRowBUS101_B1);     // retrieve the first dimension from the spreadsheet
				gdebug.Debug("Dimension 1, " + "A" + giCurrentRowBUS101_B1 + " = '" + w1ait1 + "'");

				if(true == doWeHaveAValueFromSpreadsheet(w1ait1))   // if the dimension doesn't exist then there isn't any point retrieving any other dimensions
				{
					if(0 == String.Compare("End", w1ait1, true))    // if we come across End we should break from our line loop
					{
						break;
					}

					var w1ait2 : String = retrieveFromActiveSheet(workbook, "B" + giCurrentRowBUS101_B1);     // dimension 1
					var w1ait3 : String = retrieveFromActiveSheet(workbook, "C" + giCurrentRowBUS101_B1);     // dimension 2
					var w1ait4 : String = retrieveFromActiveSheet(workbook, "D" + giCurrentRowBUS101_B1);     // dimension 4
					var w1ait5 : String = retrieveFromActiveSheet(workbook, "E" + giCurrentRowBUS101_B1);     // dimension 5
					var w1ait6 : String = retrieveFromActiveSheet(workbook, "F" + giCurrentRowBUS101_B1);     // dimension 6
					var w1ait7 : String = retrieveFromActiveSheet(workbook, "G" + giCurrentRowBUS101_B1);     // dimension 7
					var w1cucd : String = retrieveFromActiveSheet(workbook, "H" + giCurrentRowBUS101_B1);     // 
					var w1amtn : String = retrieveFromActiveSheet(workbook, "I" + giCurrentRowBUS101_B1);     // 

					var strPeriod1 : String = retrieveFromActiveSheet(workbook, "J" + giCurrentRowBUS101_B1);     // value for period 1
					var strPeriod2 : String = retrieveFromActiveSheet(workbook, "K" + giCurrentRowBUS101_B1);     // value for period 2
					var strPeriod3 : String = retrieveFromActiveSheet(workbook, "L" + giCurrentRowBUS101_B1);     // value for period 3
					var strPeriod4 : String = retrieveFromActiveSheet(workbook, "M" + giCurrentRowBUS101_B1);     // value for period 4
					var strPeriod5 : String = retrieveFromActiveSheet(workbook, "N" + giCurrentRowBUS101_B1);     // value for period 5
					var strPeriod6 : String = retrieveFromActiveSheet(workbook, "O" + giCurrentRowBUS101_B1);     // value for period 6
					var strPeriod7 : String = retrieveFromActiveSheet(workbook, "P" + giCurrentRowBUS101_B1);     // value for period 7
					var strPeriod8 : String = retrieveFromActiveSheet(workbook, "Q" + giCurrentRowBUS101_B1);     // value for period 8
					var strPeriod9 : String = retrieveFromActiveSheet(workbook, "R" + giCurrentRowBUS101_B1);     // value for period 9
					var strPeriod10 : String = retrieveFromActiveSheet(workbook, "S" + giCurrentRowBUS101_B1);    // value for period 10
					var strPeriod11 : String = retrieveFromActiveSheet(workbook, "T" + giCurrentRowBUS101_B1);    // value for period 11
					var strPeriod12 : String = retrieveFromActiveSheet(workbook, "U" + giCurrentRowBUS101_B1);    // value for period 12

					
					var mirRequest : MIRequest = new MIRequest();
					var mirInRecord : MIRecord = new MIRecord();
					mirInRecord["CONO"] = gstrCompany;
					mirInRecord["DIVI"] = gstrDivision;
					mirInRecord["BUNO"] = gstrBudgetNumber;
					mirInRecord["BVER"] = gstrBudgetVersion;
					mirInRecord["AIT1"] = w1ait1;
					mirInRecord["AIT2"] = w1ait2;
					mirInRecord["AIT3"] = w1ait3;
					mirInRecord["AIT4"] = w1ait4;
					mirInRecord["AIT5"] = w1ait5;
					mirInRecord["AIT6"] = w1ait6;
					mirInRecord["AIT7"] = w1ait7;
					mirInRecord["CUCD"] = w1cucd;
					mirInRecord["BCU1"] = convertTo2DecimalPlaces(strPeriod1);
					mirInRecord["BCU2"] = convertTo2DecimalPlaces(strPeriod2);
					mirInRecord["BCU3"] = convertTo2DecimalPlaces(strPeriod3);
					mirInRecord["BCU4"] = convertTo2DecimalPlaces(strPeriod4);
					mirInRecord["BCU5"] = convertTo2DecimalPlaces(strPeriod5);
					mirInRecord["BCU6"] = convertTo2DecimalPlaces(strPeriod6);
					mirInRecord["BCU7"] = convertTo2DecimalPlaces(strPeriod7);
					mirInRecord["BCU8"] = convertTo2DecimalPlaces(strPeriod8);
					mirInRecord["BCU9"] = convertTo2DecimalPlaces(strPeriod9);
					mirInRecord["BC10"] = convertTo2DecimalPlaces(strPeriod10);
					mirInRecord["BC11"] = convertTo2DecimalPlaces(strPeriod11);
					mirInRecord["BC12"] = convertTo2DecimalPlaces(strPeriod12);

					mirRequest.Program = "BUS100MI";
					mirRequest.Record = mirInRecord;
					mirRequest.Transaction = "AddBudgetLines";
					// mirRequest.Tag = workbook;
					argumentArray[iArrayPosition] = mirRequest;
					iArrayPosition++;
				}

				giCurrentRowBUS101_B1++;        // increment our spreadsheet position
			}
			
			gdebug.Debug("iArrayPosition = " + iArrayPosition);
			gdebug.Debug("giCurrentRowBUS101_B1 = " + giCurrentRowBUS101_B1);
			
			if(argumentArray.length > 0)
			{
				gdebug.Debug("Argument Array length = " + argumentArray.length);
				
				var mirRequestArray = new MIRequest[argumentArray.length];
				for(var i = 0; i < argumentArray.length; i++)
				{
					mirRequestArray[i] = argumentArray[i];
				}
				addBudgetLines(mirRequestArray);
				//mwWorker.RunWorkerAsync(req, addBudgetLines_OnComplete);
			}
			
			gdebug.Debug("handleLines end");
			
		}

		private function addBudgetLines(req)
		{
			if(null != req)
			{
				gdebug.Debug("req count: " + req.length);
				var mwWorker = new MIMultiWorker();
				mwWorker.RunWorkerAsync(req, addBudgetLines_OnComplete);
			}
		}
		
		private function addBudgetLines_OnComplete(amirResponse : MIMultiResult)
		{
			try
			{
				if(null != amirResponse && amirResponse.HasError == false)
				{
					gdebug.Debug("No errors");
					ConfirmDialog.ShowInformationDialogNeverHidden("Complete", "Completed without any errors");
				}
				else
				{
					gdebug.Debug("Errors!");
					if(null != amirResponse.ResponseList && amirResponse.ResponseList.Count > 0)
					{
						var strErrors : String = null;
						for(var i = 0; i < amirResponse.ResponseList.Count; i++)
						{
							var reponse : MIResponse = amirResponse.ResponseList[i];
							if(true == reponse.HasError)
							{
								if(false == String.IsNullOrEmpty(strErrors))
								{
									strErrors += Environment.NewLine;
								}
								strErrors += "Response[" + i + "]: " + reponse.Error;
							}
						}
						if(false == String.IsNullOrEmpty(strErrors))
						{
							ConfirmDialog.ShowErrorDialog("Error", "The following errors were encountered" + Environment.NewLine + strErrors);
						}
						else
						{
							ConfirmDialog.ShowErrorDialog("Error", "Undetermined errors occured creating the budget lines, please check the Smart Office logs");
						}
					}
					else
					{
						ConfirmDialog.ShowErrorDialog("Error", "Undetermined errors occured creating the budget lines");
					}
				}
			
				try
				{
					gwbWorkbook.Save();                     // save the spreadsheet
				}
				catch(ext)
				{
				}
				
				CleanUp();

			}
			catch(ex)
			{
				ConfirmDialog.ShowErrorDialog("Exception", "An Exception occurred, please check the import" + Environment.NewLine + ex);
			}
		}
		
		public function OnbtnImportBudgetClick(sender: Object, e: RoutedEventArgs)
		{
			try
			{
				// here we do some initialisation of Excel
				InitialiseExcel();

				var strFilename : String = retrieveImportFile();            // retrieve the filename of the Excel spreadsheet to open
				if((null != strFilename) && (null != gexaApplication))      // ensure that not only do we have a filename, but we also managed to initialise Excel
				{
					gwbWorkbook = gexaApplication.Workbooks.Open(strFilename);  // open the spreadsheet
					if(null != gwbWorkbook)
					{
						gwbWorkbook.Saved = true;               // get rid of those annoying save messages

						giMaxRow = gwbWorkbook.ActiveSheet.Cells(gwbWorkbook.ActiveSheet.Rows.Count,1).End(-4162).Row;      // get the end row
						
						handleHeader();                         // kick off the creation of the header, this in turn will create the lines
						//gwbWorkbook.Save();                     // save the spreadsheet

					}
					else ConfirmDialog.ShowErrorDialog("Error", "Failed to Open Workbook '" + strFilename + "'");
				}
				else ConfirmDialog.ShowErrorDialog("Error", "Filename or Excel doesn't exist: " + strFilename);
			}
			catch(exException)
			{
				ConfirmDialog.ShowErrorDialog("Error", "Error: " + exException.description);
			}
			
		}




		// retrieve some data from the active spreadsheet
		// at a specific location
		private function retrieveFromActiveSheet(awbWorkBook, astrPosition)
		{
			//gdebug.Debug("retrieveFromActiveSheet() start: " + astrPosition);
			var result = "";
			
			if(null != awbWorkBook)
			{
				if(null != awbWorkBook.ActiveSheet)
				{
					result = awbWorkBook.ActiveSheet.Range(astrPosition).Value;
					if(true == String.IsNullOrEmpty(result))
					{
						result = "";
					}
					else if(0 == String.Compare(result, "undefined"))
					{
						result = "";
					}
				}
				else
				{
					gdebug.Error("No active worksheet");
				}
			}
			else
			{
				gdebug.Error("Workbook is null");
			}
			

			return(result);
		}

		// display an OpenFileDialog box
		// and extract the result
		private function retrieveImportFile()
		{
			var result : String = null;
			var ofdFile = new System.Windows.Forms.OpenFileDialog();    // we have to use the forms OpenFileDialog unfortunately
			if(null != ofdFile)
			{
				ofdFile.Multiselect = false;
				ofdFile.Filter = "Excel Files (*.xls;*.xlsx)|*.xls;*.xlsx|All Files (*.*)|*.*"; // filter on xls or xlsx files only
				
				if(true == ofdFile.ShowDialog())
				{
					result = ofdFile.FileName;
				}
			}
			return(result);
		}

		// our central cleanup function
		private function CleanUp()
		{
			CleanUpExcel();
		}

		// our Import button is being unloaded, now's a good time to clean
		// everything up
		private function OnImportFromExcelUnloaded(sender : Object, e : RoutedEventArgs)
		{
			if(null != btnImportBudget)
			{
				btnImportBudget.remove_Click(OnbtnImportBudgetClick);
				btnImportBudget.remove_Unloaded(OnbtnImportBudgetClickUnloaded);
			}
		}

		private function convertTo2DecimalPlaces(astrValue : String)
		{
			var strResult : String = "";

			if(false == String.IsNullOrEmpty(astrValue))
			{
				var dblTemp : double = astrValue;
				strResult = dblTemp.ToString("#.##");
			}

			return(strResult);
		}

		// check to ensure that we have a value when we extract from the 
		// spreadsheet
		private function doWeHaveAValueFromSpreadsheet(astrValue : String)
		{
			var bResult : boolean = false;
			if(false == String.IsNullOrEmpty(astrValue))
			{
				if(0 != String.Compare(astrValue, "undefined"))
				{
					bResult = true;
				}
			}
			return(bResult);
		}

		// close Excel and any workbooks
		private function CleanUpExcel()
		{
				// check to ensure we have a Workbook object
				// before we attempt to close the workbook
				if(null != gwbWorkbook)
				{
					gwbWorkbook.Close();
					gwbWorkbook = null;
				}
				// make sure we have actually created
				// the Excel Application object before
				// we Quit
				if(null != gexaApplication)
				{
					gexaApplication.Quit();
					gexaApplication = null;
				}
		}

		// Initialise Excel, essentially start the Excel Application and set it to visible
		private function InitialiseExcel()
		{
			var result = null;
			try
			{
				gexaApplication = new ActiveXObject("Excel.Application");
				gexaApplication.Visible = true;
				
				result = gexaApplication;
			}
			catch(exException)
			{
				ConfirmDialog.ShowErrorDialog("Error", "Error: " + exException.Message + Environment.NewLine + exException.StackTrace);
			}
			return(result);
		}

		
		
	}
}

 

Enjoy! 🙂

This entry was posted in Development, M3 / MoveX, Misc. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s