diff --git a/AyaNovaQBI/AyaNovaQBI.csproj b/AyaNovaQBI/AyaNovaQBI.csproj index 302c51c..d87ac84 100644 --- a/AyaNovaQBI/AyaNovaQBI.csproj +++ b/AyaNovaQBI/AyaNovaQBI.csproj @@ -12,6 +12,7 @@ 512 true true + false publish\ true Disk @@ -24,7 +25,6 @@ true 0 1.0.0.%2a - false false true @@ -37,6 +37,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU @@ -51,7 +52,7 @@ False True - ..\libs\QuickBooks\Interop.QBFC15.dll + ..\..\..\..\Program Files\Common Files\Intuit\QuickBooks\Interop.QBFC15.dll packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll @@ -78,6 +79,12 @@ + + Form + + + CopyableMessageBox.cs + Form @@ -101,6 +108,9 @@ auth.cs + + CopyableMessageBox.cs + MainForm.cs diff --git a/AyaNovaQBI/CopyableMessageBox.Designer.cs b/AyaNovaQBI/CopyableMessageBox.Designer.cs new file mode 100644 index 0000000..45f56da --- /dev/null +++ b/AyaNovaQBI/CopyableMessageBox.Designer.cs @@ -0,0 +1,105 @@ +namespace AyaNovaQBI +{ + partial class CopyableMessageBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.edOut = new System.Windows.Forms.TextBox(); + this.panel1 = new System.Windows.Forms.Panel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.btnCopy = new System.Windows.Forms.Button(); + this.panel1.SuspendLayout(); + this.panel2.SuspendLayout(); + this.SuspendLayout(); + // + // edOut + // + this.edOut.Dock = System.Windows.Forms.DockStyle.Fill; + this.edOut.Location = new System.Drawing.Point(0, 0); + this.edOut.Multiline = true; + this.edOut.Name = "edOut"; + this.edOut.ReadOnly = true; + this.edOut.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.edOut.Size = new System.Drawing.Size(488, 356); + this.edOut.TabIndex = 0; + // + // panel1 + // + this.panel1.Controls.Add(this.edOut); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(488, 356); + this.panel1.TabIndex = 1; + // + // panel2 + // + this.panel2.Controls.Add(this.btnCopy); + this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel2.Location = new System.Drawing.Point(0, 281); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(488, 75); + this.panel2.TabIndex = 2; + // + // btnCopy + // + this.btnCopy.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.btnCopy.Location = new System.Drawing.Point(135, 23); + this.btnCopy.Name = "btnCopy"; + this.btnCopy.Size = new System.Drawing.Size(208, 40); + this.btnCopy.TabIndex = 0; + this.btnCopy.Text = "Copy all to clipboard"; + this.btnCopy.UseVisualStyleBackColor = true; + this.btnCopy.Click += new System.EventHandler(this.btnCopy_Click); + // + // CopyableMessageBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(488, 356); + this.Controls.Add(this.panel2); + this.Controls.Add(this.panel1); + this.Name = "CopyableMessageBox"; + this.ShowInTaskbar = false; + this.Load += new System.EventHandler(this.CopyableMessageBox_Load); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.panel2.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TextBox edOut; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Button btnCopy; + } +} \ No newline at end of file diff --git a/AyaNovaQBI/CopyableMessageBox.cs b/AyaNovaQBI/CopyableMessageBox.cs new file mode 100644 index 0000000..63575c7 --- /dev/null +++ b/AyaNovaQBI/CopyableMessageBox.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AyaNovaQBI +{ + public partial class CopyableMessageBox : Form + { + private string mDisplay = string.Empty; + public CopyableMessageBox(string Display) + { + InitializeComponent(); + mDisplay=Display; + } + + private void btnCopy_Click(object sender, EventArgs e) + { + Clipboard.SetData(System.Windows.Forms.DataFormats.Text, mDisplay); + } + + private void CopyableMessageBox_Load(object sender, EventArgs e) + { + edOut.Text = mDisplay; + + } + } +} diff --git a/AyaNovaQBI/CopyableMessageBox.resx b/AyaNovaQBI/CopyableMessageBox.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/AyaNovaQBI/CopyableMessageBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AyaNovaQBI/MainForm.cs b/AyaNovaQBI/MainForm.cs index ee753aa..5c64cd9 100644 --- a/AyaNovaQBI/MainForm.cs +++ b/AyaNovaQBI/MainForm.cs @@ -19,7 +19,7 @@ namespace AyaNovaQBI async private void MainForm_Load(object sender, EventArgs e) { - + //Initialize StringBuilder initErrors = new StringBuilder(); if (await util.InitializeQBI(initErrors) == false) diff --git a/AyaNovaQBI/Properties/AssemblyInfo.cs b/AyaNovaQBI/Properties/AssemblyInfo.cs index 3fc2e4a..587b876 100644 --- a/AyaNovaQBI/Properties/AssemblyInfo.cs +++ b/AyaNovaQBI/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: ComVisible(true)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("82cd8e13-3297-472b-8c59-c4a50a69cb5c")] diff --git a/AyaNovaQBI/Timestamp.cs b/AyaNovaQBI/Timestamp.cs index 5ff832d..7b7b79a 100644 --- a/AyaNovaQBI/Timestamp.cs +++ b/AyaNovaQBI/Timestamp.cs @@ -22,7 +22,7 @@ namespace AyaNovaQBI /// /// Do not modify the definition of BuildAt as your changes will be discarded. /// - public static DateTime BuildAt { get { return new DateTime(637915929965140980); } } //--** + public static DateTime BuildAt { get { return new DateTime(637915984703608685); } } //--** /// /// The program that time stamped it. /// diff --git a/AyaNovaQBI/util.cs b/AyaNovaQBI/util.cs index 97464b1..f48c129 100644 --- a/AyaNovaQBI/util.cs +++ b/AyaNovaQBI/util.cs @@ -7,6 +7,9 @@ using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json.Linq; +using Interop.QBFC15; +using System.Text.RegularExpressions; +using System.Windows.Forms; namespace AyaNovaQBI { @@ -456,6 +459,21 @@ namespace AyaNovaQBI #endregion #region QB STUFF + + + public static string QCountry = "US"; + public static double QVersion = 1.1; + public static string QCompanyFile = ""; + public static string QCompanyName = ""; + public static string sLastRequestXML = ""; + + public enum pfstat + { + OK = 0, + Failed = 1, + Cancel = 2 + } + public static List GetInvoiceableItems() { var random = new Random(); @@ -488,7 +506,7 @@ namespace AyaNovaQBI return false; } - + //Get license var r = await GetAsync("license"); ALicense = r.ObjectResponse["data"]["license"].ToObject(); @@ -518,30 +536,35 @@ namespace AyaNovaQBI return false; } + //PFC - integration object check (fetch or create if not present) + //Need this early so can log any issues with other aspects + if (!await IntegrationCheck(initErrors)) + return false; + LOG_AVAILABLE = true; + - //PFC - Util.QBValidate stuff: //Validate QB connection can be made and open connection and start session with QB (see Util.QBValidate in v7 + try + { + + var pfstatus = await QBValidate(); + } + catch(Exception ex) + { + initErrors.AppendLine($"QuickBooks connection validation failed before connecting\r\n{ex.Message}"); + + return false; + } //once connected collect the country, version we are dealing with (Util.qbValidate) //confirm qb is 2008 or newer and bail if not (util.qbvalidate) //cache company name and other qb info //PFC - PopulateQBListCache() //PFC - PopulateAyaListCache() - //PFC - integration object check (fetch or create if not present) - if (!await IntegrationCheck(initErrors)) - return false; - LOG_AVAILABLE = true; - - //todo: logger needs to be enabled for integration next in a convenient to call util method and then make it work in UI - await IntegrationLog("Test: boot up"); - for (int x = 0; x < 25; x++) - await IntegrationLog($"Test entry {x}"); - - var res=await GetAsync($"integration/log/{QBIntegration.Id}"); - - //TODO: if QBIntegration.IntegrationData==null then nothing is set yet and it's fresh so trigger the setup stuff //PFC - Validate settings, create if necessary (Util.ValidateSettings()) and save + //TODO: if QBIntegration.IntegrationData==null then nothing is set yet and it's fresh so trigger the setup stuff + //PFC - verify integration mapped objects still exist at each end (Util.PreFlightCheck() line 199) //DONE @@ -602,10 +625,277 @@ namespace AyaNovaQBI public static async Task IntegrationLog(string logLine) { - await PostAsync("integration/log", Newtonsoft.Json.JsonConvert.SerializeObject(new NameIdItem { Id=QBIntegration.Id, Name=logLine})); + await PostAsync("integration/log", Newtonsoft.Json.JsonConvert.SerializeObject(new NameIdItem { Id = QBIntegration.Id, Name = logLine })); } - #endregion + + + #region PFC QB side + /// + /// Open QB connection + /// gather info required for future + /// transactions + /// + public async static Task QBValidate() + { + // We want to know if we begun a session so we can end it if an + // error happens + bool booSessionBegun = false; + bool bConnected = false; + // Create the session manager object using QBFC + QBSessionManager sessionManager = new QBSessionManager(); + + while (!booSessionBegun) + { + try + { + sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); + bConnected = true; + sessionManager.BeginSession("", ENOpenMode.omDontCare); + booSessionBegun = true; + + } + catch (System.Runtime.InteropServices.COMException ex) + { + if (bConnected) + sessionManager.CloseConnection(); + if (ex.ErrorCode == -2147220458 || ex.ErrorCode == -2147220472 || ex.Message.Contains("Could not start")) + { + + if (MessageBox.Show( + "QuickBooks doesn't appear to be running on this computer.\r\n" + + "Start QuickBooks and open your company file now before proceeding.", + "AyaNova QBI: Pre flight check", + MessageBoxButtons.RetryCancel, MessageBoxIcon.Information) == DialogResult.Cancel) return pfstat.Cancel; + } + else + { + await IntegrationLog("PFC: QBValidate connect unanticipated exception: " + ex.Message + "\r\nError code:" + ex.ErrorCode.ToString()); + MessageBox.Show(ex.Message + "\r\nError code:" + string.Format("(HRESULT:0x{0:X8})", ex.ErrorCode)); + return pfstat.Cancel; + } + + } + } + + try + { + + //Get the country and latest version supported + QVersion = 0; + QCountry = "US";//default + + string[] versions = sessionManager.QBXMLVersionsForSession; + double vers = 0; + + Regex rxVersion = new Regex("[0-9.,]+", RegexOptions.Multiline | RegexOptions.Compiled); + + foreach (string s in versions) + { + if (s.StartsWith("CA") || s.StartsWith("ca")) + { + QCountry = "CA"; + + } + else if (s.StartsWith("UK") || s.StartsWith("uk")) + { + QCountry = "UK"; + + } + + //case 262 + //strip out only numeric bit regardless of what text is in there + //including commas if it's french canadian and using a comma instead of a decimal point + //(the safe to double will handle the comma if present) + string strVersionNumber = rxVersion.Match(s).Value; + vers = SafeToDouble(strVersionNumber); + + if (vers > QVersion) + { + QVersion = vers; + } + } + + if (QVersion < 6.0) + { + await IntegrationLog("PFC: Failed, QuickBooks found is too old, 2008 or higher is required, prompted user to run QB update utility to be able to proceed"); + CopyableMessageBox cp = new CopyableMessageBox( + + "You seem to be running QuickBooks older than 2008\r\n" + + "You must update to 2008 or higher before you can use AyaNova QBI.\r\n\r\n" + + "(If you are running QuickBooks 2008 or higher and still getting this error, ensure that you are also using QBFC7 or higher)\r\n\r\n" + + "VERSION FOUND = " + QVersion + + ); + + cp.ShowDialog(); + + return pfstat.Failed; + } + + + //Get the company file to open + QCompanyFile = sessionManager.GetCurrentCompanyFileName(); + + // if(QCountry=="US") + // { + //Get company data + // IY: Get the RequestMsgSet based on the correct QB Version + IMsgSetRequest requestSet = getLatestMsgSetRequest(sessionManager); + + // IY: Initialize the message set request object + requestSet.Attributes.OnError = ENRqOnError.roeStop; + + // IY: Add the request to the message set request object + ICompanyQuery cq = requestSet.AppendCompanyQueryRq(); + + IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); + IResponse response = responseSet.ResponseList.GetAt(0); + if (response.StatusCode != 0) + { + await IntegrationLog("PFC: Failed Company query:" + response.StatusMessage + ", " + response.StatusCode.ToString()); + throw new ApplicationException("PFC: Failed Company query:" + response.StatusMessage + ", " + response.StatusCode.ToString()); + + } + + ICompanyRet cl = response.Detail as ICompanyRet; + QCompanyName = ProcessQBString(cl.CompanyName); + requestSet.ClearRequests(); + //---------------- + // } + // else + // QCompanyName=QCompanyFile; + + // Close the session and connection with QuickBooks + sessionManager.EndSession(); + booSessionBegun = false; + sessionManager.CloseConnection(); + return pfstat.OK; + } + catch (Exception ex) + { + //MessageBox.Show(ex.Message.ToString() + "\nStack Trace: \n" + ex.StackTrace + "\nExiting the application"); + + await IntegrationLog("PFC: Failed with exception:" + ex.Message); + if (booSessionBegun) + { + sessionManager.EndSession(); + sessionManager.CloseConnection(); + } + throw; + } + } + #endregion pfc qb side + + + + + + #region QB Specific utils + + + /// + /// + /// + /// + /// + public static IMsgSetRequest getLatestMsgSetRequest(QBSessionManager sessionManager) + { + // Find and adapt to supported version of QuickBooks + short qbXMLMajorVer = 0; + short qbXMLMinorVer = 0; + + if (QVersion >= 5.0) + { + qbXMLMajorVer = 5; + qbXMLMinorVer = 0; + } + else if (QVersion >= 4.0) + { + qbXMLMajorVer = 4; + qbXMLMinorVer = 0; + } + else if (QVersion >= 3.0) + { + qbXMLMajorVer = 3; + qbXMLMinorVer = 0; + } + else if (QVersion >= 2.0) + { + qbXMLMajorVer = 2; + qbXMLMinorVer = 0; + } + else if (QVersion >= 1.1) + { + qbXMLMajorVer = 1; + qbXMLMinorVer = 1; + } + else + { + qbXMLMajorVer = 1; + qbXMLMinorVer = 0; + throw new System.NotSupportedException("QuickBooks 1.0 (2002 initial release) is not supported, use QuickBooks online update feature now."); + } + + // Create the message set request object + IMsgSetRequest requestMsgSet = sessionManager.CreateMsgSetRequest(QCountry, qbXMLMajorVer, qbXMLMinorVer); + return requestMsgSet; + } + + /// + /// Handle null qb string types with "aplomb" :) + /// + /// + /// + private static string ProcessQBString(IQBStringType qs) + { + if (qs == null) return ""; + return qs.GetValue(); + } + + /// + /// Case 262 addition + /// Convert a string to a double handling french canadian locale + /// , since qb is not locale aware in it's api + /// conversion can fail because .net expects a comma that isn't there + /// + /// case 262 redux, changed to use tryparse and explicitly replace comma if present + /// for french canadian versions + /// + /// + /// + /// + public static double SafeToDouble(string s) + { + if (string.IsNullOrEmpty(s)) return 0; + if (s.Contains(",")) + s = s.Replace(",", "."); + + double retvalue = 0; + if (!double.TryParse(s, out retvalue)) + { + try + { + retvalue = Convert.ToDouble(s, System.Globalization.CultureInfo.InvariantCulture); + } + catch (System.FormatException) + { + CopyableMessageBox cp = new CopyableMessageBox("SafeToDouble: Can't parse QB string double version number:\r\n[" + + s + + "]\r\nPlease copy and send this message to AyaNova tech support at support@ayanova.com"); + cp.ShowDialog(); + throw new System.ApplicationException("SafeToDouble: Can't parse QB string double value number: \"" + s + "\""); + } + } + return retvalue; + } + + #endregion qb specific utils + + + + + #endregion qb stuff } diff --git a/libs/QuickBooks/QBFC15_0Installer.exe b/libs/QuickBooks/QBFC15_0Installer.exe new file mode 100644 index 0000000..ec2d8a6 Binary files /dev/null and b/libs/QuickBooks/QBFC15_0Installer.exe differ