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! 🙂