using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; 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; using System.Data; namespace AyaNovaQBI { internal class util { #region API stuff public static Guid QBI_INTEGRATION_ID { get { return new Guid("{82CD3609-4601-4C1A-9633-7836F92D2D06}"); } } public const string TEST_ROUTE = "notify/hello"; public const string API_BASE_ROUTE = "api/v8/"; private const int MAX_TRIES = 3;//max times to retry an api call before giving up private const int API_RETRY_DELAY = 3000;//pause in ms before retrying api call public static int HTTPCLIENT_TIMEOUT_SECONDS = 100;//changed by the setting in ops anyway, just a in-case sensible default here public static HttpClient client = null; //url once known to be good internal static string ApiBaseUrl { get; set; } //auth processes url for api and this is the best guess as to the client url to use for notification / help links etc internal static string GuessClientUrl { get; set; } internal static string JWT { get; set; } // internal static long AyaNovaUserId { get; set; } //probably don't need this, if I do then some code will need to be added to decode the JWT or at the server to get my currently logged in USER ID internal static string AyaNovaUserName { get; set; } internal static AuthorizationRoles AyaNovaUserRoles { get; set; } internal static UserType AyaNovaUserType { get; set; } internal static AyaNovaLicense ALicense { get; set; } = null; internal static Integration QBIntegration { get; set; } = null; internal static bool LOG_AVAILABLE { get; set; } = false; public static void InitClient() { if (client != null) { client.Dispose(); client = null; } client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(HTTPCLIENT_TIMEOUT_SECONDS); } /// /// Only a return value of "OK" is ok /// /// /// public static async Task InitAndConfirmAddressAsync(string serverUrl) { ApiBaseUrl = serverUrl; InitClient(); try { // TimeSpan tsDefault = client.Timeout; // client.Timeout = new TimeSpan(0, 0, 20); HttpResponseMessage response = await client.GetAsync(serverUrl + TEST_ROUTE); // client.Timeout = tsDefault; if (response.IsSuccessStatusCode) return "OK"; else return $"Failed: {response.StatusCode.ToString()}"; } catch (Exception ex) { while (ex.InnerException != null) ex = ex.InnerException; return $"Failed exception: \r\n{ex.Message}"; } } public async static Task AuthenticateAsync(string login, string password = null) { InitClient(); if (password == null) password = login; dynamic creds = new JObject(); creds.login = login; creds.password = password; var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + "auth"); requestMessage.Content = new StringContent(creds.ToString(), System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception($"Authentication error, route: AUTH\r\nError:{Err}\r\nInner error:{InnerErr}"); } var a = new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(await response.Content.ReadAsStringAsync()) }; if (response.IsSuccessStatusCode) { bool tfa = a.ObjectResponse["data"]["tfa"].Value(); if (tfa == true) { //Get temp token from response var tempToken = a.ObjectResponse["data"]["tt"].Value(); //get 2fa code and send it in do { tfa t = new tfa(); if (t.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) return false; string tfaPin = t.TFAPin; dynamic tfaCreds = new JObject(); tfaCreds.pin = tfaPin; tfaCreds.tempToken = tempToken; try { var tfaResponse = await TryPostAsync("auth/tfa-authenticate", tfaCreds.ToString(Newtonsoft.Json.Formatting.None));//trypost is no delay if (ProcessLoginResponse(tfaResponse)) return true; } catch (Exception ex) { if (!ex.Message.Contains("2003"))//if not an authentication error (bad pin) then throw it back up for display throw ex; //otherwise eat it and let them re-enter the pin again to mirror how ayanova web client works } } while (true); } else { return ProcessLoginResponse(a); } } else { if (a.ObjectResponse != null && a.ObjectResponse.ContainsKey("error")) { var errCode = a.ObjectResponse["error"]["code"].Value(); if (errCode.Contains("2003")) return false;//simple authentication error //some other error, possibly expired ayanova license etc, show it so it's clear why the login failed so they known it's not a creds issue var errMessage = a.ObjectResponse["error"]["message"].Value(); throw new Exception($"Code: {errCode} - {errMessage}"); } return false; } } private static bool ProcessLoginResponse(ApiResponse a) { if (a.ObjectResponse == null) return false; if (!a.HttpResponse.IsSuccessStatusCode) { return false; } if (a.ObjectResponse["data"]["l"].Value())//license lockout { throw new Exception("Server login from QBI is disabled due to AyaNova license issue"); } JWT = a.ObjectResponse["data"]["token"].Value(); AyaNovaUserName = a.ObjectResponse["data"]["name"].Value(); AyaNovaUserRoles = (AuthorizationRoles)(int.Parse(a.ObjectResponse["data"]["roles"].Value())); AyaNovaUserType = (UserType)(int.Parse(a.ObjectResponse["data"]["usertype"].Value())); return true; } public async static Task GetAsync(string route) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryGetAsync(route); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception($"API call failed after {MAX_TRIES.ToString()} attempts", FirstException); } private async static Task TryGetAsync(string route) { var requestMessage = new HttpRequestMessage(HttpMethod.Get, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception($"GET error, route: {route}\r\nError:{Err}\r\nInner error:{InnerErr}\r\nStack:{ex.StackTrace}"); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"GET error, code: {(int)response.StatusCode}, route: {route}\r\n{responseAsString}\r\n{response.ReasonPhrase}"); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } public async static Task PostAsync(string route, dynamic d) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPostAsync(route, d.ToString(Newtonsoft.Json.Formatting.None)); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception($"API call failed after {MAX_TRIES.ToString()} attempts", FirstException); } public async static Task PostAsync(string route, string s = null) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPostAsync(route, s); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception($"API call failed after {MAX_TRIES.ToString()} attempts", FirstException); } internal async static Task TryPostAsync(string route, string postJson = null) { var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); if (!string.IsNullOrWhiteSpace(postJson)) requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception($"POST error, route: {route}\r\nError:{Err}\r\nInner error:{InnerErr}\r\nStack:{ex.StackTrace}\r\nPOSTED OBJECT:\r\n{postJson}"); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(postJson)) postJson = "n/a"; throw new Exception($"POST error, code: {(int)response.StatusCode}, route: {route}\r\n{responseAsString}\r\n{response.ReasonPhrase}\r\nPOSTED OBJECT:\r\n{postJson}"); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } /// /// /// /// /// public static JObject Parse(string jsonString) { if (string.IsNullOrWhiteSpace(jsonString)) { return null; } return JObject.Parse(jsonString); } public static long IdFromResponse(ApiResponse a) { return a.ObjectResponse["data"]["id"].Value(); } public static uint CTokenFromResponse(ApiResponse a) { return a.ObjectResponse["data"]["concurrency"].Value(); } public async static Task PutAsync(string route, dynamic d) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPutAsync(route, d.ToString(Newtonsoft.Json.Formatting.None)); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception($"API call failed after {MAX_TRIES.ToString()} attempts", FirstException); } public async static Task PutAsync(string route) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPutAsync(route); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception($"API call failed after {MAX_TRIES.ToString()} attempts", FirstException); } public async static Task TryPutAsync(string route, string putJson = null) { var requestMessage = new HttpRequestMessage(HttpMethod.Put, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); if (!string.IsNullOrWhiteSpace(putJson)) requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception($"PUT error, route: {route}\r\nError:{Err}\r\nInner error:{InnerErr}\r\nStack:{ex.StackTrace}\r\nPOSTED OBJECT:\r\n{putJson}"); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(putJson)) putJson = "n/a"; throw new Exception($"PUT error, code: {(int)response.StatusCode}, route: {route}\r\n{responseAsString}\r\n{response.ReasonPhrase}\r\nPUT OBJECT:\r\n{putJson}"); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } public class ApiResponse { public HttpResponseMessage HttpResponse { get; set; } public JObject ObjectResponse { get; set; } public string CompactResponse { get { return ObjectResponse.ToString(Newtonsoft.Json.Formatting.None); } } } #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(); // var l = new List(); // for (int i = 1; i < random.Next(25, 100); i++) // l.Add(new InvoiceableItem { Customer = $"Customer {random.Next(1, 5)}", Linked = random.Next(2) == 1, Project = $"project {i}", ServiceDate = DateTime.Now.ToString("g"), ServiceNumber = (40 + i).ToString(), Status = $"Waiting to be invoiced", StatusColor = "FF00FFAA", WorkorderId = 4 }); // return l.OrderBy(x => x.Customer) // .ThenBy(x => x.ServiceNumber) // .ThenBy(x => x.ServiceDate) // .ToList(); //} /* ██╗███╗ ██╗██╗████████╗██╗ █████╗ ██╗ ██╗███████╗███████╗ ██║████╗ ██║██║╚══██╔══╝██║██╔══██╗██║ ██║╚══███╔╝██╔════╝ ██║██╔██╗ ██║██║ ██║ ██║███████║██║ ██║ ███╔╝ █████╗ ██║██║╚██╗██║██║ ██║ ██║██╔══██║██║ ██║ ███╔╝ ██╔══╝ ██║██║ ╚████║██║ ██║ ██║██║ ██║███████╗██║███████╗███████╗ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝╚══════╝╚══════╝ */ public static async Task InitializeQBI(StringBuilder initErrors) { //LOGIN to v8 first auth d = new auth(); if (d.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) return false; //ACCOUNTING ROLE? if (!AyaNovaUserRoles.HasFlag(AuthorizationRoles.Accounting)) { initErrors.AppendLine($"User must have the \"Accounting\" Role to use QBI\r\n"); return false; } //LICENSED? var r = await GetAsync("license"); ALicense = r.ObjectResponse["data"]["license"].ToObject(); //UNEXPIRED AYANOVA LICENSE? if (ALicense.licenseWillExpire && ALicense.licenseExpiration < DateTime.UtcNow) { initErrors.AppendLine($"AyaNova license has expired {ALicense.licenseExpiration.ToLocalTime().ToString("g")}"); return false; } //QBI LICENSED? if (ALicense.features.FirstOrDefault(z => z.Feature == "QBI") == null) { initErrors.AppendLine("QBI not licensed"); return false; } //BUILD DATE VERSION ALLOWED? if (ALicense.maintenanceExpiration < Timestamp.BuildAt) { initErrors.AppendLine($"NOT LICENSED!\r\n\r\nThis QBI plugin was built {Timestamp.BuildAt.ToString("g")}\r\nbut the licensed support and updates subscription has ended on {ALicense.maintenanceExpiration.ToLocalTime().ToString("g")}\r\n\r\nDowngrade back to your previous licensed QBI version or\r\npurchase a support and updates subscription to continue using this version of QBI."); return false; } //PFC - integration object check (fetch or create if not present) if (!await IntegrationCheck(initErrors)) return false; LOG_AVAILABLE = true; await IntegrationLog($"PFC: AyaNova user \"{AyaNovaUserName}\" starting QBI session, pre-flight check (PFC) commencing..."); //QB CONNECTION validation and setup try { if (await QBValidate() == pfstat.Cancel) { await IntegrationLog("PFC: Unable to validate QuickBooks connection, user selected cancel"); return false; } else { await IntegrationLog($"PFC: QB Company name= {QCompanyName}, QB Country version={QCountry}, QBVersion={QVersion}, Companyfile={QCompanyFile}"); await PopulateQBListCache(); await PopulateAyaListCache(); if (string.IsNullOrWhiteSpace(QBIntegration.IntegrationData) || QBIntegration.Items.Count == 0) return true;//nothing mapped or set to validate return to mainform for initial setup to be triggered //PFC - verify integration mapped objects still exist at each end (Util.PreFlightCheck() line 199) if (!await ValidateMap(initErrors)) return false; //DONE } } catch (Exception ex) { initErrors.AppendLine($"QuickBooks connection validation failed before connecting\r\n{ex.Message}"); return false; } await IntegrationLog("PFC: QBI initialized and ready for use"); return true; } /// /// Ensure existance of QBI Integration object /// /// /// public static async Task IntegrationCheck(StringBuilder initErrors) { ApiResponse r = null; try { r = await GetAsync($"integration/exists/{QBI_INTEGRATION_ID}"); if (r.ObjectResponse["data"].Value() == false) { //doesn't exist, need to create it now QBIntegration = new Integration(); QBIntegration.IntegrationAppId = QBI_INTEGRATION_ID; QBIntegration.Active = true; QBIntegration.Name = "QBI - QuickBooks Desktop integration"; r = await PostAsync("integration", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration)); QBIntegration.Id = IdFromResponse(r); await IntegrationLog("AyaNova QBI Integration installed to AyaNova"); } else { //Exists, fetch it check if active then we're done here r = await GetAsync($"integration/{QBI_INTEGRATION_ID}"); QBIntegration = r.ObjectResponse["data"].ToObject(); if (!QBIntegration.Active) { initErrors.AppendLine("QBI Integration is currently deactivated and can not be used\r\nThis setting can be changed in AyaNova in the Administration section -> Integrations -> QuickBooks Desktop integration record\r\nSet to active and save to enable QBI"); return false; } } return true; } catch (Exception ex) { initErrors.AppendLine("Error fetching QBI Integration object"); initErrors.AppendLine(ex.Message); initErrors.AppendLine(r.CompactResponse); return false; } } /// /// Ensure existance of QBI Integration object /// /// /// public static async Task ValidateMap(StringBuilder initErrors) { //Missing links table: DataTable dtTemp = new DataTable(); dtTemp.Columns.Add("MAPID", typeof(Guid)); dtTemp.Columns.Add("Name", typeof(string)); bool present = true; foreach (IntegrationItem m in QBIntegration.Items) { present = true; switch (m.AType) { case AyaType.Customer: present = QBClients.Rows.Contains(m.IntegrationItemId); break; case AyaType.Vendor: present = QBVendors.Rows.Contains(m.IntegrationItemId); break; case AyaType.ServiceRate: case AyaType.TravelRate: case AyaType.Part: present = QBItems.Rows.Contains(m.IntegrationItemId); break; } if (!present) dtTemp.Rows.Add(new object[] { m.Id, m.AType.ToString() + ": " + m.IntegrationItemName }); } if (dtTemp.Rows.Count > 0) { if (dtTemp.Rows.Count == QBIntegration.Items.Count) { //None of the items mapped match offer to remove them all #region Nothing matches await IntegrationLog("PFC: No integration maps match qb database objects!"); DialogResult dr = MessageBox.Show("None of the mapped items in AyaNova were found in the \r\n" + "Currently open QuickBooks database.\r\n" + "It's possible you have the wrong database open.\r\n\r\n" + "Do you want to remove all mappings from AyaNova?", "", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3); if (dr == DialogResult.Yes) { dr = MessageBox.Show("If you select YES all mappings will be removed from AyaNova.", "", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3); if (dr == DialogResult.Yes) { await IntegrationLog("PFC: User opted to remove all mappings after double warning."); QBIntegration.Items.Clear(); //foreach (DataRow row in dtTemp.Rows) //{ // QBIntegration.Items.Remove(row["MAPID"].ToString()); //} //TODO: POST BACK TO SERVER //Exists, fetch it check if active then we're done here ApiResponse r = await PostAsync($"integration/{QBI_INTEGRATION_ID}", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration)); QBIntegration = r.ObjectResponse["data"].ToObject(); return false; } } #endregion } else { //some items match so iterate them and offer to delete one by one await IntegrationLog("PFC: Some integration maps do not match qb database objects"); foreach (DataRow row in dtTemp.Rows) { DialogResult dr = MessageBox.Show("Linked object: " + row["Name"].ToString() + "\r\n" + "Is missing or set Inactive in QuickBooks.\r\n\r\nRemove it's link from AyaNova?", "", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3); if (dr == DialogResult.Cancel) return false; if (dr == DialogResult.Yes) QBIntegration.Items.Remove(QBIntegration.Items.Where(z => z.IntegrationItemId == row["MAPID"].ToString()).First()); } ApiResponse r = await PostAsync($"integration/{QBI_INTEGRATION_ID}", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration)); QBIntegration = r.ObjectResponse["data"].ToObject(); } } return true; } public static async Task IntegrationLog(string logLine) => await PostAsync("integration/log", Newtonsoft.Json.JsonConvert.SerializeObject(new NameIdItem { Id = QBIntegration.Id, Name = logLine })); #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")) { var msg = "QuickBooks doesn't appear to be running on this computer.\r\n" + "Start QuickBooks and open your company file now before proceeding."; await IntegrationLog($"PFC: {msg}"); if (MessageBox.Show( msg, "AyaNova QBI: Pre flight check", MessageBoxButtons.RetryCancel, MessageBoxIcon.Information) == DialogResult.Cancel) { await IntegrationLog($"PFC: User opted to cancel"); return pfstat.Cancel; } await IntegrationLog($"PFC: User opted to retry"); } 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) { var msg = $"You seem to be running QuickBooks older than 2008\r\nYou 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 QBFC15 or higher)\r\n\r\nVERSION FOUND = {QVersion}"; await IntegrationLog($"PFC: Failed - {msg}"); CopyableMessageBox cp = new CopyableMessageBox(msg); 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(); //---------------- // 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; } /// /// returns an empty string if passed in value is null or empty /// else returns Prepend plus passed in string followed by append string /// /// Text to return to the left of sText /// string to return if not null or empty /// Text to return to the right of sText /// public static string SS(string sPrepend, string sText, string sAppend) { if (sText == null) return ""; if (sText == "") return ""; return sPrepend + sText + sAppend; } #endregion qb specific utils #region QB API helper methods/ attributes/cached lists /// /// Populate or repopulate the list of /// public static async Task PopulateQBListCache() { //Get the cached QB data Waiting w = new Waiting(); w.Show(); w.Ops = "Reading from QuickBooks..."; w.Step = "Classes"; await PopulateQBClassCacheAsync(); w.Step = "Vendors"; await PopulateQBVendorCacheAsync(); w.Step = "Customers"; await PopulateQBClientCacheAsync(); w.Step = "Items"; await PopulateQBItemCache(); if (!(QVersion < 3))//qbXML 3.0 or higher (QB 2004 any country or newer) { w.Step = "Invoice templates"; await PopulateQBInvoiceTemplatesAsync(); } //case 632 w.Step = "Accounts"; await PopulateQBAccountCacheAsync(); //case 519 w.Step = "Terms"; await PopulateQBTermsCache(); w.Close(); } #region QuickBooks "items" public enum qbitemtype { Inventory, NonInventory, Service, OtherCharge, Assembly } private static DataTable _dtQBItems = null; /// /// qb items /// public static DataTable QBItems { get { return _dtQBItems; } } /// /// Given a QB Item ID, return the /// AyaNova Vendor ID linked to that items /// QB preferred Vendor ID or /// 0 on any problem or not found /// /// /// public static long AyaVendorForQBItem(string QBItemID) { if (string.IsNullOrWhiteSpace(QBItemID)) return 0; DataRow dr = _dtQBItems.Rows.Find(QBItemID); if (dr == null || dr["VendorID"] == null || dr["VendorID"].ToString() == "") return 0; DataRow drVendor = _dtQBVendors.Rows.Find(dr["VendorID"].ToString()); if (drVendor == null) return 0; var item = QBIntegration.Items.FirstOrDefault(z => z.IntegrationItemId == drVendor["ID"].ToString()); if (item == null) return 0; //Ok we have a matching vendor in the list, return the id of it return item.Id; } /// /// Populate the cached qb data /// billable /// private async static Task PopulateQBItemCache() { if (_dtQBItems == null) { _dtQBItems = new DataTable("QBItems"); //setup the columns _dtQBItems.Columns.Add("ID", typeof(string)); _dtQBItems.Columns.Add("FullName", typeof(string)); _dtQBItems.Columns.Add("Type", typeof(qbitemtype)); _dtQBItems.Columns.Add("Modified", typeof(DateTime)); _dtQBItems.Columns.Add("Price", typeof(decimal)); _dtQBItems.Columns.Add("Cost", typeof(decimal)); _dtQBItems.Columns.Add("SalesDesc", typeof(string)); _dtQBItems.Columns.Add("ReorderPoint", typeof(decimal)); _dtQBItems.Columns.Add("VendorID", typeof(string)); _dtQBItems.PrimaryKey = new DataColumn[] { _dtQBItems.Columns[0] }; //Case 237 _dtQBItems.DefaultView.Sort = "FullName asc"; } else _dtQBItems.Clear(); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 IItemQuery ItemQ = requestSet.AppendItemQueryRq(); //Active items only ItemQ.ORListQuery.ListFilter.ActiveStatus.SetValue(ENActiveStatus.asActiveOnly); //This is intended to be called after already sucessfully connected //to get version info so no special safety checks here sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //Added: 21-June-2006 nonzero status codes 500 and higher are serious errors, less than 500 could just mean no matching items in list if (response.StatusCode > 499) { throw new ApplicationException($"{response.StatusMessage} Code: {response.StatusCode}"); } //Added: 21-June-2006 outer if to avoid crash on no match response (code 1) if (response.StatusCode == 0) { IORItemRetList orItemRetList = response.Detail as IORItemRetList; if (!(orItemRetList.Count == 0)) { for (int ndx = 0; ndx <= (orItemRetList.Count - 1); ndx++) { IORItemRet orItemRet = orItemRetList.GetAt(ndx); // IY: The ortype property returns an enum // of the elements that can be contained in the OR object switch (orItemRet.ortype) { case ENORItemRet.orirItemServiceRet: { // orir prefix comes from OR + Item + Ret IItemServiceRet ItemServiceRet = orItemRet.ItemServiceRet; IORSalesPurchase sp = ItemServiceRet.ORSalesPurchase; _dtQBItems.Rows.Add(new object[] { ItemServiceRet.ListID.GetValue(), ItemServiceRet.FullName.GetValue(), qbitemtype.Service, ItemServiceRet.TimeModified.GetValue(), PriceGrabber(sp), CostGrabber(sp), SalesDescGrabber(sp), 0, PrefVendorGrabber(sp) }); } break; case ENORItemRet.orirItemInventoryRet: { IItemInventoryRet ItemInventoryRet = orItemRet.ItemInventoryRet; _dtQBItems.Rows.Add(new object[] { ItemInventoryRet.ListID.GetValue(), ItemInventoryRet.FullName.GetValue(), qbitemtype.Inventory, ItemInventoryRet.TimeModified.GetValue(), PriceGrabber(ItemInventoryRet.SalesPrice), PriceGrabber(ItemInventoryRet.PurchaseCost), ProcessQBString(ItemInventoryRet.SalesDesc), QuanGrabber(ItemInventoryRet.ReorderPoint), BaseRefIDGrabber(ItemInventoryRet.PrefVendorRef) }); } break; case ENORItemRet.orirItemNonInventoryRet: { IItemNonInventoryRet ItemNonInventoryRet = orItemRet.ItemNonInventoryRet; _dtQBItems.Rows.Add(new object[] { ItemNonInventoryRet.ListID.GetValue(), ItemNonInventoryRet.FullName.GetValue(), qbitemtype.NonInventory, ItemNonInventoryRet.TimeModified.GetValue(), PriceGrabber(ItemNonInventoryRet.ORSalesPurchase), CostGrabber(ItemNonInventoryRet.ORSalesPurchase), SalesDescGrabber(ItemNonInventoryRet.ORSalesPurchase), 0, PrefVendorGrabber(ItemNonInventoryRet.ORSalesPurchase) }); } break; case ENORItemRet.orirItemOtherChargeRet: { IItemOtherChargeRet ItemOtherChargeRet = orItemRet.ItemOtherChargeRet; _dtQBItems.Rows.Add(new object[] { ItemOtherChargeRet.ListID.GetValue(), ItemOtherChargeRet.FullName.GetValue(), qbitemtype.OtherCharge, ItemOtherChargeRet.TimeModified.GetValue(), PriceGrabber(ItemOtherChargeRet.ORSalesPurchase), CostGrabber(ItemOtherChargeRet.ORSalesPurchase), SalesDescGrabber(ItemOtherChargeRet.ORSalesPurchase), 0, PrefVendorGrabber(ItemOtherChargeRet.ORSalesPurchase) }); } break; case ENORItemRet.orirItemInventoryAssemblyRet: { IItemInventoryAssemblyRet ItemInventoryAssemblyRet = orItemRet.ItemInventoryAssemblyRet; _dtQBItems.Rows.Add(new object[] { ItemInventoryAssemblyRet.ListID.GetValue(), ItemInventoryAssemblyRet.FullName.GetValue(), qbitemtype.Assembly, ItemInventoryAssemblyRet.TimeModified.GetValue(), PriceGrabber(ItemInventoryAssemblyRet.SalesPrice), PriceGrabber(ItemInventoryAssemblyRet.PurchaseCost), ProcessQBString(ItemInventoryAssemblyRet.SalesDesc), QuanGrabber(ItemInventoryAssemblyRet.BuildPoint), BaseRefIDGrabber(ItemInventoryAssemblyRet.PrefVendorRef) }); } break; } } // for loop } // if } // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBItems: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } /// /// Given a qb BaseRef object /// return the ListID or empty string if null /// /// /// private static string BaseRefIDGrabber(IQBBaseRef b) { if (b != null && b.ListID != null) { return b.ListID.GetValue(); } return ""; } /// /// Given a qb Pricetype object /// return the price or zero on any issue /// /// /// private static decimal PriceGrabber(IQBPriceType p) { decimal d = 0;//default if (p != null) { d = System.Convert.ToDecimal(p.GetValue()); } return d; } /// /// Given a qb IQBQuanType object /// return the value as decimal or zero on any issue /// /// /// private static decimal QuanGrabber(IQBQuanType p) { decimal d = 0;//default if (p != null) { d = System.Convert.ToDecimal(p.GetValue()); } return d; } /// /// Given a qb salespurchase object /// return the price /// /// /// private static decimal PriceGrabber(IORSalesPurchase sp) { decimal d = 0;//default decimal d1 = 0; decimal d2 = 0; if (sp != null) { if (sp.SalesOrPurchase != null && sp.SalesOrPurchase.ORPrice != null && sp.SalesOrPurchase.ORPrice.Price != null) { d1 = System.Convert.ToDecimal(sp.SalesOrPurchase.ORPrice.Price.GetValue()); } if (sp.SalesAndPurchase != null && sp.SalesAndPurchase.SalesPrice != null) { d2 = System.Convert.ToDecimal(sp.SalesAndPurchase.SalesPrice.GetValue()); } //get the highest price of the two if (d1 > d2) d = d1; else d = d2; } return d; } /// /// Given a qb salespurchase object /// return the purchase cose /// /// /// private static decimal CostGrabber(IORSalesPurchase sp) { decimal d = 0;//default if (sp != null) { if (sp.SalesAndPurchase != null && sp.SalesAndPurchase.PurchaseCost != null) { d = System.Convert.ToDecimal(sp.SalesAndPurchase.PurchaseCost.GetValue()); } } return d; } /// /// return the sales description if available /// /// /// private static string SalesDescGrabber(IORSalesPurchase sp) { string str = ""; if (sp != null) { if (sp.SalesOrPurchase != null && sp.SalesOrPurchase.Desc != null) { str = sp.SalesOrPurchase.Desc.GetValue(); } if (sp.SalesAndPurchase != null && sp.SalesAndPurchase.SalesDesc != null) { str = sp.SalesAndPurchase.SalesDesc.GetValue(); } } return str; } /// /// return the preferred vendor if available /// /// /// private static string PrefVendorGrabber(IORSalesPurchase sp) { string str = ""; if (sp != null) { if (sp.SalesAndPurchase != null && sp.SalesAndPurchase.PrefVendorRef != null && sp.SalesAndPurchase.PrefVendorRef.ListID != null) { str = sp.SalesAndPurchase.PrefVendorRef.ListID.GetValue(); } } return str; } #endregion quickbooks items #region QuickBooks "Transactionclasses" public static string TRANSACTION_CLASS_NO_CLASS_SELECTED = ""; private static DataTable _dtQBClasses = null; /// /// QB Transaction Classes /// public static DataTable QBClasses { get { return _dtQBClasses; } } /// /// Populate the cached qb data /// billable /// private static async Task PopulateQBClassCacheAsync() { if (_dtQBClasses == null) { _dtQBClasses = new DataTable("QBClasses"); //setup the columns _dtQBClasses.Columns.Add("ID", typeof(string)); _dtQBClasses.Columns.Add("FullName", typeof(string)); _dtQBClasses.PrimaryKey = new DataColumn[] { _dtQBClasses.Columns[0] }; //Case 237 _dtQBClasses.DefaultView.Sort = "FullName asc"; } else _dtQBClasses.Clear(); //case 3268 _dtQBClasses.Rows.Add(new object[] { TRANSACTION_CLASS_NO_CLASS_SELECTED, "< Do not use classes >" }); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 IClassQuery cq = requestSet.AppendClassQueryRq(); //transactions cq.ORListQuery.ListFilter.ActiveStatus.SetValue(ENActiveStatus.asActiveOnly); //This is intended to be called in a secondary thread immediately after already sucessfully connected //to get version info so no special safety checks here sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error, likely no classes defined if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); IClassRetList cl = response.Detail as IClassRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { IClassRet clitem = cl.GetAt(ndx); if (clitem != null && clitem.FullName != null && clitem.ListID != null) { //add a record to the datatable _dtQBClasses.Rows.Add(new object[] { clitem.ListID.GetValue(), clitem.FullName.GetValue() }); } } // for loop } // if }//if status ==0 nonzero means some error, probably no classes defined // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBClasses: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } #endregion quickbooks transaction classes #region QuickBooks Invoice Templates private static DataTable _dtQBInvoiceTemplates = null; /// /// QB Transaction Templates /// public static DataTable QBInvoiceTemplates { get { return _dtQBInvoiceTemplates; } } /// /// Populate the cached qb data /// Invoice templates /// private static async Task PopulateQBInvoiceTemplatesAsync() { if (_dtQBInvoiceTemplates == null) { _dtQBInvoiceTemplates = new DataTable("QBInvoiceTemplates"); //setup the columns _dtQBInvoiceTemplates.Columns.Add("ID", typeof(string)); _dtQBInvoiceTemplates.Columns.Add("FullName", typeof(string)); _dtQBInvoiceTemplates.PrimaryKey = new DataColumn[] { _dtQBInvoiceTemplates.Columns[0] }; //Case 237 _dtQBInvoiceTemplates.DefaultView.Sort = "FullName asc"; } else _dtQBInvoiceTemplates.Clear(); _dtQBInvoiceTemplates.Rows.Add(new object[] { "", "< Use default >" }); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 ITemplateQuery cq = requestSet.AppendTemplateQueryRq(); //This is intended to be called in a secondary thread immediately after already sucessfully connected //to get version info so no special safety checks here sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //sLastRequestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error, likely no templates defined if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); ITemplateRetList cl = response.Detail as ITemplateRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { try { ITemplateRet clitem = cl.GetAt(ndx); bool b = clitem.IsActive.GetValue(); b = clitem.TemplateType == null; if (clitem != null && clitem.Name != null && clitem.ListID != null && clitem.TemplateType.IsSet() && clitem.TemplateType.GetValue() == ENTemplateType.tttInvoice) { //add a record to the datatable _dtQBInvoiceTemplates.Rows.Add(new object[] { clitem.ListID.GetValue(), clitem.Name.GetValue() }); } } catch (System.Runtime.InteropServices.COMException ex)//QBFC7 - throwing this exception here on last item in list can't pin it down { string s = ex.ToString(); } } // for loop } // if }//if status ==0 nonzero means some error, probably no classes defined // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBInvoiceTemplates: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } #endregion quickbooks Templates #region QuickBooks "Customers" private static async Task GetQBCustomerEditSequenceAsync(string customerid) { string strEditSequence = ""; //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 ICustomerQuery cq = requestSet.AppendCustomerQueryRq(); cq.IncludeRetElementList.Add("EditSequence"); cq.ORCustomerListQuery.ListIDList.Add(customerid); sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error this is unrecoverable //so throw an exception //Changed: 21-June-2006 nonzero status codes 500 and higher are serious errors, less than 500 could just mean no matching items in list if (response.StatusCode > 499) { throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode); } //Added: 21-June-2006 outer if to avoid crash on no match response (code 1) if (response.StatusCode == 0) { ICustomerRetList cl = response.Detail as ICustomerRetList; if (!(cl.Count == 0)) { ICustomerRet clitem = cl.GetAt(0); if (clitem != null) strEditSequence = clitem.EditSequence.GetValue(); } } // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("GetClientEditSequence: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } return strEditSequence; } private static DataTable _dtQBClients = null; /// /// QB Transaction Clients /// public static DataTable QBClients { get { return _dtQBClients; } } #region Address structure /// /// Address properties /// public struct Address { public string DeliveryAddress; public string City; public string StateProv; public string Country; public string Postal; } #endregion /// /// Populate the cached qb data /// of customers / clients /// private static async Task PopulateQBClientCacheAsync() { if (_dtQBClients == null) { _dtQBClients = new DataTable("QBClients"); //setup the columns _dtQBClients.Columns.Add("ID", typeof(string)); _dtQBClients.Columns.Add("FullName", typeof(string)); _dtQBClients.Columns.Add("MailAddress", typeof(Address)); _dtQBClients.Columns.Add("StreetAddress", typeof(Address)); _dtQBClients.Columns.Add("Phone", typeof(string)); _dtQBClients.Columns.Add("Fax", typeof(string)); _dtQBClients.Columns.Add("AltPhone", typeof(string)); _dtQBClients.Columns.Add("Email", typeof(string)); _dtQBClients.Columns.Add("Contact", typeof(string)); _dtQBClients.Columns.Add("Created", typeof(DateTime)); _dtQBClients.Columns.Add("Modified", typeof(DateTime)); _dtQBClients.Columns.Add("Account", typeof(string)); _dtQBClients.PrimaryKey = new DataColumn[] { _dtQBClients.Columns[0] }; //Case 237 _dtQBClients.DefaultView.Sort = "FullName asc"; } else _dtQBClients.Clear(); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 ICustomerQuery cq = requestSet.AppendCustomerQueryRq(); //Active Customers only cq.ORCustomerListQuery.CustomerListFilter.ActiveStatus.SetValue(ENActiveStatus.asActiveOnly); //case 1664 //not sure why these were ever in here to be honest //whups, it's *necessary* see case 1100 //there is a bug in qbfc7 which blows if there are "special" characters //it was fixed later but we use qbfc7 currently cq.ORCustomerListQuery.CustomerListFilter.ORNameFilter.NameRangeFilter.FromName.SetValue("0"); cq.ORCustomerListQuery.CustomerListFilter.ORNameFilter.NameRangeFilter.ToName.SetValue("ZZ"); sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML // string requestXML = requestSet.ToXMLString(); // MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); // MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error this is unrecoverable //so throw an exception //Changed: 21-June-2006 nonzero status codes 500 and higher are serious errors, less than 500 could just mean no matching items in list if (response.StatusCode > 499) { throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode); } //Added: 21-June-2006 outer if to avoid crash on no match response (code 1) if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); ICustomerRetList cl = response.Detail as ICustomerRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { ICustomerRet clitem = cl.GetAt(ndx); if (clitem != null) { //add a record to the datatable DataRow dr = _dtQBClients.Rows.Add( new object[]{ clitem.ListID.GetValue(), clitem.FullName.GetValue(), ProcessAddress(clitem.BillAddress), ProcessAddress(clitem.ShipAddress), ProcessQBString(clitem.Phone), ProcessQBString(clitem.Fax), ProcessQBString(clitem.AltPhone), ProcessQBString(clitem.Email), ProcessQBString(clitem.Contact), clitem.TimeCreated.GetValue(), clitem.TimeModified.GetValue(), ProcessQBString(clitem.AccountNumber), }); } } // for loop } // if } // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBClients: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } /// /// Take a qb address and return an AyaNova friendly /// address structure /// /// /// private static Address ProcessAddress(IAddress a) { Address b = new Address(); b.City = ""; b.Country = ""; b.DeliveryAddress = ""; b.Postal = ""; b.StateProv = ""; if (a == null) return b; //Append each line of the address, add cr/lf for each line if present after //the first line //Assumption: First line always has *something* in it b.DeliveryAddress = ProcessQBString(a.Addr1); b.DeliveryAddress += SS("\r\n", ProcessQBString(a.Addr2), ""); b.DeliveryAddress += SS("\r\n", ProcessQBString(a.Addr3), ""); //Address line 4 is a qbxml 2 or higher feature if (QVersion > 1.1) b.DeliveryAddress += SS("\r\n", ProcessQBString(a.Addr4), ""); //Country specific: b.City = ProcessQBString(a.City); //QBFC7 unifies county and province to "state" b.StateProv = ProcessQBString(a.State); //switch(QCountry) //{ // case "CA": // b.StateProv=ProcessQBString(a.Province); // break; // case "UK": // b.StateProv=ProcessQBString(a.County); // break; // default: // b.StateProv=ProcessQBString(a.State); // break; //} b.Country = ProcessQBString(a.Country); b.Postal = ProcessQBString(a.PostalCode); return b; } #endregion quickbooks transaction Clients #region QuickBooks "Vendors" private static DataTable _dtQBVendors = null; /// /// QB Vendors /// public static DataTable QBVendors { get { return _dtQBVendors; } } /// /// Populate the cached qb data /// of Vendors /// private static async Task PopulateQBVendorCacheAsync() { if (_dtQBVendors == null) { _dtQBVendors = new DataTable("QBVendors"); //setup the columns _dtQBVendors.Columns.Add("ID", typeof(string)); _dtQBVendors.Columns.Add("FullName", typeof(string)); _dtQBVendors.Columns.Add("MailAddress", typeof(Address)); _dtQBVendors.Columns.Add("StreetAddress", typeof(Address)); _dtQBVendors.Columns.Add("Phone", typeof(string)); _dtQBVendors.Columns.Add("Fax", typeof(string)); _dtQBVendors.Columns.Add("AltPhone", typeof(string)); _dtQBVendors.Columns.Add("Email", typeof(string)); _dtQBVendors.Columns.Add("Contact", typeof(string)); _dtQBVendors.Columns.Add("Created", typeof(DateTime)); _dtQBVendors.Columns.Add("Modified", typeof(DateTime)); _dtQBVendors.Columns.Add("Account", typeof(string)); _dtQBVendors.PrimaryKey = new DataColumn[] { _dtQBVendors.Columns[0] }; //Case 237 _dtQBVendors.DefaultView.Sort = "FullName asc"; } else _dtQBVendors.Clear(); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 IVendorQuery cq = requestSet.AppendVendorQueryRq(); //Active Vendors only cq.ORVendorListQuery.VendorListFilter.ActiveStatus.SetValue(ENActiveStatus.asActiveOnly); sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error; this is unrecoverable //so throw an exception //Changed: 21-June-2006 nonzero status codes 500 and higher are serious errors, less than 500 could just mean no matching items in list if (response.StatusCode > 499) { throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode); } //Added: 21-June-2006 outer if to avoid crash on no match response (code 1) if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); IVendorRetList cl = response.Detail as IVendorRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { IVendorRet clitem = cl.GetAt(ndx); if (clitem != null) { //add a record to the datatable DataRow dr = _dtQBVendors.Rows.Add( new object[] { clitem.ListID.GetValue(), clitem.Name.GetValue(), ProcessAddress(clitem.VendorAddress), ProcessAddress(clitem.VendorAddress), ProcessQBString(clitem.Phone), ProcessQBString(clitem.Fax), ProcessQBString(clitem.AltPhone), ProcessQBString(clitem.Email), ProcessQBString(clitem.Contact), clitem.TimeCreated.GetValue(), clitem.TimeModified.GetValue(), ProcessQBString(clitem.AccountNumber) }); } } // for loop } // if } // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBVendors: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } #endregion quickbooks Vendors #region QuickBooks "accounts" private static DataTable _dtQBAccounts = null; /// /// QB Transaction Classes /// public static DataTable QBAccounts { get { return _dtQBAccounts; } } /// /// Populate the cached qb account list data /// private static async Task PopulateQBAccountCacheAsync() { if (_dtQBAccounts == null) { _dtQBAccounts = new DataTable("QBAccounts"); //setup the columns _dtQBAccounts.Columns.Add("ID", typeof(string)); _dtQBAccounts.Columns.Add("FullName", typeof(string)); _dtQBAccounts.Columns.Add("Type", typeof(string)); _dtQBAccounts.PrimaryKey = new DataColumn[] { _dtQBAccounts.Columns[0] }; //Case 237 _dtQBAccounts.DefaultView.Sort = "FullName asc"; } else _dtQBAccounts.Clear(); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 IAccountQuery cq = requestSet.AppendAccountQueryRq(); //accounts (active ones only) cq.ORAccountListQuery.AccountListFilter.ActiveStatus.SetValue(ENActiveStatus.asActiveOnly); //This is intended to be called in a secondary thread immediately after already sucessfully connected //to get version info so no special safety checks here sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error, likely no classes defined if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); IAccountRetList cl = response.Detail as IAccountRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { IAccountRet clitem = cl.GetAt(ndx); if (clitem != null && clitem.FullName != null && clitem.ListID != null) { //add a record to the datatable _dtQBAccounts.Rows.Add(new object[] { clitem.ListID.GetValue(), clitem.AccountType.GetAsString() + " - " + clitem.FullName.GetValue(), clitem.AccountType.GetAsString() }); } } // for loop } // if }//if status ==0 nonzero means some error, probably no classes defined // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog($"PopulateQBAccounts: Failed with exception:{ex.Message}"); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } #endregion quickbooks accounts #region QuickBooks "Terms" private static DataTable _dtQBTerms = null; /// /// QB Transaction Classes /// public static DataTable QBTerms { get { return _dtQBTerms; } } /// /// Populate the cached qb terms list data /// private async static Task PopulateQBTermsCache() { if (_dtQBTerms == null) { _dtQBTerms = new DataTable("QBTerms"); //setup the columns _dtQBTerms.Columns.Add("ID", typeof(string)); _dtQBTerms.Columns.Add("FullName", typeof(string)); _dtQBTerms.PrimaryKey = new DataColumn[] { _dtQBTerms.Columns[0] }; //Case 237 _dtQBTerms.DefaultView.Sort = "FullName asc"; } else _dtQBTerms.Clear(); //Connect to QB and fill // IY: Create the session manager object using QBFC QBSessionManager sessionManager = new QBSessionManager(); // IY: We want to know if we begun a session so we can end it if an // error happens bool booSessionBegun = false; try { // 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 ITermsQuery cq = requestSet.AppendTermsQueryRq(); //This is intended to be called in a secondary thread immediately after already sucessfully connected //to get version info so no special safety checks here sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); sessionManager.BeginSession("", ENOpenMode.omDontCare); booSessionBegun = true; // IY: Do the request and get the response message set object IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); // Uncomment the following to view and save the request and response XML //string requestXML = requestSet.ToXMLString(); //MessageBox.Show(requestXML); // SaveXML(requestXML); //string responseXML = responseSet.ToXMLString(); //MessageBox.Show(responseXML); // SaveXML(responseXML); IResponse response = responseSet.ResponseList.GetAt(0); //nonzero indicates an error, likely no classes defined if (response.StatusCode == 0) { //int statusCode = response.StatusCode; //string statusMessage = response.StatusMessage; //string statusSeverity = response.StatusSeverity; //MessageBox.Show("Status:\nCode = " + statusCode + "\nMessage = " + statusMessage + "\nSeverity = " + statusSeverity); IORTermsRetList cl = response.Detail as IORTermsRetList; if (!(cl.Count == 0)) { for (int ndx = 0; ndx <= (cl.Count - 1); ndx++) { IORTermsRet clitem = cl.GetAt(ndx); if (clitem != null) { //add a record to the datatable if (clitem.StandardTermsRet != null && clitem.StandardTermsRet.ListID.IsSet()) _dtQBTerms.Rows.Add(new object[] { clitem.StandardTermsRet.ListID.GetValue(), clitem.StandardTermsRet.Name.GetValue() }); else _dtQBTerms.Rows.Add(new object[] { clitem.DateDrivenTermsRet.ListID.GetValue(), clitem.DateDrivenTermsRet.Name.GetValue() }); } } // for loop } // if }//if status ==0 nonzero means some error, probably no classes defined // IY: Close the session and connection with QuickBooks sessionManager.EndSession(); booSessionBegun = false; sessionManager.CloseConnection(); } catch (Exception ex) { await IntegrationLog("PopulateQBTerms: Failed with exception:" + ex.Message); if (booSessionBegun) { sessionManager.EndSession(); sessionManager.CloseConnection(); } throw; } } #endregion quickbooks Terms #endregion #region AyaNova cached lists public async static Task PopulateAyaListCache() { //Get the cached QB data Waiting w = new Waiting(); w.Show(); w.Ops = "Reading from AyaNova..."; w.Step = "Clients"; await PopulateAyaClientList(); w.Step = "Vendors"; await PopulateAyaVendorList(); w.Step = "Rates"; await PopulateAyaServiceRateList(); await PopulateAyaTravelRateList(); w.Step = "Parts"; await PopulateAyaPartList(); w.Close(); } #region AyaNova clients private static List _clientlist = null; /// /// AyaNova ClientPickList /// public static List AyaClientList { get { return _clientlist; } } public static async Task PopulateAyaClientList() { var a = await PostAsync("pick-list/list/", $"{{\"ayaType\":{(int)AyaType.Customer}}}"); _clientlist = a.ObjectResponse["data"].ToObject>(); } #endregion ayanova clients #region AyaNova Vendors private static List _vendorlist = null; /// /// AyaNova vendor list /// public static List AyaVendorList { get { return _vendorlist; } } public static async Task PopulateAyaVendorList() { var a = await PostAsync("pick-list/list/", $"{{\"ayaType\":{(int)AyaType.Vendor}}}"); _vendorlist = a.ObjectResponse["data"].ToObject>(); } #endregion ayanova vendors #region AyaNova Rates private static List _ServiceRatelist = null; /// /// AyaNova service rate list /// public static List AyaServiceRateList { get { return _ServiceRatelist; } } public static async Task PopulateAyaServiceRateList() { var a = await GetAsync("service-rate/accounting-list"); _ServiceRatelist = a.ObjectResponse["data"].ToObject>(); } private static List _TravelRatelist = null; /// /// AyaNova Travel rate list /// public static List AyaTravelRateList { get { return _TravelRatelist; } } public static async Task PopulateAyaTravelRateList() { var a = await GetAsync("travel-rate/accounting-list"); _TravelRatelist = a.ObjectResponse["data"].ToObject>(); } #endregion ayanova rates #region AyaNova Parts private static List _partlist = null; /// /// AyaNova part list /// public static List AyaPartList { get { return _partlist; } } public static async Task PopulateAyaPartList() { var a = await GetAsync("part/accounting-list"); _partlist = a.ObjectResponse["data"].ToObject>(); } #endregion ayanova parts #endregion #endregion qb specific non-api stuff } }