5997 lines
240 KiB
C#
5997 lines
240 KiB
C#
using Interop.QBFC15;
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
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 QBIIntegrationData QDat { get; set; } = null;
|
|
internal static bool LOG_AVAILABLE { get; set; } = false;
|
|
|
|
internal static bool USE_INVENTORY { get; set; } = false;
|
|
|
|
|
|
|
|
public static void InitClient()
|
|
{
|
|
if (client != null)
|
|
{
|
|
client.Dispose();
|
|
client = null;
|
|
}
|
|
client = new HttpClient();
|
|
client.Timeout = TimeSpan.FromSeconds(HTTPCLIENT_TIMEOUT_SECONDS);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only a return value of "OK" is ok
|
|
/// </summary>
|
|
/// <param name="serverUrl"></param>
|
|
/// <returns></returns>
|
|
public static async Task<string> 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 static async Task<bool> 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<bool>();
|
|
if (tfa == true)
|
|
{
|
|
//Get temp token from response
|
|
|
|
var tempToken = a.ObjectResponse["data"]["tt"].Value<string>();
|
|
|
|
//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<string>();
|
|
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<string>();
|
|
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<bool>())//license lockout
|
|
{
|
|
throw new Exception("Server login from QBI is disabled due to AyaNova license issue");
|
|
}
|
|
|
|
JWT = a.ObjectResponse["data"]["token"].Value<string>();
|
|
|
|
AyaNovaUserName = a.ObjectResponse["data"]["name"].Value<string>();
|
|
AyaNovaUserRoles = (AuthorizationRoles)(int.Parse(a.ObjectResponse["data"]["roles"].Value<string>()));
|
|
AyaNovaUserType = (UserType)(int.Parse(a.ObjectResponse["data"]["usertype"].Value<string>()));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
public static async Task<ApiResponse> 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 static async Task<ApiResponse> 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 static async Task<ApiResponse> 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 static async Task<ApiResponse> 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 static async Task<ApiResponse> 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) };
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="jsonString"></param>
|
|
/// <returns></returns>
|
|
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<long>();
|
|
}
|
|
|
|
public static uint CTokenFromResponse(ApiResponse a)
|
|
{
|
|
return a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
|
}
|
|
|
|
|
|
|
|
public static async Task<ApiResponse> 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 static async Task<ApiResponse> PutAsync(string route, string s = null)
|
|
{
|
|
Exception FirstException = null;
|
|
|
|
for (int x = 0; x < MAX_TRIES; x++)
|
|
{
|
|
try
|
|
{
|
|
return await TryPutAsync(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);
|
|
}
|
|
|
|
public static async Task<ApiResponse> 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 static async Task<ApiResponse> 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 async Task<bool> 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<AyaNovaLicense>();
|
|
|
|
//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;
|
|
|
|
//PFC - get global setttings for use inventory and others await window.$gz.api.get("global-biz-setting/client");
|
|
r = await GetAsync("global-biz-setting/client");
|
|
USE_INVENTORY = r.ObjectResponse["data"]["useInventory"].Value<bool>();
|
|
//return object for future reference
|
|
//var ret = new
|
|
//{
|
|
// //Actual global settings:
|
|
// FilterCaseSensitive = AyaNova.Util.ServerGlobalBizSettings.Cache.FilterCaseSensitive,
|
|
// UseInventory = AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory,
|
|
// DefaultTaxPartSaleId = AyaNova.Util.ServerGlobalBizSettings.Cache.TaxPartSaleId,
|
|
// DefaultTaxPartPurchaseId = AyaNova.Util.ServerGlobalBizSettings.Cache.TaxPartPurchaseId,
|
|
// DefaultTaxRateSaleId = AyaNova.Util.ServerGlobalBizSettings.Cache.TaxRateSaleId,
|
|
// WorkOrderTravelDefaultMinutes = AyaNova.Util.ServerGlobalBizSettings.Cache.WorkOrderTravelDefaultMinutes,
|
|
// WorkLaborScheduleDefaultMinutes = AyaNova.Util.ServerGlobalBizSettings.Cache.WorkLaborScheduleDefaultMinutes,
|
|
// SignatureTitle = AyaNova.Util.ServerGlobalBizSettings.Cache.SignatureTitle,
|
|
// SignatureHeader = AyaNova.Util.ServerGlobalBizSettings.Cache.SignatureHeader,
|
|
// SignatureFooter = AyaNova.Util.ServerGlobalBizSettings.Cache.SignatureFooter,
|
|
// CSRInfoText = AyaNova.Util.ServerGlobalBizSettings.Cache.CustomerServiceRequestInfoText,
|
|
|
|
|
|
// //used to drive UI in case of unlicensed or attention required
|
|
// LicenseStatus = AyaNova.Core.License.ActiveKey.Status,
|
|
// MaintenanceExpired = AyaNova.Core.License.ActiveKey.MaintenanceExpired,
|
|
// ServerDbId = AyaNova.Core.License.ServerDbId,
|
|
// Company = AyaNova.Core.License.ActiveKey.RegisteredTo
|
|
|
|
//};
|
|
|
|
|
|
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();
|
|
//PFC - verify integration mapped objects still exist in QB
|
|
if (!await ValidateQuickBooksHasMappedItems(initErrors))
|
|
return false;
|
|
await IntegrationLog("PFC: QBI initialized and ready for use");
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
initErrors.AppendLine($"QuickBooks connection validation failed before connecting\r\n{ex.Message}");
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Ensure existance of QBI Integration object
|
|
/// </summary>
|
|
/// <param name="initErrors"></param>
|
|
/// <returns></returns>
|
|
public static async Task<bool> IntegrationCheck(StringBuilder initErrors)
|
|
{
|
|
ApiResponse r = null;
|
|
try
|
|
{
|
|
r = await GetAsync($"integration/exists/{QBI_INTEGRATION_ID}");
|
|
|
|
if (r.ObjectResponse["data"].Value<bool>() == 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";
|
|
QDat = new QBIIntegrationData();
|
|
QBIntegration.IntegrationData = Newtonsoft.Json.JsonConvert.SerializeObject(QDat);//default empty integration data object
|
|
r = await PostAsync("integration", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration));
|
|
QBIntegration = r.ObjectResponse["data"].ToObject<Integration>();
|
|
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<Integration>();
|
|
|
|
if (string.IsNullOrWhiteSpace(QBIntegration.IntegrationData))
|
|
{
|
|
initErrors.AppendLine("QBI Integration data is empty which should not happen normally and indicates corruption of some kind with the QBI setttings data stored in AyaNova.\r\nThe QBI settings should be removed and re-created by deleting the QBI Integration object in AyaNova\r\nSee the Administration section -> Integrations -> QuickBooks Desktop integration record in AyaNova\r\nThis record should be deleted then restart QBI to start setup again and create a fresh QBI integration settings object in AyaNova");
|
|
return false;
|
|
}
|
|
QDat = Newtonsoft.Json.JsonConvert.DeserializeObject<QBIIntegrationData>(QBIntegration.IntegrationData);
|
|
|
|
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;
|
|
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Ensure mapped items still existing in QuickBooks
|
|
/// </summary>
|
|
/// <param name="initErrors"></param>
|
|
/// <returns></returns>
|
|
public static async Task<bool> ValidateQuickBooksHasMappedItems(StringBuilder initErrors)
|
|
{
|
|
//Missing links table:
|
|
DataTable dtTemp = new DataTable();
|
|
dtTemp.Columns.Add("MAPID", typeof(long));
|
|
dtTemp.Columns.Add("Name", typeof(string));
|
|
|
|
bool present = true;
|
|
List<NameIdItem> BadIntegrationItemIds = new List<NameIdItem>();
|
|
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)
|
|
BadIntegrationItemIds.Add(new NameIdItem { Name = m.IntegrationItemName, Id = m.Id });
|
|
//dtTemp.Rows.Add(new object[] { m.Id, m.AType.ToString() + ": " + m.IntegrationItemName });
|
|
|
|
}
|
|
|
|
if (BadIntegrationItemIds.Count > 0)
|
|
{
|
|
if (BadIntegrationItemIds.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();
|
|
await SaveIntegrationObject();
|
|
////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<Integration>();
|
|
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)
|
|
foreach (var bi in BadIntegrationItemIds)
|
|
{
|
|
|
|
DialogResult dr = MessageBox.Show("Linked object: " + bi.Name + "\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)
|
|
{
|
|
//var mapId = (long)row["MAPID"];
|
|
var removeItem = QBIntegration.Items.Where(z => z.Id == bi.Id).First();
|
|
//TODO: this needs to be a reverse for next loop
|
|
bool bResult = QBIntegration.Items.Remove(removeItem);
|
|
if (!bResult)
|
|
MessageBox.Show("Error attempting to remove unmapped item; it could not be found in the map list");
|
|
|
|
}
|
|
}
|
|
await SaveIntegrationObject();
|
|
|
|
//ApiResponse r = await PostAsync($"integration/{QBI_INTEGRATION_ID}", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration));
|
|
//QBIntegration = r.ObjectResponse["data"].ToObject<Integration>();
|
|
|
|
}
|
|
}
|
|
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 Validate User settings are completed and valid
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Validate the users preferences
|
|
/// if any are missing or invalid prompt for them
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static async Task<pfstat> ValidateSettings(bool ForceReset)
|
|
{
|
|
bool SetEverything = false;
|
|
if (ForceReset)
|
|
SetEverything = true;
|
|
//Display user friendly dialog
|
|
//explaining that configuration needs to be set
|
|
if (QDat.NothingSet)
|
|
{
|
|
SetEverything = true;
|
|
MessageBox.Show(
|
|
"AyaNova QBI has now connected sucessfully to both AyaNova and QuickBooks.\r\n\r\n" +
|
|
"The next step is to set preferences for how AyaNova QBI will operate.\r\n\r\n" +
|
|
"AyaNova QBI will now step through each setting and get your preference\r\n" +
|
|
"in order to integrate AyaNova with QuickBooks.\r\n\r\n" +
|
|
"These settings can be changed later.",
|
|
"Setup wizard", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
|
|
await IntegrationLog("ValidateSettings: there are no QBI settings made - prompting user for all preferences now");
|
|
}
|
|
|
|
if (ForceReset)
|
|
await IntegrationLog("ValidateSettings: forced reset of all QBI settings initiated");
|
|
|
|
|
|
#region confirm company file
|
|
|
|
ApproveCompanyFile s0 = new ApproveCompanyFile();
|
|
|
|
s0.QBCompanyName = QCompanyName.Replace("&", "&&");
|
|
s0.QBCompanyPath = QCompanyFile;
|
|
|
|
if (s0.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
await IntegrationLog("ValidateSettings: User cancelled when shown company file currently open - " + QCompanyFile);
|
|
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WO Pre status
|
|
|
|
//Validate any existing status
|
|
if (SetEverything == false && QDat.PreWOStatus != 0)
|
|
{
|
|
if (AyaWOStatusList.Any(z => z.Id == QDat.PreWOStatus))
|
|
goto PRESTATUSOK;
|
|
}
|
|
else
|
|
{
|
|
//Empty pre status is valid if not first
|
|
//time setup as user can opt for selecting
|
|
//workorders of any status
|
|
if (SetEverything == false)
|
|
goto PRESTATUSOK;
|
|
}
|
|
|
|
|
|
//We've arrived here because there is no valid setting for Pre workorder status
|
|
//or it's the first time through and needs to be selected on way or another
|
|
SetWOStatus s1 = new SetWOStatus();
|
|
s1.DialogTitle = "AyaNova QBI setup - Choose billable Workorder Status";
|
|
s1.OptionTitle = "Billable workorder status";
|
|
s1.OptionDescription = "One of AyaNova QBI's tasks is to look for work orders in AyaNova \r\n" +
|
|
"that are ready to be billed out and put them in a list for your selection. \r\n" +
|
|
" \r\n" +
|
|
"By default QBI will consider work orders that are set to service completed \r\n" +
|
|
"and are not closed as it's selection criteria. \r\n" +
|
|
" \r\n" +
|
|
"In addition, you can further refine the types of work orders that QBI \r\n" +
|
|
"considers ready for billing by specifying here a particular workorder Status \r\n" +
|
|
"you want to include in addition to the default criteria. ";
|
|
s1.SelectedStatus = QDat.PreWOStatus;
|
|
s1.PreStatus = true;
|
|
if (s1.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.PreWOStatus = s1.SelectedStatus;
|
|
|
|
|
|
PRESTATUSOK:
|
|
#endregion
|
|
|
|
#region WO POST status
|
|
|
|
//Validate any existing status
|
|
if (SetEverything == false && QDat.PostWOStatus != 0)
|
|
{
|
|
if (AyaWOStatusList.Any(z => z.Id == QDat.PostWOStatus))
|
|
goto POSTSTATUSOK;
|
|
}
|
|
else
|
|
{
|
|
//Empty post status is valid if not first
|
|
//time setup
|
|
if (SetEverything == false)
|
|
goto POSTSTATUSOK;
|
|
}
|
|
|
|
//We've arrived here because there is no valid setting for POST workorder status
|
|
//or it's the first time through and needs to be selected on way or another
|
|
s1 = new SetWOStatus();
|
|
s1.DialogTitle = "AyaNova QBI setup - Choose post billed Workorder Status";
|
|
s1.OptionTitle = "Post billing workorder status";
|
|
s1.OptionDescription = "After QBI has billed out a work order, it can change the \r\n" +
|
|
"work order status for you automatically if desired.\r\nIt is recommended to set the work order to a locking / closed status after invoicing.";
|
|
s1.SelectedStatus = QDat.PostWOStatus;
|
|
s1.PreStatus = false;
|
|
if (s1.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.PostWOStatus = s1.SelectedStatus;
|
|
s1.Dispose();
|
|
s1 = null;
|
|
|
|
POSTSTATUSOK:
|
|
#endregion
|
|
|
|
#region Outside service charge as
|
|
//Validate any existing status
|
|
if (SetEverything == false && QDat.OutsideServiceChargeAs != null && QDat.OutsideServiceChargeAs != "")
|
|
{
|
|
if (QBItems.Rows.Contains(QDat.OutsideServiceChargeAs))
|
|
goto OUTSIDESERVICECHARGEASOK;
|
|
else
|
|
{
|
|
MessageBox.Show("The QuickBooks Item previously set for invoicing Outside Service items\r\n" +
|
|
"No longer appears to be valid. You will next be prompted to re-select a valid \r\n" +
|
|
"QuickBooks Item.");
|
|
}
|
|
}
|
|
|
|
|
|
//We've arrived here because there is no valid setting for OutsideServiceChargeAs
|
|
SetQBChargeAs s2 = new SetQBChargeAs();
|
|
s2.DialogTitle = "AyaNova QBI setup - Charge outside service as?";
|
|
s2.OptionTitle = "Outside service";
|
|
s2.OptionDescription = "QBI needs to know what QuickBooks Item you want \r\n" +
|
|
"to use when invoicing the AyaNova \"Outside service\" portion of a work order.\r\n\r\n" +
|
|
"Outside service is any 3rd party repair that is billable to the customer.\r\n\r\n" +
|
|
"This setting is mandatory / required.";
|
|
s2.QBItems = QBItems;
|
|
s2.SelectedQBItem = QDat.OutsideServiceChargeAs;
|
|
if (s2.ShowDialog() == DialogResult.Cancel)
|
|
return pfstat.Cancel;
|
|
else
|
|
QDat.OutsideServiceChargeAs = s2.SelectedQBItem;
|
|
s2.Dispose();
|
|
s2 = null;
|
|
|
|
OUTSIDESERVICECHARGEASOK:
|
|
|
|
#endregion
|
|
|
|
#region Misc expense charge as
|
|
//Validate any existing
|
|
if (SetEverything == false && QDat.MiscExpenseChargeAs != null && QDat.MiscExpenseChargeAs != "")
|
|
{
|
|
goto MISCCHARGEASOK;
|
|
}
|
|
|
|
|
|
//We've arrived here because there is no valid setting for Misc expense
|
|
s2 = new SetQBChargeAs();
|
|
s2.DialogTitle = "AyaNova QBI setup - Charge Misc. Expense as?";
|
|
s2.OptionTitle = "Miscellaneous expenses";
|
|
s2.OptionDescription = "QBI needs to know what QuickBooks Item you want \r\n" +
|
|
"to use when invoicing the AyaNova \"Miscellaneous expense\" portion of a work order.\r\n\r\n" +
|
|
"This setting is mandatory / required.";
|
|
s2.QBItems = QBItems;
|
|
s2.SelectedQBItem = QDat.MiscExpenseChargeAs;
|
|
|
|
if (s2.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.MiscExpenseChargeAs = s2.SelectedQBItem;
|
|
s2.Dispose();
|
|
s2 = null;
|
|
|
|
MISCCHARGEASOK:
|
|
|
|
#endregion
|
|
|
|
#region Workorder item loan charge as
|
|
//Validate any existing
|
|
if (SetEverything == false && QDat.WorkOrderItemLoanChargeAs != null && QDat.WorkOrderItemLoanChargeAs != "")
|
|
{
|
|
goto LOANCHARGEASOK;
|
|
}
|
|
|
|
|
|
//We've arrived here because there is no valid setting for Misc expense
|
|
s2 = new SetQBChargeAs();
|
|
s2.DialogTitle = "AyaNova QBI setup - Charge loan item as?";
|
|
s2.OptionTitle = "Work order loans";
|
|
s2.OptionDescription = "QBI needs to know what QuickBooks Item you want \r\n" +
|
|
"to use when invoicing the AyaNova \"Workorder item loan\" portion of a work order.\r\n\r\n" +
|
|
"This setting is mandatory / required.";
|
|
s2.QBItems = QBItems;
|
|
s2.SelectedQBItem = QDat.WorkOrderItemLoanChargeAs;
|
|
if (s2.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.WorkOrderItemLoanChargeAs = s2.SelectedQBItem;
|
|
s2.Dispose();
|
|
s2 = null;
|
|
|
|
LOANCHARGEASOK:
|
|
|
|
#endregion
|
|
|
|
#region QB Transaction class
|
|
//Validate any existing
|
|
if (SetEverything == false && !string.IsNullOrWhiteSpace(QDat.TransactionClass))
|
|
{
|
|
//if something is set but there are no tr classes
|
|
//then just clear it and move along
|
|
if (QBClasses.Rows.Count == 1)
|
|
{
|
|
QDat.TransactionClass = TRANSACTION_CLASS_NO_CLASS_SELECTED;//case 3268
|
|
goto TRCLASSOK;
|
|
}
|
|
|
|
//Something is set and there *are* tr classes so
|
|
//let's validate it...
|
|
|
|
if (QBClasses.Rows.Contains(QDat.TransactionClass))
|
|
goto TRCLASSOK;
|
|
else
|
|
{
|
|
MessageBox.Show("The QuickBooks transaction class previously set for invoicing\r\n" +
|
|
"no longer appears to be valid. You will next be prompted to re-select it.");
|
|
}
|
|
}
|
|
|
|
//Perhaps there are no transaction classes, this is the default
|
|
//if not then don't prompt for it obviously :)
|
|
//also if it was empty and were not in first setup mode then
|
|
//don't bother prompting it might be the users choice.
|
|
|
|
|
|
//We've arrived here because there is no setting for transaction classes
|
|
//but there are some defined in QB
|
|
SetQBClass s3 = new SetQBClass();
|
|
s3.DialogTitle = "AyaNova QBI setup - Transaction class";
|
|
s3.OptionTitle = "Transaction class";
|
|
s3.OptionDescription = "QBI needs to know what QuickBooks Transaction Class you want \r\n" +
|
|
"to use when invoicing Work orders.\r\n\r\n" +
|
|
"If you do not use transaction classes or are not sure what they are\r\n" +
|
|
"select < Do not use classes> from the list below. Classes are off by default in QuickBooks.\r\n\r\n" +
|
|
"This setting is Optional and not required.";
|
|
s3.QBClasses = QBClasses;
|
|
|
|
if (QBClasses.Rows.Contains(QDat.TransactionClass))
|
|
s3.SelectedQBClass = QDat.TransactionClass;
|
|
else
|
|
s3.SelectedQBClass = TRANSACTION_CLASS_NO_CLASS_SELECTED;
|
|
|
|
if (s3.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.TransactionClass = s3.SelectedQBClass;
|
|
s3.Dispose();
|
|
s3 = null;
|
|
|
|
TRCLASSOK:
|
|
|
|
#endregion
|
|
|
|
#region QB InvoiceTemplate
|
|
//Templates are only supported in xml 3 or greater (all countries)
|
|
//if Set everything (first run) then display a dialog about it
|
|
if (QVersion < 3 && SetEverything == true)
|
|
{
|
|
|
|
SetInfoOnly s3a = new SetInfoOnly();
|
|
s3a.DialogTitle = "AyaNova QBI setup - Invoice template";
|
|
s3a.OptionTitle = "Invoice template - NOT SUPPORTED";
|
|
s3a.OptionDescription =
|
|
"QBI can use a specific QuickBooks Invoice template for printing work orders.\r\n" +
|
|
"However, your version of QuickBooks does not support integrating this \r\n" +
|
|
"feature with 3rd party applications such as AyaNova QBI.\r\n" +
|
|
|
|
"Supported versions of QuickBooks for using Invoice templates with QBI are:\r\n\r\n" +
|
|
"U.S., Canadian or U.K. QuickBooks 2004 or newer\r\n\r\n" +
|
|
|
|
"If you upgrade your QuickBooks in future you will be able to select this option\r\n" +
|
|
"for now it is disabled and the default invoice template will be used";
|
|
|
|
s3a.ShowDialog();
|
|
goto TRInvoiceTemplateOK;
|
|
}
|
|
|
|
//Subsequent, non-setup, runs with unsupported version
|
|
if (QVersion < 3)
|
|
goto TRInvoiceTemplateOK;
|
|
|
|
|
|
//Validate any existing
|
|
if (QDat.QBInvoiceTemplate != null && QDat.QBInvoiceTemplate != "")
|
|
{
|
|
//if something is set but there are no InvoiceTemplates
|
|
//then just clear it and move along
|
|
if (QBInvoiceTemplates.Rows.Count == 1)
|
|
{
|
|
QDat.QBInvoiceTemplate = "";
|
|
goto TRInvoiceTemplateOK;
|
|
}
|
|
|
|
//Something is set and there *are* tr InvoiceTemplates so
|
|
//let's validate it...
|
|
if (QBInvoiceTemplates.Rows.Contains(QDat.QBInvoiceTemplate))
|
|
{
|
|
if (!SetEverything)
|
|
goto TRInvoiceTemplateOK;
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("The QuickBooks Invoice Template previously set for invoicing\r\n" +
|
|
"no longer appears to be valid. You will next be prompted to re-select it.");
|
|
}
|
|
}
|
|
|
|
//Perhaps there are no InvoiceTemplates, this is the default
|
|
//if not then don't prompt for it obviously :)
|
|
//also if it was empty and were not in first setup mode then
|
|
//don't bother prompting it might be the users choice.
|
|
//todo: make something besides and empty string to indicate
|
|
//deliberately non selected items
|
|
if (QBInvoiceTemplates.Rows.Count == 1 || SetEverything == false)
|
|
goto TRInvoiceTemplateOK;
|
|
|
|
|
|
//We've arrived here because there is no setting for InvoiceTemplates
|
|
//Or the user want's to change it
|
|
//and there are some defined in QB
|
|
SetQBInvoiceTemplate s3b = new SetQBInvoiceTemplate();
|
|
s3b.DialogTitle = "AyaNova QBI setup - Invoice template";
|
|
s3b.OptionTitle = "Invoice template";
|
|
s3b.OptionDescription = "QBI needs to know what QuickBooks Invoice template you want \r\n" +
|
|
"QBI to set for invoices created from Work orders.\r\n\r\n" +
|
|
"QuickBooks Invoice templates are used in QuickBooks to specify different print formats\r\n" +
|
|
"for invoices. If you do not use Invoice templates or are not sure what they are\r\n" +
|
|
"select < Use default > from the list below.\r\n\r\n" +
|
|
"This setting is required.";
|
|
s3b.QBInvoiceTemplates = QBInvoiceTemplates;
|
|
s3b.SelectedQBInvoiceTemplate = QDat.QBInvoiceTemplate;
|
|
|
|
if (s3b.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.QBInvoiceTemplate = s3b.SelectedQBInvoiceTemplate;
|
|
s3b.Dispose();
|
|
s3b = null;
|
|
|
|
TRInvoiceTemplateOK:
|
|
|
|
|
|
#endregion
|
|
|
|
#region QB Terms
|
|
|
|
|
|
|
|
//Validate any existing
|
|
//case 3228 added extra condition set everything is false
|
|
if (SetEverything == false && !string.IsNullOrEmpty(QDat.TermsDefault))
|
|
{
|
|
//if something is set but there are no terms
|
|
//then just clear it and move along
|
|
if (QBTerms.Rows.Count == 1)
|
|
{
|
|
QDat.TermsDefault = "";
|
|
goto TermsOK;
|
|
}
|
|
|
|
//Something is set and there *are* terms so
|
|
//let's validate it...
|
|
if (QBTerms.Rows.Contains(QDat.TermsDefault))
|
|
{
|
|
if (!SetEverything)
|
|
goto TermsOK;
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("The QuickBooks default terms previously set for invoicing\r\n" +
|
|
"no longer appears to be valid. You will next be prompted to re-select it.");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//We've arrived here because there is no setting for Terms
|
|
//Or the user want's to change it
|
|
//and there are some defined in QB
|
|
SetQBTerms termsdialog = new SetQBTerms();
|
|
termsdialog.DialogTitle = "AyaNova QBI setup - Customer default invoice terms";
|
|
termsdialog.OptionTitle = "Default terms";
|
|
termsdialog.OptionDescription = "QBI needs to know what QuickBooks terms you want \r\n" +
|
|
"QBI to set for customers imported from AyaNova.\r\n\r\n" +
|
|
"When an invoice for a customer is created the selected terms will be applied.\r\n\r\n" +
|
|
"This setting is required.";
|
|
termsdialog.QBTerms = QBTerms;
|
|
termsdialog.SelectedQBTerm = QDat.TermsDefault;
|
|
|
|
if (termsdialog.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.TermsDefault = termsdialog.SelectedQBTerm;
|
|
termsdialog.Dispose();
|
|
termsdialog = null;
|
|
|
|
TermsOK:
|
|
|
|
|
|
#endregion
|
|
|
|
#region ToBePrinted
|
|
//No validation possible
|
|
//so prompt only if not setup yet
|
|
if (!SetEverything)
|
|
{
|
|
goto TBPOK;
|
|
}
|
|
|
|
SetToBePrinted s4 = new SetToBePrinted();
|
|
s4.DialogTitle = "AyaNova QBI setup - Set invoice to be printed?";
|
|
s4.OptionTitle = "Invoice to be printed";
|
|
s4.OptionDescription = "QBI needs to know if you want invoices that it creates \r\n" +
|
|
"in QuickBooks to be set to \"To be printed\".\r\n\r\n" +
|
|
"(Please note that \"To be emailed\" which is available in some\r\n" +
|
|
"versions of QuickBooks is not an option at this time as\r\n" +
|
|
"QuickBooks has not exposed that property to developers)";
|
|
s4.ToBePrinted = QDat.ToBePrinted;
|
|
|
|
if (s4.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.ToBePrinted = s4.ToBePrinted;
|
|
s4.Dispose();
|
|
s4 = null;
|
|
|
|
TBPOK:
|
|
|
|
#endregion
|
|
|
|
#region SetMemoField
|
|
//No validation possible
|
|
//so prompt only if not setup yet
|
|
if (!SetEverything)
|
|
{
|
|
goto SETMEMOOK;
|
|
}
|
|
|
|
SetMemoField s5 = new SetMemoField();
|
|
s5.DialogTitle = "AyaNova QBI setup - Set Memo field?";
|
|
s5.OptionTitle = "Invoice memo field";
|
|
s5.OptionDescription =
|
|
"QBI needs to know if you want invoices that it creates \r\n" +
|
|
"in QuickBooks to have their \"Memo\" field set with\r\n" +
|
|
"information about the work order(s) that were the basis for\r\n" +
|
|
"the invoice and the name of the AyaNova user who generated them.\r\n\r\n" +
|
|
"This may be useful as a back reference, this setting is optional";
|
|
s5.FillMemoField = QDat.SetMemoField;
|
|
|
|
if (s5.ShowDialog() == DialogResult.Cancel)
|
|
{
|
|
return pfstat.Cancel;
|
|
|
|
}
|
|
else
|
|
QDat.SetMemoField = s5.FillMemoField;
|
|
s5.Dispose();
|
|
s5 = null;
|
|
|
|
SETMEMOOK:
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
//#region SetAutoCloseField Case 7
|
|
////No validation possible
|
|
////so prompt only if not setup yet
|
|
//if (!SetEverything)
|
|
//{
|
|
// goto SETAUTOCLOSEOK;
|
|
|
|
//}
|
|
|
|
//SetAutoClose s6 = new SetAutoClose();
|
|
//s6.DialogTitle = "AyaNova QBI setup - Close when invoiced?";
|
|
//s6.OptionTitle = "Close work order after invoicing";
|
|
//s6.OptionDescription =
|
|
// "QBI needs to know if you want work orders that it invoices \r\n" +
|
|
// "automatically set to closed";
|
|
//s6.AutoClose = QDat.AutoClose;
|
|
|
|
//if (s6.ShowDialog() == DialogResult.Cancel)
|
|
//{
|
|
// return pfstat.Cancel;
|
|
|
|
//}
|
|
//else
|
|
// QDat.AutoClose = s6.AutoClose;
|
|
//s6.Dispose();
|
|
//s6 = null;
|
|
|
|
//SETAUTOCLOSEOK:
|
|
|
|
// #endregion
|
|
|
|
|
|
//Save if changes made
|
|
if (QDat.IsDirty)
|
|
{
|
|
//await IntegrationLog("ValidateSettings: QBI settings modified by user, saving now");
|
|
//QBIntegration.IntegrationData = Newtonsoft.Json.JsonConvert.SerializeObject(QDat);
|
|
//ApiResponse r = await PutAsync("integration", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration));
|
|
//QBIntegration.Concurrency = r.ObjectResponse["data"]["concurrency"].Value<uint>();
|
|
//////Case 299
|
|
////QBI.AIObject = QDat.XMLData;
|
|
//////QBI.AIObject=QDat;
|
|
|
|
////QBI = (Integration)QBI.Save();
|
|
//QDat.IsDirty = false;
|
|
await SaveIntegrationObject();
|
|
}
|
|
await IntegrationLog($"ValidateSettings: QBI main integration data that will be used for this session \"{QBIntegration.IntegrationData}\" ");
|
|
|
|
return pfstat.OK;
|
|
}
|
|
#endregion validate user settings
|
|
|
|
#region Integration object persistance
|
|
public static async Task SaveIntegrationObject()
|
|
{
|
|
//NOTE: this put route returns the entire integration object in order to update the concurrency tokens of the items collection
|
|
//ensures clean updates and current information
|
|
if (QDat.IsDirty)
|
|
{
|
|
await IntegrationLog("ValidateSettings: QBI settings modified by user, saving now");
|
|
QBIntegration.IntegrationData = Newtonsoft.Json.JsonConvert.SerializeObject(QDat);
|
|
}
|
|
ApiResponse r = await PutAsync("integration", Newtonsoft.Json.JsonConvert.SerializeObject(QBIntegration));
|
|
QBIntegration = r.ObjectResponse["data"].ToObject<Integration>();
|
|
QDat.IsDirty = false;
|
|
}
|
|
#endregion integration object persistance
|
|
|
|
#region PFC QB side
|
|
/// <summary>
|
|
/// Open QB connection
|
|
/// gather info required for future
|
|
/// transactions
|
|
/// </summary>
|
|
public static async Task<pfstat> 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
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sessionManager"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle null qb string types with "aplomb" :)
|
|
/// </summary>
|
|
/// <param name="qs"></param>
|
|
/// <returns></returns>
|
|
private static string ProcessQBString(IQBStringType qs)
|
|
{
|
|
if (qs == null) return "";
|
|
return qs.GetValue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
///
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// returns an empty string if passed in value is null or empty
|
|
/// else returns Prepend plus passed in string followed by append string
|
|
/// </summary>
|
|
/// <param name="sPrepend">Text to return to the left of sText</param>
|
|
/// <param name="sText">string to return if not null or empty</param>
|
|
/// <param name="sAppend">Text to return to the right of sText</param>
|
|
/// <returns></returns>
|
|
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
|
|
/// <summary>
|
|
/// Populate or repopulate the list of
|
|
/// </summary>
|
|
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 PopulateQBItemCacheAsync();
|
|
|
|
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;
|
|
|
|
/// <summary>
|
|
/// qb items
|
|
/// </summary>
|
|
public static DataTable QBItems
|
|
{
|
|
get
|
|
{
|
|
return _dtQBItems;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="QBItemID"></param>
|
|
/// <returns></returns>
|
|
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.ObjectId;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb data
|
|
/// billable
|
|
/// </summary>
|
|
internal static async Task PopulateQBItemCacheAsync()
|
|
{
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Given a qb BaseRef object
|
|
/// return the ListID or empty string if null
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
private static string BaseRefIDGrabber(IQBBaseRef b)
|
|
{
|
|
|
|
|
|
if (b != null && b.ListID != null)
|
|
{
|
|
return b.ListID.GetValue();
|
|
}
|
|
return "";
|
|
|
|
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a qb Pricetype object
|
|
/// return the price or zero on any issue
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
private static decimal PriceGrabber(IQBPriceType p)
|
|
{
|
|
decimal d = 0;//default
|
|
|
|
if (p != null)
|
|
{
|
|
d = System.Convert.ToDecimal(p.GetValue());
|
|
}
|
|
|
|
|
|
return d;
|
|
}
|
|
/// <summary>
|
|
/// Given a qb IQBQuanType object
|
|
/// return the value as decimal or zero on any issue
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
private static decimal QuanGrabber(IQBQuanType p)
|
|
{
|
|
decimal d = 0;//default
|
|
|
|
if (p != null)
|
|
{
|
|
d = System.Convert.ToDecimal(p.GetValue());
|
|
}
|
|
|
|
|
|
return d;
|
|
}
|
|
/// <summary>
|
|
/// Given a qb salespurchase object
|
|
/// return the price
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Given a qb salespurchase object
|
|
/// return the purchase cose
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// return the sales description if available
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// return the preferred vendor if available
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <returns></returns>
|
|
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 = "<GZNOCLASS>";
|
|
|
|
private static DataTable _dtQBClasses = null;
|
|
|
|
/// <summary>
|
|
/// QB Transaction Classes
|
|
/// </summary>
|
|
public static DataTable QBClasses
|
|
{
|
|
get
|
|
{
|
|
return _dtQBClasses;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb data
|
|
/// billable
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// QB Transaction Templates
|
|
/// </summary>
|
|
public static DataTable QBInvoiceTemplates
|
|
{
|
|
get
|
|
{
|
|
return _dtQBInvoiceTemplates;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb data
|
|
/// Invoice templates
|
|
/// </summary>
|
|
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<string> 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;
|
|
|
|
/// <summary>
|
|
/// QB Transaction Clients
|
|
/// </summary>
|
|
public static DataTable QBClients
|
|
{
|
|
get
|
|
{
|
|
return _dtQBClients;
|
|
}
|
|
}
|
|
|
|
#region Address structure
|
|
/// <summary>
|
|
/// Address properties
|
|
/// </summary>
|
|
public struct Address
|
|
{
|
|
|
|
public string DeliveryAddress;
|
|
public string City;
|
|
public string StateProv;
|
|
public string Country;
|
|
public string Postal;
|
|
}
|
|
|
|
#endregion
|
|
/// <summary>
|
|
/// Populate the cached qb data
|
|
/// of customers / clients
|
|
/// </summary>
|
|
internal 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Take a qb address and return an AyaNova friendly
|
|
/// address structure
|
|
/// </summary>
|
|
/// <param name="a"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// QB Vendors
|
|
/// </summary>
|
|
public static DataTable QBVendors
|
|
{
|
|
get
|
|
{
|
|
return _dtQBVendors;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb data
|
|
/// of Vendors
|
|
/// </summary>
|
|
internal 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;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static async Task<string> GetQBVendorEditSequenceAsync(string vendorid)
|
|
{
|
|
|
|
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
|
|
IVendorQuery cq = requestSet.AppendVendorQueryRq();
|
|
cq.IncludeRetElementList.Add("EditSequence");
|
|
cq.ORVendorListQuery.ListIDList.Add(vendorid);
|
|
|
|
|
|
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)
|
|
{
|
|
IVendorRetList cl = response.Detail as IVendorRetList;
|
|
if (!(cl.Count == 0))
|
|
{
|
|
IVendorRet 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("GetVendorEditSequence: Failed with exception:" + ex.Message);
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
throw;
|
|
|
|
}
|
|
|
|
return strEditSequence;
|
|
}
|
|
|
|
|
|
#endregion quickbooks Vendors
|
|
|
|
#region QuickBooks "accounts"
|
|
|
|
|
|
private static DataTable _dtQBAccounts = null;
|
|
|
|
/// <summary>
|
|
/// QB Transaction Classes
|
|
/// </summary>
|
|
public static DataTable QBAccounts
|
|
{
|
|
get
|
|
{
|
|
return _dtQBAccounts;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb account list data
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// QB Transaction Classes
|
|
/// </summary>
|
|
public static DataTable QBTerms
|
|
{
|
|
get
|
|
{
|
|
return _dtQBTerms;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Populate the cached qb terms list data
|
|
/// </summary>
|
|
private static async 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
|
|
|
|
#region Change QB Item price
|
|
|
|
public static async Task ChangeQBItemPrice(string QBListID, decimal NewPrice)
|
|
{
|
|
|
|
//Added: 18-Nov-2006 CASE 92
|
|
//check if inventory item as this code only handles price changing for inventory
|
|
//items in qb
|
|
qbitemtype qtype = (qbitemtype)QBItems.Rows.Find(QBListID)["Type"];
|
|
if (qtype != qbitemtype.Inventory)
|
|
{
|
|
MessageBox.Show("Only inventory items in QuickBooks can have their price changed\r\n" +
|
|
"The current item is of type: " + qtype.ToString());
|
|
return;
|
|
}
|
|
|
|
//----Get the edit sequence-----
|
|
string sEditSequence = await GetInventoryItemEditSequenceAsync(QBListID);
|
|
|
|
//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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//------change the price----
|
|
// IY: Add the request to the message set request object
|
|
IItemInventoryMod ItemQ = requestSet.AppendItemInventoryModRq();
|
|
|
|
ItemQ.ListID.SetValue(QBListID);
|
|
ItemQ.EditSequence.SetValue(sEditSequence);
|
|
ItemQ.SalesPrice.SetValue((double)NewPrice);
|
|
|
|
//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);
|
|
//-------------------------
|
|
|
|
|
|
|
|
//--------------
|
|
// IY: Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
QBItems.Rows.Find(QBListID)["Price"] = NewPrice;
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await IntegrationLog("ChangeQBItemPrice: Failed with exception:" + ex.Message);
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public static async Task<string> GetInventoryItemEditSequenceAsync(string QBListID)
|
|
{
|
|
//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;
|
|
|
|
//----Get the edit sequence-----
|
|
// IY: Add the request to the message set request object
|
|
IItemInventoryQuery iq = requestSet.AppendItemInventoryQueryRq();
|
|
|
|
//v8NOTE apparently this was changed in the api so if it fails need to dig in docs but it's a close match so suspect they just "improved" upon it with a breaking change
|
|
//iq.ORListQuery.ListIDList.Add(QBListID);
|
|
iq.ORListQueryWithOwnerIDAndClass.ListIDList.Add(QBListID);
|
|
|
|
|
|
|
|
|
|
//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);
|
|
//-------------------------
|
|
IResponse response = responseSet.ResponseList.GetAt(0);
|
|
|
|
//Added: 18-Nov-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);
|
|
|
|
}// v8 saw this with one record in testing update, assuming it's a glitch due to coding experimentation in the data
|
|
// StatusMessage "The query request has not been fully completed. There was a required element (\"190000-933272656\") that could not be found in QuickBooks." System.String
|
|
|
|
|
|
|
|
IItemInventoryRetList il = response.Detail as IItemInventoryRetList;
|
|
IItemInventoryRet i = il.GetAt(0);
|
|
string EditSequence = i.EditSequence.GetValue();
|
|
|
|
|
|
//--------------
|
|
// IY: Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
return EditSequence;
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await IntegrationLog("PopulateQBItems: Failed with exception:" + ex.Message);
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#endregion QB api helper methods end
|
|
|
|
#region AyaNova cached lists
|
|
public static async Task PopulateAyaListCache()
|
|
{
|
|
//Get the cached QB data
|
|
Waiting w = new Waiting();
|
|
w.Show();
|
|
w.Ops = "Reading from AyaNova...";
|
|
|
|
w.Step = "Translation text";
|
|
await PopulateAyaTranslationList();
|
|
|
|
w.Step = "Clients";
|
|
await PopulateAyaClientList();
|
|
|
|
w.Step = "Vendors";
|
|
await PopulateAyaVendorList();
|
|
|
|
w.Step = "Rates";
|
|
await PopulateAyaServiceRateList();
|
|
await PopulateAyaTravelRateList();
|
|
|
|
w.Step = "Parts";
|
|
await PopulateAyaPartList();
|
|
|
|
w.Step = "WorkOrder status list";
|
|
await PopulateAyaWOStatusList();
|
|
|
|
|
|
|
|
w.Close();
|
|
}
|
|
|
|
#region AyaNova clients
|
|
|
|
private static List<NameIdActiveItem> _clientlist = null;
|
|
/// <summary>
|
|
/// AyaNova ClientPickList
|
|
/// </summary>
|
|
public static List<NameIdActiveItem> AyaClientList
|
|
{
|
|
get
|
|
{
|
|
return _clientlist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaClientList()
|
|
{
|
|
var a = await GetAsync("customer/accounting-list");
|
|
_clientlist = a.ObjectResponse["data"].ToObject<List<NameIdActiveItem>>();
|
|
}
|
|
|
|
#endregion ayanova clients
|
|
|
|
#region AyaNova Vendors
|
|
|
|
private static List<NameIdActiveItem> _vendorlist = null;
|
|
/// <summary>
|
|
/// AyaNova vendor list
|
|
/// </summary>
|
|
public static List<NameIdActiveItem> AyaVendorList
|
|
{
|
|
get
|
|
{
|
|
return _vendorlist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaVendorList()
|
|
{
|
|
var a = await GetAsync("vendor/accounting-list");
|
|
_vendorlist = a.ObjectResponse["data"].ToObject<List<NameIdActiveItem>>();
|
|
}
|
|
|
|
#endregion ayanova vendors
|
|
|
|
#region AyaNova Rates
|
|
|
|
private static List<NameIdActiveChargeCostItem> _ServiceRatelist = null;
|
|
/// <summary>
|
|
/// AyaNova service rate list
|
|
/// </summary>
|
|
public static List<NameIdActiveChargeCostItem> AyaServiceRateList
|
|
{
|
|
get
|
|
{
|
|
return _ServiceRatelist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaServiceRateList()
|
|
{
|
|
var a = await GetAsync("service-rate/accounting-list");
|
|
_ServiceRatelist = a.ObjectResponse["data"].ToObject<List<NameIdActiveChargeCostItem>>();
|
|
}
|
|
|
|
|
|
private static List<NameIdActiveChargeCostItem> _TravelRatelist = null;
|
|
/// <summary>
|
|
/// AyaNova Travel rate list
|
|
/// </summary>
|
|
public static List<NameIdActiveChargeCostItem> AyaTravelRateList
|
|
{
|
|
get
|
|
{
|
|
return _TravelRatelist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaTravelRateList()
|
|
{
|
|
var a = await GetAsync("travel-rate/accounting-list");
|
|
_TravelRatelist = a.ObjectResponse["data"].ToObject<List<NameIdActiveChargeCostItem>>();
|
|
}
|
|
#endregion ayanova rates
|
|
|
|
#region AyaNova Parts
|
|
|
|
private static List<NameIdActiveChargeCostItem> _partlist = null;
|
|
/// <summary>
|
|
/// AyaNova part list
|
|
/// </summary>
|
|
public static List<NameIdActiveChargeCostItem> AyaPartList
|
|
{
|
|
get
|
|
{
|
|
return _partlist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaPartList()
|
|
{
|
|
var a = await GetAsync("part/accounting-list");
|
|
_partlist = a.ObjectResponse["data"].ToObject<List<NameIdActiveChargeCostItem>>();
|
|
}
|
|
|
|
#endregion ayanova parts
|
|
|
|
#region AyaNova WorkOrder STATUS list
|
|
|
|
private static List<WorkOrderStatus> _woStatuslist = null;
|
|
/// <summary>
|
|
/// AyaNova part list
|
|
/// </summary>
|
|
public static List<WorkOrderStatus> AyaWOStatusList
|
|
{
|
|
get
|
|
{
|
|
return _woStatuslist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaWOStatusList()
|
|
{
|
|
var a = await GetAsync("work-order-status/list");
|
|
_woStatuslist = a.ObjectResponse["data"]["all"].ToObject<List<WorkOrderStatus>>();
|
|
}
|
|
|
|
#endregion ayanova WorkOrder STATUS list
|
|
|
|
#region Translations
|
|
|
|
private static Dictionary<string, string> _translist = null;
|
|
/// <summary>
|
|
/// AyaNova part list
|
|
/// </summary>
|
|
public static Dictionary<string, string> AyaTranslations
|
|
{
|
|
get
|
|
{
|
|
return _translist;
|
|
}
|
|
}
|
|
|
|
public static async Task PopulateAyaTranslationList()
|
|
{
|
|
|
|
var a = await PostAsync("translation/subset", Newtonsoft.Json.JsonConvert.SerializeObject(new List<string> {
|
|
"WorkOrderStatus","OK", "Cancel"
|
|
}));
|
|
//this weirdness is required because it comes from the server as a json collection of objects with {Key:"blahkey", value:"blahvalue"} rather than what newtonsoft expects for a dictionary which is [{key:"blahvalue"},{key:"blahvalue2"}]
|
|
var v = a.ObjectResponse["data"].ToObject<List<KeyValuePair<string, string>>>();
|
|
_translist = v.ToDictionary((keyItem) => keyItem.Key, (valueItem) => valueItem.Value);
|
|
|
|
}
|
|
|
|
//await window.$gz.api.upsert("translation/subset", needIt);
|
|
#endregion translations
|
|
|
|
#endregion ayanova cached lists
|
|
|
|
#region Exception helper
|
|
|
|
public static string CrackException(Exception ex)
|
|
{
|
|
while (ex.InnerException != null)
|
|
{
|
|
ex = ex.InnerException;
|
|
}
|
|
return ex.Message + "\r\n-------TRACE------\r\n" + ex.StackTrace;
|
|
}
|
|
|
|
|
|
//case 3717
|
|
public static async Task CrackDisplayAndIntegrationLogException(Exception ex, string methodText = "n/a", string extraText = "n/a")
|
|
{
|
|
|
|
string message = "Exception error\r\nMethod: " + methodText + "\r\nExtra: " + extraText + "\r\n Error:" + CrackException(ex);
|
|
//Log it so we have it forever in the log
|
|
await IntegrationLog(message);
|
|
//display it to the user
|
|
CopyableMessageBox cb = new CopyableMessageBox(message);
|
|
cb.ShowDialog();
|
|
}
|
|
|
|
#endregion exception helper
|
|
|
|
#region Import / refresh to AyaNova
|
|
|
|
public static async Task RefreshAyaNovaCustomerFromQBAsync(IntegrationItem im)
|
|
{
|
|
if (im == null) return;//this object is not linked
|
|
var r = await GetAsync($"customer/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Customer>();
|
|
if (c == null) return;
|
|
|
|
DataRow dr = _dtQBClients.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in client list?
|
|
CopyQBCustomerInfoToAyaNovaClient(dr, c);
|
|
string sName = dr["FullName"].ToString();
|
|
c.Name = sName;
|
|
await PutAsync($"customer", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
im.LastSync = DateTime.Now;
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated customer
|
|
/// to an AyaNova client record
|
|
/// </summary>
|
|
/// <param name="QuickBooksID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportQBCustomer(string QuickBooksID, ArrayList alErrors)
|
|
{
|
|
|
|
DataRow dr = _dtQBClients.Rows.Find(QuickBooksID);
|
|
//QBListID not found in client list?
|
|
if (dr == null)
|
|
{
|
|
alErrors.Add("ImportQBCustomer: ID not found " + QuickBooksID);
|
|
return;
|
|
}
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
//if (sName.Length > 255)
|
|
//{
|
|
// alErrors.Add("ImportQBCustomer: QuickBooks customer name exceeds 255 character limit for AyaNova\r\n" +
|
|
// "Name: " + dr["FullName"].ToString() + "\r\n" +
|
|
// "was imported as: " + sName);
|
|
// sName = sName.Substring(0, 255);
|
|
//}
|
|
ApiResponse r = null;
|
|
try
|
|
{
|
|
//already a client by that name cached here? (qbi7 would check directly if existing but we'll let save handle that if there is a conflict for v8 and just check the obvious first)
|
|
if (QBIntegration.Items.Any(z => z.AType == AyaType.Customer && z.IntegrationItemName == dr["FullName"].ToString()))
|
|
{
|
|
alErrors.Add("ImportQBCustomer: " + dr["FullName"].ToString() + " already exists in AyaNova");
|
|
return;
|
|
}
|
|
|
|
//Import seems safe...
|
|
|
|
Customer c = new Customer();
|
|
c.Name = sName;
|
|
CopyQBCustomerInfoToAyaNovaClient(dr, c);
|
|
c.Active = true;
|
|
|
|
//Try to save and return errors if not in alerrors
|
|
r = await PostAsync($"customer", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
long newCustomerId = IdFromResponse(r);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Customer, IntegrationItemName = sName, IntegrationItemId = QuickBooksID, LastSync = DateTime.Now, ObjectId = newCustomerId };
|
|
QBIntegration.Items.Add(m);
|
|
await SaveIntegrationObject();
|
|
//IntegrationMap m = QBI.Maps.Add(QBI);
|
|
//m.Name = sName;
|
|
//m.RootObjectID = c.ID;
|
|
//m.RootObjectType = RootObjectTypes.Client;
|
|
//m.LastSync = DateTime.Now;
|
|
//m.ForeignID = QuickBooksID;
|
|
//QBI = (Integration)QBI.Save();
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
|
|
alErrors.Add("ImportQBCustomer: AyaNova won't allow import / link of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + CrackException(ex));
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public static void CopyQBCustomerInfoToAyaNovaClient(DataRow dr, Customer c)
|
|
{
|
|
Address a = (Address)dr["MailAddress"];
|
|
c.PostAddress = a.DeliveryAddress;//2
|
|
c.PostCity = a.City;//3
|
|
c.PostRegion = a.StateProv;//4
|
|
c.PostCountry = a.Country;//5
|
|
c.PostCode = a.Postal;//6
|
|
|
|
a = (Address)dr["StreetAddress"];
|
|
c.Address = a.DeliveryAddress;//7
|
|
c.City = a.City;//8
|
|
c.Region = a.StateProv;//9
|
|
c.Country = a.Country;//10
|
|
|
|
|
|
//Case 518
|
|
if (string.IsNullOrWhiteSpace(c.Address)) c.Address = c.PostAddress;
|
|
if (string.IsNullOrWhiteSpace(c.PostAddress)) c.PostAddress = c.Address;
|
|
|
|
if (string.IsNullOrWhiteSpace(c.City)) c.City = c.PostCity;
|
|
if (string.IsNullOrWhiteSpace(c.PostCity)) c.PostCity = c.City;
|
|
|
|
if (string.IsNullOrWhiteSpace(c.Region)) c.Region = c.PostRegion;
|
|
if (string.IsNullOrWhiteSpace(c.PostRegion)) c.PostRegion = c.Region;
|
|
|
|
if (string.IsNullOrWhiteSpace(c.Country)) c.Country = c.PostCountry;
|
|
if (string.IsNullOrWhiteSpace(c.PostCountry)) c.PostCountry = c.Country;
|
|
|
|
|
|
//Contact cn=c.Contacts.Add(RootObjectTypes.Client,c.ID);
|
|
|
|
c.Notes = $"QB Contact - {dr["Contact"]}";
|
|
|
|
|
|
//Phone field
|
|
if (dr["Phone"].ToString() != "")
|
|
{
|
|
c.Phone1 = dr["Phone"].ToString();
|
|
}
|
|
|
|
//Fax field
|
|
if (dr["Fax"].ToString() != "")
|
|
{
|
|
c.Phone2 = dr["Fax"].ToString();//15
|
|
}
|
|
|
|
//AltPhone field
|
|
if (dr["AltPhone"].ToString() != "")
|
|
{
|
|
c.Phone3 = dr["AltPhone"].ToString();//16
|
|
}
|
|
|
|
//Email field
|
|
if (dr["Email"].ToString() != "")
|
|
{
|
|
c.EmailAddress = dr["Email"].ToString();//17
|
|
}
|
|
|
|
//Account number field
|
|
if (dr["Account"].ToString() != "")
|
|
{
|
|
c.AccountNumber = dr["Account"].ToString();//18
|
|
}
|
|
}
|
|
|
|
|
|
public static async Task RefreshAyaNovaVendorFromQBAsync(IntegrationItem im)
|
|
{
|
|
if (im == null) return;//this object is not linked
|
|
var r = await GetAsync($"vendor/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Vendor>();
|
|
if (c == null) return;
|
|
|
|
DataRow dr = _dtQBClients.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in client list?
|
|
|
|
//CopyQBVendorInfoToAyaNovaVendor(dr, c);
|
|
Address a = (Address)dr["MailAddress"];
|
|
c.PostAddress = a.DeliveryAddress;
|
|
c.PostCity = a.City;
|
|
c.PostRegion = a.StateProv;
|
|
c.PostCountry = a.Country;
|
|
c.PostCode = a.Postal;
|
|
|
|
a = (Address)dr["StreetAddress"];
|
|
c.Address = a.DeliveryAddress;
|
|
c.City = a.City;
|
|
c.Region = a.StateProv;
|
|
c.Country = a.Country;
|
|
|
|
c.Contact = dr["Contact"].ToString();
|
|
if (dr["Phone"].ToString() != "")
|
|
c.Phone1 = dr["Phone"].ToString();
|
|
|
|
if (dr["Fax"].ToString() != "")
|
|
c.Phone2 = dr["Fax"].ToString();
|
|
|
|
if (dr["AltPhone"].ToString() != "")
|
|
c.Phone3 = dr["AltPhone"].ToString();
|
|
|
|
if (dr["Email"].ToString() != "")
|
|
c.EmailAddress = dr["Email"].ToString();
|
|
|
|
if (dr["Account"].ToString() != "")
|
|
c.AccountNumber = dr["Account"].ToString();
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
c.Name = sName;
|
|
await PutAsync($"vendor", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
im.LastSync = DateTime.Now;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import the indicated Vendor
|
|
/// to an AyaNova vendor record
|
|
/// </summary>
|
|
/// <param name="QuickBooksID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportQBVendor(string QuickBooksID, ArrayList alErrors)
|
|
{
|
|
|
|
DataRow dr = _dtQBVendors.Rows.Find(QuickBooksID);
|
|
//QBListID not found in Vendor list?
|
|
if (dr == null)
|
|
{
|
|
alErrors.Add("ImportQBVendor: ID not found " + QuickBooksID);
|
|
return;
|
|
}
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
//if (sName.Length > 255)
|
|
//{
|
|
// alErrors.Add("ImportQBVendor: QuickBooks Vendor name exceeds 255 character limit for AyaNova\r\n" +
|
|
// "Name: " + dr["FullName"].ToString() + "\r\n" +
|
|
// "was imported as: " + sName);
|
|
// sName = sName.Substring(0, 255);
|
|
//}
|
|
try
|
|
{
|
|
//already a Vendor by that name
|
|
// if (Vendor.Exists(Guid.Empty, dr["FullName"].ToString()))
|
|
if (QBIntegration.Items.Any(z => z.AType == AyaType.Vendor && z.IntegrationItemName == dr["FullName"].ToString()))
|
|
{
|
|
alErrors.Add("ImportQBVendor: " + dr["FullName"].ToString() + " already exists in AyaNova");
|
|
return;
|
|
}
|
|
|
|
//Import seems safe...
|
|
|
|
Vendor c = new Vendor();
|
|
c.Name = sName;
|
|
c.Active = true;
|
|
|
|
|
|
Address a = (Address)dr["MailAddress"];
|
|
c.PostAddress = a.DeliveryAddress;
|
|
c.PostCity = a.City;
|
|
c.PostRegion = a.StateProv;
|
|
c.PostCountry = a.Country;
|
|
c.PostCode = a.Postal;
|
|
|
|
a = (Address)dr["StreetAddress"];
|
|
c.Address = a.DeliveryAddress;
|
|
c.City = a.City;
|
|
c.Region = a.StateProv;
|
|
c.Country = a.Country;
|
|
|
|
c.Contact = dr["Contact"].ToString();
|
|
|
|
if (dr["Phone"].ToString() != "")
|
|
c.Phone1 = dr["Phone"].ToString();
|
|
|
|
if (dr["Fax"].ToString() != "")
|
|
c.Phone2 = dr["Fax"].ToString();
|
|
|
|
if (dr["AltPhone"].ToString() != "")
|
|
c.Phone3 = dr["AltPhone"].ToString();
|
|
|
|
if (dr["Email"].ToString() != "")
|
|
c.EmailAddress = dr["Email"].ToString();
|
|
|
|
if (dr["Account"].ToString() != "")
|
|
c.AccountNumber = dr["Account"].ToString();
|
|
|
|
|
|
//Try to save and return errors if not in alerrors
|
|
var r = await PostAsync($"vendor", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
long newId = IdFromResponse(r);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Vendor, IntegrationItemName = sName, IntegrationItemId = QuickBooksID, LastSync = DateTime.Now, ObjectId = newId };
|
|
QBIntegration.Items.Add(m);
|
|
await util.SaveIntegrationObject();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportQBVendor: AyaNova won't allow import / link of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated QB Item
|
|
/// to an AyaNova Rate record
|
|
/// </summary>
|
|
/// <param name="QuickBooksID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportQBServiceRate(string QuickBooksID, ArrayList alErrors)
|
|
{
|
|
|
|
DataRow dr = _dtQBItems.Rows.Find(QuickBooksID);
|
|
//QBListID not found in Rate list?
|
|
if (dr == null)
|
|
{
|
|
alErrors.Add("ImportQBRate: ID not found " + QuickBooksID);
|
|
return;
|
|
}
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
try
|
|
{
|
|
//already a Rate by that name
|
|
//if (Rate.Exists(Guid.Empty, dr["FullName"].ToString()))
|
|
if (QBIntegration.Items.Any(z => z.AType == AyaType.ServiceRate && z.IntegrationItemName == dr["FullName"].ToString()))
|
|
{
|
|
alErrors.Add("ImportQBServiceRate: " + dr["FullName"].ToString() + " already exists in AyaNova");
|
|
return;
|
|
}
|
|
|
|
//Import seems safe...
|
|
//Rates rates = Rates.GetItems(false);
|
|
//Rate c = rates.Add();
|
|
//c.RateType = AsRateType;
|
|
ServiceRate c = new ServiceRate();
|
|
|
|
c.Name = sName;
|
|
c.Charge = (decimal)dr["Price"];
|
|
//c.ContractRate = false;
|
|
c.Cost = (decimal)dr["Cost"];
|
|
c.Notes = T(255, dr["SalesDesc"].ToString());
|
|
c.Active = true;
|
|
// c.RateUnitChargeDescriptionID = MostLikelyRateUnitChargeDescriptionID;
|
|
|
|
|
|
|
|
//if (!c.IsSavable)
|
|
//{
|
|
// alErrors.Add("ImportQBRate: AyaNova won't allow import of " + c.Name + "\r\n" +
|
|
// "Due to the following broken rules:\r\n" + c.GetBrokenRulesString());
|
|
// return;
|
|
//}
|
|
|
|
|
|
//rates = (Rates)rates.Save();
|
|
////Link
|
|
//IntegrationMap m = QBI.Maps.Add(QBI);
|
|
//m.Name = sName;
|
|
//m.RootObjectID = c.ID;
|
|
//m.RootObjectType = RootObjectTypes.Rate;
|
|
//m.LastSync = DateTime.Now;
|
|
//m.ForeignID = QuickBooksID;
|
|
//QBI = (Integration)QBI.Save();
|
|
var r = await PostAsync($"service-rate", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
long newId = IdFromResponse(r);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.ServiceRate, IntegrationItemName = sName, IntegrationItemId = QuickBooksID, LastSync = DateTime.Now, ObjectId = newId };
|
|
QBIntegration.Items.Add(m);
|
|
await util.SaveIntegrationObject();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportQBServiceRate: AyaNova won't allow import / link of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated QB Item
|
|
/// to an AyaNova TravelRate record
|
|
/// </summary>
|
|
/// <param name="QuickBooksID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportQBTravelRate(string QuickBooksID, ArrayList alErrors)
|
|
{
|
|
|
|
DataRow dr = _dtQBItems.Rows.Find(QuickBooksID);
|
|
//QBListID not found in Rate list?
|
|
if (dr == null)
|
|
{
|
|
alErrors.Add("ImportQBRate: ID not found " + QuickBooksID);
|
|
return;
|
|
}
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
|
|
try
|
|
{
|
|
//already a Rate by that name
|
|
//if (Rate.Exists(Guid.Empty, dr["FullName"].ToString()))
|
|
if (QBIntegration.Items.Any(z => z.AType == AyaType.TravelRate && z.IntegrationItemName == dr["FullName"].ToString()))
|
|
{
|
|
alErrors.Add("ImportQBTravelRate: " + dr["FullName"].ToString() + " already exists in AyaNova");
|
|
return;
|
|
}
|
|
|
|
//Import seems safe...
|
|
|
|
TravelRate c = new TravelRate();
|
|
c.Name = sName;
|
|
c.Charge = (decimal)dr["Price"];
|
|
//c.ContractRate = false;
|
|
c.Cost = (decimal)dr["Cost"];
|
|
c.Notes = T(255, dr["SalesDesc"].ToString());
|
|
c.Active = true;
|
|
|
|
var r = await PostAsync($"travel-rate", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
long newId = IdFromResponse(r);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.TravelRate, IntegrationItemName = sName, IntegrationItemId = QuickBooksID, LastSync = DateTime.Now, ObjectId = newId };
|
|
QBIntegration.Items.Add(m);
|
|
await util.SaveIntegrationObject();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportQBTravelRate: AyaNova won't allow import / link of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
public static async Task RefreshAyaNovaPartFromQBAsync(IntegrationItem im, bool pricesOnly = false)
|
|
{
|
|
if (im == null) return;//this part is not linked
|
|
var r = await GetAsync($"part/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Part>();
|
|
if (c == null) return;
|
|
|
|
DataRow dr = _dtQBItems.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in part list?
|
|
|
|
if (!pricesOnly)
|
|
{
|
|
string sName = dr["FullName"].ToString();
|
|
c.Name = sName;
|
|
c.Description = T(255, dr["SalesDesc"].ToString());
|
|
c.WholeSalerId = AyaVendorForQBItem(im.IntegrationItemId);
|
|
}
|
|
c.Retail = (decimal)dr["Price"];
|
|
c.Cost = (decimal)dr["Cost"];
|
|
await PutAsync($"part", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
im.LastSync = DateTime.Now;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated QB Item
|
|
/// to an AyaNova Part record
|
|
/// </summary>
|
|
/// <param name="QuickBooksID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportQBPart(string QuickBooksID, ArrayList alErrors)
|
|
{
|
|
|
|
DataRow dr = _dtQBItems.Rows.Find(QuickBooksID);
|
|
//QBListID not found in Part list?
|
|
if (dr == null)
|
|
{
|
|
alErrors.Add("ImportQBPart: ID not found " + QuickBooksID);
|
|
return;
|
|
}
|
|
|
|
string sName = dr["FullName"].ToString();
|
|
try
|
|
{
|
|
//already a Part by that number?
|
|
if (QBIntegration.Items.Any(z => z.AType == AyaType.Part && z.IntegrationItemName == dr["FullName"].ToString()))
|
|
{
|
|
alErrors.Add("ImportQBPart: Part name " + dr["FullName"].ToString() + " already exists in AyaNova");
|
|
return;
|
|
}
|
|
|
|
//Import seems safe...
|
|
Part c = new Part();
|
|
|
|
|
|
c.Name = sName;
|
|
c.Description = T(255, dr["SalesDesc"].ToString());
|
|
c.WholeSalerId = AyaVendorForQBItem(QuickBooksID);
|
|
c.Retail = (decimal)dr["Price"];
|
|
c.Cost = (decimal)dr["Cost"];
|
|
c.Active = true;
|
|
|
|
//Try to save and return errors if not in alerrors
|
|
var r = await PostAsync($"part", Newtonsoft.Json.JsonConvert.SerializeObject(c));
|
|
long newId = IdFromResponse(r);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Part, IntegrationItemName = sName, IntegrationItemId = QuickBooksID, LastSync = DateTime.Now, ObjectId = newId };
|
|
QBIntegration.Items.Add(m);
|
|
await SaveIntegrationObject();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportQBPart: AyaNova won't allow import / link of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#endregion Import to AyaNova
|
|
|
|
#region Export / refresh to QuickBooks
|
|
|
|
|
|
/// <summary>
|
|
/// Refresh the indicated AyaNova client
|
|
/// to it's linked QuickBooks customer record
|
|
/// </summary>
|
|
public static async Task RefreshQBCustomerFromAyaNovaAsync(IntegrationItem im)
|
|
{
|
|
if (im == null) return;//this object is not linked
|
|
var r = await GetAsync($"customer/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Customer>();
|
|
if (c == null) return;
|
|
|
|
|
|
DataRow dr = _dtQBClients.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in client list?
|
|
|
|
string strEditSequence = await GetQBCustomerEditSequenceAsync(im.IntegrationItemId);
|
|
if (string.IsNullOrEmpty(strEditSequence))
|
|
{
|
|
MessageBox.Show("RefreshQBClientFromAyaNova -> Error: unable to fetch edit sequence from QuickBooks. No changes made.");
|
|
return;
|
|
}
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 41)
|
|
sName = sName.Substring(0, 41);
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
|
|
ICustomerMod ca = requestSet.AppendCustomerModRq();
|
|
|
|
//Set field value for ListID
|
|
ca.ListID.SetValue(im.IntegrationItemId);
|
|
// ca.ParentRef.ListID.SetValue(im.ForeignID);
|
|
ca.EditSequence.SetValue(strEditSequence);
|
|
|
|
dr["FullName"] = sName;
|
|
dr["MailAddress"] = new Address();
|
|
dr["StreetAddress"] = new Address();
|
|
dr["Phone"] = "";
|
|
dr["Fax"] = "";
|
|
dr["AltPhone"] = "";
|
|
dr["Email"] = "";
|
|
dr["Contact"] = "";
|
|
//dr["Created"] = DateTime.MinValue;//flag indicating fresh record incomplete
|
|
dr["Modified"] = DateTime.Now;//ditto
|
|
dr["Account"] = "";
|
|
|
|
ca.Name.SetValue(sName);
|
|
//ca.FirstName.SetValue(T(25,Primary.FirstName));
|
|
//Case 686
|
|
//V8NOTSUPPORTED ca.Contact.SetValue(T(41, c.Contact));
|
|
//ca.LastName.SetValue(T(25, c.Contact));
|
|
|
|
if (c.Phone1 != null)
|
|
ca.Phone.SetValue(T(21, c.Phone1));
|
|
else
|
|
ca.Phone.SetValue("");
|
|
ca.Fax.SetValue(T(21, c.Phone2));
|
|
ca.AltPhone.SetValue(T(21, c.Phone3));
|
|
ca.Email.SetValue(T(99, c.EmailAddress));
|
|
|
|
|
|
|
|
bool bHasBillAddress = false;
|
|
|
|
//Mailing address
|
|
//Changed:4-Sept-2006 client name is now addr1
|
|
//rest shuffle down a spot
|
|
//Also changed max length of address lines from 21 characters to 41
|
|
//as that is what is documented, not sure why they were set to 21 here before
|
|
ca.BillAddress.Addr1.SetValue(T(41, c.Name));
|
|
|
|
if (c.PostAddress != "")
|
|
{
|
|
bHasBillAddress = true;
|
|
string[] sad = c.PostAddress.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.BillAddress.Addr2.SetValue(T(41, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.BillAddress.Addr3.SetValue(T(41, sad[1].TrimEnd()));
|
|
|
|
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.BillAddress.Addr4.SetValue(T(41, sad[2].TrimEnd()));
|
|
|
|
}
|
|
ca.BillAddress.City.SetValue(T(31, c.PostCity));
|
|
ca.BillAddress.Country.SetValue(T(31, c.PostCountry));
|
|
ca.BillAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
|
|
//QBFC7 - unifies county and province to "State"
|
|
ca.BillAddress.State.SetValue(T(21, c.PostRegion));
|
|
|
|
bool bHasShipAddress = false;
|
|
//Delivery address
|
|
//Changed:4-Sept-2006 client name is now addr1
|
|
//rest shuffle down a spot
|
|
//Also changed max length of address lines from 21 characters to 41
|
|
//as that is what is documented, not sure why they were set to 21 here before
|
|
ca.ShipAddress.Addr1.SetValue(T(41, c.Name));
|
|
|
|
if (c.Address != "")
|
|
{
|
|
bHasShipAddress = true;
|
|
string[] sad = c.Address.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.ShipAddress.Addr2.SetValue(T(41, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.ShipAddress.Addr3.SetValue(T(41, sad[1].TrimEnd()));
|
|
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.ShipAddress.Addr4.SetValue(T(41, sad[2].TrimEnd()));
|
|
|
|
}
|
|
|
|
|
|
ca.ShipAddress.City.SetValue(T(31, c.City));
|
|
ca.ShipAddress.Country.SetValue(T(31, c.Country));
|
|
ca.ShipAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
|
|
//QBFC7 - unifies county and province to "State"
|
|
ca.ShipAddress.State.SetValue(T(21, c.Region));
|
|
|
|
|
|
//Added: 18-Nov-2006 CASE 95
|
|
//ensure that if only one address in ayanova that both types in QB get it
|
|
if (!bHasShipAddress || !bHasBillAddress)
|
|
{
|
|
if (bHasShipAddress && !bHasBillAddress)
|
|
{
|
|
//copy shipping address to billing address
|
|
CopyAddress(ca.ShipAddress, ca.BillAddress);
|
|
|
|
}
|
|
else if (bHasBillAddress && !bHasShipAddress)
|
|
{
|
|
CopyAddress(ca.BillAddress, ca.ShipAddress);
|
|
}
|
|
}
|
|
|
|
|
|
ca.AccountNumber.SetValue(T(99, c.AccountNumber));
|
|
|
|
//case 519
|
|
ca.TermsRef.ListID.SetValue(QDat.TermsDefault);
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
ICustomerRet cr = response.Detail as ICustomerRet;
|
|
string sNewCustID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
|
|
|
|
//Link
|
|
im.IntegrationItemName = sName;
|
|
im.LastSync = DateTime.Now;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
MessageBox.Show("RefreshQBClientFromAyaNova: QuickBooks won't allow refresh of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import the indicated client
|
|
/// to QuickBooks customer record
|
|
/// </summary>
|
|
/// <param name="CustomerId"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportAyaCustomer(long CustomerId, ArrayList alErrors)
|
|
{
|
|
|
|
if (!AyaClientList.Any(z => z.Id == CustomerId))
|
|
{
|
|
alErrors.Add("ImportAyaClient: Client not found in AyaNova (deleted recently?) (" + CustomerId.ToString() + ")");
|
|
return;
|
|
|
|
}
|
|
var r = await GetAsync($"customer/{CustomerId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Customer>();
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 41)
|
|
{
|
|
sName = sName.Substring(0, 41);
|
|
alErrors.Add("ImportAyaClient: AyaNova client name exceeds 41 character limit for QuickBooks\r\n" +
|
|
"Name: " + c.Name + "\r\n" +
|
|
"will be imported as: " + sName);
|
|
|
|
}
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
ICustomerAdd ca = requestSet.AppendCustomerAddRq();
|
|
|
|
//create a new row to add to the client cache datatable
|
|
DataRow dr = _dtQBClients.NewRow();
|
|
|
|
|
|
dr["FullName"] = sName;
|
|
dr["MailAddress"] = new Address();
|
|
dr["StreetAddress"] = new Address();
|
|
dr["Phone"] = "";
|
|
dr["Fax"] = "";
|
|
dr["AltPhone"] = "";
|
|
dr["Email"] = "";
|
|
dr["Contact"] = "";
|
|
dr["Created"] = DateTime.MinValue;//flag indicating fresh record incomplete
|
|
dr["Modified"] = DateTime.MinValue;//ditto
|
|
dr["Account"] = "";
|
|
|
|
ca.Name.SetValue(sName);
|
|
|
|
//AyaNova 8 doesn't have a single "contact" field so it can't be imported
|
|
// ca.Contact.SetValue(T(41, c.Contact));
|
|
|
|
|
|
if (c.Phone1 != null)
|
|
ca.Phone.SetValue(T(21, c.Phone1));
|
|
else
|
|
ca.Phone.SetValue("");
|
|
ca.Fax.SetValue(T(21, c.Phone2));
|
|
ca.AltPhone.SetValue(T(21, c.Phone3));
|
|
ca.Email.SetValue(T(99, c.EmailAddress));
|
|
|
|
|
|
|
|
bool bHasBillAddress = false;
|
|
|
|
//Mailing address
|
|
//Changed:4-Sept-2006 client name is now addr1
|
|
//rest shuffle down a spot
|
|
//Also changed max length of address lines from 21 characters to 41
|
|
//as that is what is documented, not sure why they were set to 21 here before
|
|
ca.BillAddress.Addr1.SetValue(T(41, c.Name));
|
|
|
|
if (!string.IsNullOrWhiteSpace(c.PostAddress))
|
|
{
|
|
bHasBillAddress = true;
|
|
string[] sad = c.PostAddress.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.BillAddress.Addr2.SetValue(T(41, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.BillAddress.Addr3.SetValue(T(41, sad[1].TrimEnd()));
|
|
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.BillAddress.Addr4.SetValue(T(41, sad[2].TrimEnd()));
|
|
|
|
}
|
|
ca.BillAddress.City.SetValue(T(31, c.PostCity));
|
|
ca.BillAddress.Country.SetValue(T(31, c.PostCountry));
|
|
ca.BillAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
ca.BillAddress.State.SetValue(T(21, c.PostRegion));
|
|
|
|
bool bHasShipAddress = false;
|
|
//Delivery address
|
|
//Changed:4-Sept-2006 client name is now addr1
|
|
//rest shuffle down a spot
|
|
//Also changed max length of address lines from 21 characters to 41
|
|
//as that is what is documented, not sure why they were set to 21 here before
|
|
ca.ShipAddress.Addr1.SetValue(T(41, c.Name));
|
|
|
|
if (!string.IsNullOrWhiteSpace(c.Address))
|
|
{
|
|
bHasShipAddress = true;
|
|
string[] sad = c.Address.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.ShipAddress.Addr2.SetValue(T(41, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.ShipAddress.Addr3.SetValue(T(41, sad[1].TrimEnd()));
|
|
|
|
//if(sad.GetLength(0)>2)
|
|
// ca.ShipAddress.Addr3.SetValue(T(21,sad[2].TrimEnd()));
|
|
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.ShipAddress.Addr4.SetValue(T(41, sad[2].TrimEnd()));
|
|
|
|
}
|
|
|
|
|
|
ca.ShipAddress.City.SetValue(T(31, c.City));
|
|
ca.ShipAddress.Country.SetValue(T(31, c.Country));
|
|
ca.ShipAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
ca.ShipAddress.State.SetValue(T(21, c.Region));
|
|
|
|
//Added: 18-Nov-2006 CASE 95
|
|
//ensure that if only one address in ayanova that both types in QB get it
|
|
if (!bHasShipAddress || !bHasBillAddress)
|
|
{
|
|
if (bHasShipAddress && !bHasBillAddress)
|
|
{
|
|
//copy shipping address to billing address
|
|
CopyAddress(ca.ShipAddress, ca.BillAddress);
|
|
|
|
}
|
|
else if (bHasBillAddress && !bHasShipAddress)
|
|
{
|
|
CopyAddress(ca.BillAddress, ca.ShipAddress);
|
|
}
|
|
}
|
|
|
|
|
|
ca.AccountNumber.SetValue(T(99, c.AccountNumber));
|
|
|
|
//case 519
|
|
ca.TermsRef.ListID.SetValue(QDat.TermsDefault);
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
ICustomerRet cr = response.Detail as ICustomerRet;
|
|
string sNewCustID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
//catch the new ID for the QB Item
|
|
dr["ID"] = sNewCustID;
|
|
//add the new row for the newly imported object
|
|
_dtQBClients.Rows.Add(dr);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Customer, IntegrationItemName = sName, IntegrationItemId = sNewCustID, LastSync = DateTime.Now, ObjectId = c.Id };
|
|
QBIntegration.Items.Add(m);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportAyaClient: QuickBooks won't allow import of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Copy the contents of one address to the other
|
|
/// </summary>
|
|
/// <param name="from"></param>
|
|
/// <param name="to"></param>
|
|
private static void CopyAddress(IAddress from, IAddress to)
|
|
{
|
|
if (from.Addr1.IsSet())
|
|
to.Addr1.SetValue(from.Addr1.GetValue());
|
|
|
|
if (from.Addr2.IsSet())
|
|
to.Addr2.SetValue(from.Addr2.GetValue());
|
|
|
|
if (from.Addr3.IsSet())
|
|
to.Addr3.SetValue(from.Addr3.GetValue());
|
|
|
|
if (QVersion > 1.1)//4th address line is 2 or newer
|
|
{
|
|
if (from.Addr4.IsSet())
|
|
to.Addr4.SetValue(from.Addr4.GetValue());
|
|
}
|
|
|
|
if (from.City.IsSet())
|
|
to.City.SetValue(from.City.GetValue());
|
|
|
|
if (from.Country.IsSet())
|
|
to.Country.SetValue(from.Country.GetValue());
|
|
|
|
if (from.PostalCode.IsSet())
|
|
to.PostalCode.SetValue(from.PostalCode.GetValue());
|
|
|
|
//QBFC7 - unifies county and province to "State"
|
|
if (from.State.IsSet())
|
|
to.State.SetValue(from.State.GetValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//############ NEW VENDOR REFRESH, copied and altered from csutomer refresh so any issues may be related to that
|
|
/// <summary>
|
|
///Update QuickBooks Vendor record from AyaNova
|
|
/// </summary>
|
|
public static async Task RefreshQBVendorFromAyaNovaAsync(IntegrationItem im)
|
|
{
|
|
if (im == null) return;//this object is not linked
|
|
var r = await GetAsync($"vendor/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Vendor>();
|
|
if (c == null) return;
|
|
|
|
|
|
DataRow dr = _dtQBVendors.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in client list?
|
|
|
|
string strEditSequence = await GetQBVendorEditSequenceAsync(im.IntegrationItemId);
|
|
if (string.IsNullOrEmpty(strEditSequence))
|
|
{
|
|
MessageBox.Show("RefreshQBVendorFromAyaNova -> Error: unable to fetch edit sequence from QuickBooks. No changes made.");
|
|
return;
|
|
}
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 41)
|
|
sName = sName.Substring(0, 41);
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
|
|
IVendorMod ca = requestSet.AppendVendorModRq();
|
|
|
|
//Set field value for ListID
|
|
ca.ListID.SetValue(im.IntegrationItemId);
|
|
// ca.ParentRef.ListID.SetValue(im.ForeignID);
|
|
ca.EditSequence.SetValue(strEditSequence);
|
|
|
|
dr["FullName"] = sName;
|
|
dr["MailAddress"] = new Address();
|
|
dr["StreetAddress"] = new Address();
|
|
dr["Phone"] = "";
|
|
dr["Fax"] = "";
|
|
dr["AltPhone"] = "";
|
|
dr["Email"] = "";
|
|
dr["Contact"] = "";
|
|
//dr["Created"] = DateTime.MinValue;//flag indicating fresh record incomplete
|
|
dr["Modified"] = DateTime.Now;//ditto
|
|
dr["Account"] = "";
|
|
|
|
ca.Name.SetValue(sName);
|
|
ca.Contact.SetValue(T(41, c.Contact));
|
|
//ca.LastName.SetValue(T(25, c.Contact));
|
|
|
|
if (c.Phone1 != null)
|
|
ca.Phone.SetValue(T(21, c.Phone1));
|
|
else
|
|
ca.Phone.SetValue("");
|
|
ca.Fax.SetValue(T(21, c.Phone2));
|
|
ca.AltPhone.SetValue(T(21, c.Phone3));
|
|
ca.Email.SetValue(T(99, c.EmailAddress));
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(c.PostAddress))
|
|
{
|
|
string[] sad = c.PostAddress.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.VendorAddress.Addr1.SetValue(T(21, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.VendorAddress.Addr2.SetValue(T(21, sad[1].TrimEnd()));
|
|
if (sad.GetLength(0) > 2)
|
|
ca.VendorAddress.Addr3.SetValue(T(21, sad[2].TrimEnd()));
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.VendorAddress.Addr4.SetValue(T(21, sad[3].TrimEnd()));
|
|
|
|
}
|
|
ca.VendorAddress.City.SetValue(T(31, c.PostCity));
|
|
ca.VendorAddress.Country.SetValue(T(31, c.PostCountry));
|
|
ca.VendorAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
ca.VendorAddress.State.SetValue(T(21, c.PostRegion));
|
|
|
|
ca.AccountNumber.SetValue(T(99, c.AccountNumber));
|
|
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
IVendorRet cr = response.Detail as IVendorRet;
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
|
|
|
|
//Link
|
|
im.IntegrationItemName = sName;
|
|
im.LastSync = DateTime.Now;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
MessageBox.Show("RefreshQBVendorFromAyaNova: QuickBooks won't allow refresh of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
//###### new vendor refresh
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated Vendor
|
|
/// to QuickBooks Vendor record
|
|
/// </summary>
|
|
/// <param name="VendorID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportAyaVendor(long VendorID, ArrayList alErrors)
|
|
{
|
|
|
|
if (!AyaVendorList.Any(z => z.Id == VendorID))
|
|
{
|
|
alErrors.Add("ImportAyaVendor: Vendor not found in AyaNova (deleted recently?) (" + VendorID.ToString() + ")");
|
|
return;
|
|
|
|
}
|
|
var r = await GetAsync($"vendor/{VendorID}");
|
|
var c = r.ObjectResponse["data"].ToObject<Vendor>();
|
|
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 41)
|
|
{
|
|
sName = sName.Substring(0, 41);
|
|
alErrors.Add("ImportAyaVendor: AyaNova Vendor name exceeds 41 character limit for QuickBooks\r\n" +
|
|
"Name: " + c.Name + "\r\n" +
|
|
"will be imported as: " + sName);
|
|
|
|
}
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
IVendorAdd ca = requestSet.AppendVendorAddRq();
|
|
//create a new row to add to the client cache datatable
|
|
DataRow dr = _dtQBVendors.NewRow();
|
|
|
|
|
|
dr["FullName"] = sName;
|
|
dr["MailAddress"] = new Address();
|
|
dr["StreetAddress"] = new Address();
|
|
dr["Phone"] = "";
|
|
dr["Fax"] = "";
|
|
dr["AltPhone"] = "";
|
|
dr["Email"] = "";
|
|
dr["Contact"] = "";
|
|
dr["Created"] = DateTime.MinValue;//flag indicating fresh record incomplete
|
|
dr["Modified"] = DateTime.MinValue;//ditto
|
|
dr["Account"] = "";
|
|
|
|
ca.Name.SetValue(sName);
|
|
|
|
//Case 687
|
|
ca.Contact.SetValue(T(41, c.Contact));
|
|
|
|
if (c.Phone1 != null)
|
|
ca.Phone.SetValue(T(21, c.Phone1));
|
|
else
|
|
ca.Phone.SetValue("");
|
|
ca.Fax.SetValue(T(21, c.Phone2));
|
|
ca.AltPhone.SetValue(T(21, c.Phone3));
|
|
ca.Email.SetValue(T(99, c.EmailAddress));
|
|
|
|
if (!string.IsNullOrWhiteSpace(c.PostAddress))
|
|
{
|
|
string[] sad = c.PostAddress.Replace("\r\n", "\n").Split('\n');
|
|
if (sad.GetLength(0) > 0)
|
|
ca.VendorAddress.Addr1.SetValue(T(21, sad[0].TrimEnd()));
|
|
if (sad.GetLength(0) > 1)
|
|
ca.VendorAddress.Addr2.SetValue(T(21, sad[1].TrimEnd()));
|
|
if (sad.GetLength(0) > 2)
|
|
ca.VendorAddress.Addr3.SetValue(T(21, sad[2].TrimEnd()));
|
|
if (QVersion > 1.1 && sad.GetLength(0) > 3)//4th address line is 2 or newer
|
|
ca.VendorAddress.Addr4.SetValue(T(21, sad[3].TrimEnd()));
|
|
|
|
}
|
|
ca.VendorAddress.City.SetValue(T(31, c.PostCity));
|
|
ca.VendorAddress.Country.SetValue(T(31, c.PostCountry));
|
|
ca.VendorAddress.PostalCode.SetValue(T(13, c.PostCode));
|
|
ca.VendorAddress.State.SetValue(T(21, c.PostRegion));
|
|
|
|
ca.AccountNumber.SetValue(T(99, c.AccountNumber));
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
IVendorRet cr = response.Detail as IVendorRet;
|
|
string sNewCustID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
//catch the new ID for the QB Item
|
|
dr["ID"] = sNewCustID;
|
|
//add the new row for the newly imported object
|
|
_dtQBVendors.Rows.Add(dr);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Vendor, IntegrationItemName = sName, IntegrationItemId = sNewCustID, LastSync = DateTime.Now, ObjectId = c.Id };
|
|
QBIntegration.Items.Add(m);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportAyaVendor: QuickBooks won't allow import of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Refresh the indicated AyaNova part
|
|
/// to it's linked QuickBooks item record
|
|
/// </summary>
|
|
public static async Task RefreshQBPartFromAyaNova(IntegrationItem im, bool pricesOnly = false)
|
|
{
|
|
if (im == null) return;//this object is not linked
|
|
var r = await GetAsync($"part/{im.ObjectId}");
|
|
var c = r.ObjectResponse["data"].ToObject<Part>();
|
|
if (c == null) return;
|
|
|
|
|
|
DataRow dr = _dtQBItems.Rows.Find(im.IntegrationItemId);
|
|
if (dr == null) return; //QBListID not found in part list?
|
|
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 31)
|
|
sName = sName.Substring(0, 31);
|
|
|
|
|
|
|
|
string sDescription = c.Description ?? "";// PartPickList.GetOnePart(c.ID)[0].DisplayName(AyaBizUtils.GlobalSettings.DefaultPartDisplayFormat);
|
|
if (sDescription.Length > 4095)
|
|
sDescription = sDescription.Substring(0, 4095);
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
IItemInventoryMod ca = requestSet.AppendItemInventoryModRq();
|
|
//Get and set edit sequence
|
|
string sEditSequence = await GetInventoryItemEditSequenceAsync(im.IntegrationItemId);
|
|
|
|
if (!pricesOnly)
|
|
{
|
|
|
|
dr["FullName"] = sName;
|
|
dr["Type"] = qbitemtype.Inventory;
|
|
dr["VendorID"] = c.WholeSalerId.ToString();
|
|
dr["SalesDesc"] = sDescription;
|
|
|
|
//Set the qb item values
|
|
ca.Name.SetValue(sName);
|
|
ca.SalesDesc.SetValue(sDescription);
|
|
ca.PurchaseDesc.SetValue(sDescription);
|
|
}
|
|
dr["Price"] = c.Retail;
|
|
dr["Cost"] = c.Cost;
|
|
dr["Modified"] = DateTime.Now;
|
|
|
|
ca.SalesPrice.SetValue((double)c.Retail);
|
|
ca.PurchaseCost.SetValue((double)c.Cost);
|
|
|
|
//if (QVersion > 6.0)//new v8
|
|
// ca.ManufacturerPartNumber.SetValue(T(31, c.ManufacturerNumber));
|
|
|
|
//Set the QB item identification and edit sequence
|
|
ca.ListID.SetValue(im.IntegrationItemId);
|
|
ca.EditSequence.SetValue(sEditSequence);
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
im.LastSync = DateTime.Now;
|
|
if (!pricesOnly)
|
|
im.IntegrationItemName = sName;
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
CopyableMessageBox cp = new CopyableMessageBox(
|
|
"RefreshQBPartFromAyaNova: QuickBooks won't allow update of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
cp.ShowDialog();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Import a list of ayanova parts to QuickBooks
|
|
/// </summary>
|
|
/// <param name="objectIDList"></param>
|
|
public static async Task ImportAyaPart(List<long> objectIDList)
|
|
{
|
|
ArrayList alErrors = new ArrayList();
|
|
foreach (long ayid in objectIDList)
|
|
{
|
|
try
|
|
{
|
|
|
|
await ImportAyaPart(ayid, alErrors);
|
|
|
|
}
|
|
catch { };
|
|
}
|
|
|
|
if (alErrors.Count != 0)
|
|
{
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append("Export AyaNova parts to QuickBooks completed with some errors:\r\n\r\n");
|
|
foreach (object o in alErrors)
|
|
{
|
|
sb.Append((string)o);
|
|
sb.Append("\r\n************\r\n");
|
|
|
|
}
|
|
|
|
CopyableMessageBox cb = new CopyableMessageBox(sb.ToString());
|
|
cb.ShowDialog();
|
|
cb.Dispose();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated part
|
|
/// to QuickBooks item record
|
|
/// </summary>
|
|
/// <param name="VendorID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportAyaPart(long PartID, ArrayList alErrors)
|
|
{
|
|
|
|
if (!AyaPartList.Any(z => z.Id == PartID))
|
|
{
|
|
alErrors.Add("ImportAyaPart: Part not found in AyaNova (deleted recently?) (" + PartID.ToString() + ")");
|
|
return;
|
|
|
|
}
|
|
var r = await GetAsync($"part/{PartID}");
|
|
var c = r.ObjectResponse["data"].ToObject<Part>();
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 31)
|
|
{
|
|
sName = sName.Substring(0, 31);
|
|
alErrors.Add("ImportAyaPart: AyaNova part name exceeds 31 character limit for QuickBooks\r\n" +
|
|
"Name: " + c.Name + "\r\n" +
|
|
"will be imported as: " + sName);
|
|
|
|
}
|
|
string sDescription = c.Description ?? "";//PartPickList.GetOnePart(c.ID)[0].DisplayName(AyaBizUtils.GlobalSettings.DefaultPartDisplayFormat);
|
|
if (sDescription.Length > 4095)
|
|
{
|
|
sDescription = sDescription.Substring(0, 4095);
|
|
alErrors.Add("ImportAyaPart: AyaNova part Description exceeds 4095 character limit for sales description in QuickBooks\r\n" +
|
|
"will be imported as: " + sDescription);
|
|
|
|
}
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
IItemInventoryAdd ca = requestSet.AppendItemInventoryAddRq();
|
|
//create a new row to add to the client cache datatable
|
|
DataRow dr = _dtQBItems.NewRow();
|
|
dr["FullName"] = sName;
|
|
dr["Type"] = qbitemtype.Inventory;
|
|
dr["Price"] = c.Retail;
|
|
dr["Cost"] = c.Cost;
|
|
dr["SalesDesc"] = sDescription;
|
|
dr["ReorderPoint"] = 0;
|
|
dr["Modified"] = DateTime.MinValue;
|
|
dr["VendorID"] = c.WholeSalerId.ToString();
|
|
|
|
//------------------------
|
|
//Set the qb item values
|
|
ca.Name.SetValue(sName);
|
|
ca.IncomeAccountRef.ListID.SetValue(QDat.QBInventoryIncomeAccountReference);
|
|
ca.COGSAccountRef.ListID.SetValue(QDat.QBInventoryCOGSAccountRef);
|
|
ca.AssetAccountRef.ListID.SetValue(QDat.QBInventoryAssetAccountRef);
|
|
ca.SalesDesc.SetValue(sDescription);
|
|
ca.PurchaseDesc.SetValue(sDescription);
|
|
|
|
ca.SalesPrice.SetValue((double)c.Retail);
|
|
ca.PurchaseCost.SetValue((double)c.Cost);
|
|
|
|
//if (QVersion > 6.0)//new v8
|
|
// ca.ManufacturerPartNumber.SetValue(T(31, c.ManufacturerNumber));
|
|
|
|
|
|
if (USE_INVENTORY)
|
|
{
|
|
var rstocklevel = await GetAsync($"part/stock-levels/{c.Id}");
|
|
var partstocklevels = rstocklevel.ObjectResponse["data"].ToObject<List<PartStockLevel>>();
|
|
var rinventorylevel = await GetAsync($"part/latest-inventory/{c.Id}");
|
|
var partinventories = rstocklevel.ObjectResponse["data"].ToObject<List<PartInventory>>();
|
|
|
|
ca.QuantityOnHand.SetValue((double)partinventories.Sum(z => z.Balance));
|
|
ca.ReorderPoint.SetValue((double)partstocklevels.Sum(z => z.MinimumQuantity));
|
|
}
|
|
//------------------------
|
|
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
IItemInventoryRet cr = response.Detail as IItemInventoryRet;
|
|
string sNewID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
//catch the new ID for the QB Item
|
|
dr["ID"] = sNewID;
|
|
//add the new row for the newly imported object
|
|
_dtQBItems.Rows.Add(dr);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.Part, IntegrationItemName = sName, IntegrationItemId = sNewID, LastSync = DateTime.Now, ObjectId = c.Id };
|
|
QBIntegration.Items.Add(m);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportAyaPart: failed to import " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Import the indicated service rate
|
|
/// to QuickBooks item record
|
|
/// </summary>
|
|
/// <param name="VendorID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportAyaServiceRate(long RateID, ArrayList alErrors)
|
|
{
|
|
|
|
if (!AyaServiceRateList.Any(z => z.Id == RateID))
|
|
{
|
|
alErrors.Add("ImportAyaServiceRate: ServiceRate not found in AyaNova (deleted recently?) (" + RateID.ToString() + ")");
|
|
return;
|
|
|
|
}
|
|
// RatePickList.RatePickListInfo c = ratelist[RateID];
|
|
var r = await GetAsync($"service-rate/{RateID}");
|
|
var c = r.ObjectResponse["data"].ToObject<ServiceRate>();
|
|
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 31)
|
|
{
|
|
sName = sName.Substring(0, 31);
|
|
alErrors.Add("ImportAyaServiceRate: AyaNova ServiceRate name exceeds 31 character limit for QuickBooks\r\n" +
|
|
"Name: " + c.Name + "\r\n" +
|
|
"will be imported as: " + sName);
|
|
|
|
}
|
|
string sDescription = c.Name;
|
|
if (string.IsNullOrEmpty(sDescription)) sDescription = c.Name;
|
|
if (sDescription.Length > 4095)
|
|
{
|
|
sDescription = sDescription.Substring(0, 4095);
|
|
alErrors.Add("ImportAyaServiceRate: AyaNova ServiceRate description exceeds 4095 character limit for sales description in QuickBooks\r\n" +
|
|
"will be imported as: " + sDescription);
|
|
|
|
}
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
IItemServiceAdd ca = requestSet.AppendItemServiceAddRq();
|
|
//create a new row to add to the client cache datatable
|
|
DataRow dr = _dtQBItems.NewRow();
|
|
dr["FullName"] = sName;
|
|
dr["Type"] = qbitemtype.Service;
|
|
dr["Price"] = c.Charge;
|
|
dr["Cost"] = c.Cost;
|
|
dr["SalesDesc"] = sDescription;
|
|
dr["ReorderPoint"] = 0;
|
|
dr["Modified"] = DateTime.MinValue;
|
|
dr["VendorID"] = "";
|
|
|
|
//------------------------
|
|
//Set the qb item values
|
|
ca.Name.SetValue(sName);
|
|
ca.ORSalesPurchase.SalesOrPurchase.Desc.SetValue(sDescription);
|
|
ca.ORSalesPurchase.SalesOrPurchase.ORPrice.Price.SetValue((double)c.Charge);
|
|
ca.ORSalesPurchase.SalesOrPurchase.AccountRef.ListID.SetValue(QDat.QBServiceIncomeAccountRef);
|
|
//------------------------
|
|
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
IItemServiceRet cr = response.Detail as IItemServiceRet;
|
|
string sNewID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
//catch the new ID for the QB Item
|
|
dr["ID"] = sNewID;
|
|
//add the new row for the newly imported object
|
|
_dtQBItems.Rows.Add(dr);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.ServiceRate, IntegrationItemName = sName, IntegrationItemId = sNewID, LastSync = DateTime.Now, ObjectId = c.Id };
|
|
QBIntegration.Items.Add(m);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportAyaServiceRate: QuickBooks won't allow import of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//TRAVEL RATE
|
|
/// <summary>
|
|
/// Import the indicated Travel rate
|
|
/// to QuickBooks item record
|
|
/// </summary>
|
|
/// <param name="VendorID"></param>
|
|
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
|
|
public static async Task ImportAyaTravelRate(long RateID, ArrayList alErrors)
|
|
{
|
|
|
|
if (!AyaTravelRateList.Any(z => z.Id == RateID))
|
|
{
|
|
alErrors.Add("ImportAyaTravelRate: TravelRate not found in AyaNova (deleted recently?) (" + RateID.ToString() + ")");
|
|
return;
|
|
|
|
}
|
|
// RatePickList.RatePickListInfo c = ratelist[RateID];
|
|
var r = await GetAsync($"travel-rate/{RateID}");
|
|
var c = r.ObjectResponse["data"].ToObject<TravelRate>();
|
|
|
|
|
|
string sName = c.Name;
|
|
if (sName.Length > 31)
|
|
{
|
|
sName = sName.Substring(0, 31);
|
|
alErrors.Add("ImportAyaTravelRate: AyaNova TravelRate name exceeds 31 character limit for QuickBooks\r\n" +
|
|
"Name: " + c.Name + "\r\n" +
|
|
"will be imported as: " + sName);
|
|
|
|
}
|
|
string sDescription = c.Name;
|
|
if (string.IsNullOrEmpty(sDescription)) sDescription = c.Name;
|
|
if (sDescription.Length > 4095)
|
|
{
|
|
sDescription = sDescription.Substring(0, 4095);
|
|
alErrors.Add("ImportAyaTravelRate: AyaNova TravelRate description exceeds 4095 character limit for sales description in QuickBooks\r\n" +
|
|
"will be imported as: " + sDescription);
|
|
|
|
}
|
|
|
|
|
|
//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
|
|
{
|
|
|
|
|
|
//Import seems safe...
|
|
|
|
// 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
|
|
IItemServiceAdd ca = requestSet.AppendItemServiceAddRq();
|
|
//create a new row to add to the client cache datatable
|
|
DataRow dr = _dtQBItems.NewRow();
|
|
dr["FullName"] = sName;
|
|
dr["Type"] = qbitemtype.Service;
|
|
dr["Price"] = c.Charge;
|
|
dr["Cost"] = c.Cost;
|
|
dr["SalesDesc"] = sDescription;
|
|
dr["ReorderPoint"] = 0;
|
|
dr["Modified"] = DateTime.MinValue;
|
|
dr["VendorID"] = "";
|
|
|
|
//------------------------
|
|
//Set the qb item values
|
|
ca.Name.SetValue(sName);
|
|
ca.ORSalesPurchase.SalesOrPurchase.Desc.SetValue(sDescription);
|
|
ca.ORSalesPurchase.SalesOrPurchase.ORPrice.Price.SetValue((double)c.Charge);
|
|
ca.ORSalesPurchase.SalesOrPurchase.AccountRef.ListID.SetValue(QDat.QBServiceIncomeAccountRef);
|
|
//------------------------
|
|
|
|
|
|
//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;
|
|
|
|
|
|
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
|
|
|
|
if (response.StatusCode != 0)
|
|
{
|
|
throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode);
|
|
|
|
}
|
|
|
|
IItemServiceRet cr = response.Detail as IItemServiceRet;
|
|
string sNewID = cr.ListID.GetValue();
|
|
requestSet.ClearRequests();
|
|
//----------------
|
|
|
|
|
|
// Close the session and connection with QuickBooks
|
|
sessionManager.EndSession();
|
|
booSessionBegun = false;
|
|
sessionManager.CloseConnection();
|
|
|
|
//catch the new ID for the QB Item
|
|
dr["ID"] = sNewID;
|
|
//add the new row for the newly imported object
|
|
_dtQBItems.Rows.Add(dr);
|
|
|
|
//Link
|
|
var m = new IntegrationItem { AType = AyaType.TravelRate, IntegrationItemName = sName, IntegrationItemId = sNewID, LastSync = DateTime.Now, ObjectId = c.Id };
|
|
QBIntegration.Items.Add(m);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (booSessionBegun)
|
|
{
|
|
sessionManager.EndSession();
|
|
sessionManager.CloseConnection();
|
|
}
|
|
|
|
|
|
//crack the exception in case it's a generic dataportal one
|
|
//and it is if it's got an inner exception of any kind
|
|
if (ex.InnerException != null) ex = ex.InnerException;
|
|
alErrors.Add("ImportAyaTravelRate: QuickBooks won't allow import of " + sName + "\r\n" +
|
|
"Due to the following error:\r\n" + ex.Message);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
private static string T(int nLength, string s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) return "";
|
|
if (s.Length <= nLength) return s;
|
|
else
|
|
return s.Substring(0, nLength);
|
|
}
|
|
|
|
#endregion export to quickbooks
|
|
|
|
|
|
|
|
#region Workorder mismatch scanning
|
|
|
|
public enum MisMatchReason
|
|
{
|
|
NotLinkedToQB = 0,
|
|
PriceDifferent = 1,
|
|
NothingToInvoice = 2
|
|
|
|
}
|
|
/// <summary>
|
|
/// Mismatch properties
|
|
/// A structure for storing mismatches identified
|
|
///so user can resolve them.
|
|
/// </summary>
|
|
public class MisMatch
|
|
{
|
|
//public Guid WorkorderID;
|
|
public long ObjectId { get; set; }
|
|
public AyaType ObjectType { get; set; }
|
|
public string Name { get; set; }
|
|
public MisMatchReason Reason { get; set; }
|
|
public decimal AyaPrice { get; set; }
|
|
public decimal QBPrice { get; set; }
|
|
public long WorkOrderItemPartId { get; set; }
|
|
public string QBListID { get; set; }
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Given a workorder ID
|
|
/// scans the objects in the workorder
|
|
/// that need to be linked to QB for invoicing
|
|
/// and on any error found adds them to the
|
|
/// mismatched object array list
|
|
/// </summary>
|
|
/// <param name="workOrderId">Id of workorder being scanned</param>
|
|
/// <param name="MisMatches">An list of mismatch objects</param>
|
|
/// <param name="PriceOverrides">An list of id values of workorderitemparts that have been set by
|
|
/// user to forcibly use the price set on the workorderitem part even though
|
|
/// it differs from the quickbooks price</param>
|
|
/// <returns>True if all links ok, false if there are any mismatches at all</returns>
|
|
public static async Task<bool> ScanLinksOK(long workOrderId, List<MisMatch> MisMatches, List<long> PriceOverrides)
|
|
{
|
|
bool bReturn = true;
|
|
bool bSomethingToInvoice = false;
|
|
|
|
var a = await GetAsync($"workorder/{workOrderId}");
|
|
WorkOrder w = a.ObjectResponse["data"].ToObject<WorkOrder>();
|
|
if (w == null)
|
|
throw new Exception($"ScanLinksOK: WorkOrder with id {workOrderId} was not found in AyaNova and may have just been deleted.\r\nUnable to proceed.");
|
|
|
|
|
|
//Client ok?
|
|
if (!QBIntegration.Items.Any(z => z.AType == AyaType.Customer && z.ObjectId == w.CustomerId))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch(AyaClientList.FirstOrDefault(z => z.Id == w.CustomerId).Name, w.CustomerId, AyaType.Customer, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
}
|
|
|
|
//Service rates:
|
|
foreach (WorkOrderItem wi in w.Items)
|
|
{
|
|
#region Labor
|
|
foreach (WorkOrderItemLabor wl in wi.Labors)
|
|
{
|
|
|
|
//If there's *any* labor then there is something to invoice
|
|
bSomethingToInvoice = true;
|
|
|
|
//Check that rate isn't actually guid.empty
|
|
//it's possible that some users have not selected a rate on the workorder
|
|
if (null == wl.ServiceRateId || wl.ServiceRateId == 0)
|
|
throw new System.ApplicationException($"ERROR: Workorder {w.Serial} has a Labor item with no rate selected\r\nThis is a serious problem for QBI and needs to be rectified before QBI can be used.\r\n");
|
|
|
|
if (!QBIntegration.Items.Any(z => z.AType == AyaType.ServiceRate && z.ObjectId == wl.ServiceRateId))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch(AyaServiceRateList.FirstOrDefault(z => z.Id == wl.ServiceRateId).Name, (long)wl.ServiceRateId, AyaType.ServiceRate, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
}
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Travel
|
|
foreach (WorkOrderItemTravel wt in wi.Travels)
|
|
{
|
|
//If there's *any* travel then there is something to invoice
|
|
bSomethingToInvoice = true;
|
|
|
|
//Check that rate isn't actually empty
|
|
//it's possible that some users have not selected a rate on the workorder
|
|
if (null == wt.TravelRateId || wt.TravelRateId == 0)
|
|
throw new System.ApplicationException($"ERROR: Workorder {w.Serial} has a Travel item with no rate selected\r\nThis is a serious problem for QBI and needs to be rectified before QBI can be used.\r\n");
|
|
|
|
if (!QBIntegration.Items.Any(z => z.AType == AyaType.TravelRate && z.ObjectId == wt.TravelRateId))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch(AyaTravelRateList.FirstOrDefault(z => z.Id == wt.TravelRateId).Name, (long)wt.TravelRateId, AyaType.TravelRate, MisMatchReason.NotLinkedToQB, MisMatches); ;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Parts
|
|
foreach (WorkOrderItemPart wp in wi.Parts)
|
|
{
|
|
//If there's *any* parts then there is something to invoice
|
|
bSomethingToInvoice = true;
|
|
|
|
//Changed: 14-Nov-2006 to check that linked item id exists in qb
|
|
var mappedPart = QBIntegration.Items.FirstOrDefault(z => z.AType == AyaType.Part && z.ObjectId == wp.PartId);
|
|
if (mappedPart == null || QBItems.Rows.Find(mappedPart.IntegrationItemId) == null)
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch(AyaPartList.FirstOrDefault(z => z.Id == wp.PartId).Name, wp.PartId, AyaType.Part, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
|
|
}
|
|
else
|
|
{
|
|
//check the price
|
|
if (!PriceOverrides.Contains(wp.Id))
|
|
{
|
|
var ayaPartRecord = AyaPartList.FirstOrDefault(z => z.Id == wp.PartId);
|
|
|
|
decimal qbPrice = (decimal)QBItems.Rows.Find(mappedPart.IntegrationItemId)["Price"];
|
|
|
|
|
|
//------------DISCOUNT-----------------
|
|
string disco = "";
|
|
//Added:20-July-2006 to incorporate discounts on parts into qb invoice
|
|
decimal charge = wp.PriceViz;//price viz is the final per item price including any discounts
|
|
|
|
//was there an adjustment on the work order of the price?
|
|
if (wp.PriceViz != wp.ListPrice)
|
|
{
|
|
//there was some form of adjustment (not necessarily a discount)
|
|
disco = " (Price " + wp.ListPrice.ToString("c") + " adjusted on workorder to " + wp.PriceViz.ToString("c") + ") \r\n";
|
|
}
|
|
|
|
|
|
//-----------------------------
|
|
|
|
//It's a match, let's see if the price matches as well
|
|
if (charge != qbPrice)
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch($"WO: {w.Serial}{disco} Part: {ayaPartRecord.Name}",
|
|
wp.PartId,
|
|
AyaType.Part,
|
|
MisMatchReason.PriceDifferent,
|
|
MisMatches,
|
|
qbPrice,
|
|
charge,
|
|
wp.Id,
|
|
mappedPart.IntegrationItemId);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Outside service charges
|
|
|
|
if (wi.OutsideServices.Count > 0)
|
|
{
|
|
if (wi.OutsideServices.Any(z => z.PriceViz != 0))//priceviz incorporates shipping and repairs together so unlike in qbi v7 only need to check that one field for the entire collection
|
|
{
|
|
bSomethingToInvoice = true;
|
|
//there is something billable, just need to make sure
|
|
//that there is a QB charge defined for outside service
|
|
if (string.IsNullOrWhiteSpace(QDat.OutsideServiceChargeAs) ||
|
|
!QBItems.Rows.Contains(QDat.OutsideServiceChargeAs))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch("Outside services - can't be billed as not yet mapped to a QB Charge item\r\nSet in Tools->Preferences", 0, AyaType.WorkOrderItemOutsideService, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Workorder item loan charges
|
|
|
|
if (wi.Loans.Count>0)
|
|
{
|
|
//foreach (WorkOrderItemLoan wil in wi.Loans)
|
|
if(wi.Loans.Any(z => z.PriceViz != 0))
|
|
{
|
|
|
|
//if (wil.PriceViz != 0)
|
|
//{
|
|
//case 772
|
|
bSomethingToInvoice = true;
|
|
|
|
//there is something billable, just need to make sure
|
|
//that there is a QB charge defined for loaned item charges
|
|
if (string.IsNullOrWhiteSpace(QDat.WorkorderItemLoanChargeAs) ||
|
|
!QBItems.Rows.Contains(QDat.WorkorderItemLoanChargeAs))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch("Workorder item loan - can't be billed as not yet mapped to a QB Charge item\r\nSet in Tools->Preferences", 0, AyaType.WorkOrderItemLoan, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
break;
|
|
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Workorder item misc expenses
|
|
|
|
if (wi.HasExpenses)
|
|
{
|
|
foreach (WorkOrderItemMiscExpense wie in wi.Expenses)
|
|
{
|
|
|
|
|
|
if (wie.ChargeToClient)
|
|
{
|
|
bSomethingToInvoice = true;
|
|
//there is something billable, just need to make sure
|
|
//that there is a QB charge defined for misc expense item charges
|
|
if (QDat.MiscExpenseChargeAs == null ||
|
|
QDat.MiscExpenseChargeAs == "" ||
|
|
!QBItems.Rows.Contains(QDat.MiscExpenseChargeAs))
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch("Workorder item expense", Guid.Empty, AyaType.WorkOrderItemMiscExpense, MisMatchReason.NotLinkedToQB, MisMatches);
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
|
|
}//workorder items loop
|
|
|
|
//If there are no mismatches so far,
|
|
//maybe it's because it's got nothing to invoice?
|
|
if (bReturn && !bSomethingToInvoice)
|
|
{
|
|
bReturn = false;
|
|
AddMisMatch("WO: " + w.WorkorderService.ServiceNumber.ToString() + " - Nothing chargeable on it, will not be invoiced",
|
|
Guid.Empty, AyaType.Nothing, MisMatchReason.NothingToInvoice, MisMatches);
|
|
|
|
}
|
|
|
|
return bReturn;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void AddMisMatch(string Name, long ObjectId, AyaType ObjectType, MisMatchReason Reason, List<MisMatch> Mismatches)
|
|
{
|
|
AddMisMatch(Name, ObjectId, ObjectType, Reason, Mismatches, 0m, 0m, 0, "");
|
|
}
|
|
|
|
private static void AddMisMatch(string Name, long ObjectId, AyaType ObjectType,
|
|
MisMatchReason Reason, List<MisMatch> Mismatches, decimal QBPrice, decimal AyaPrice, long WorkOrderItemPartId, string QBListID)
|
|
{
|
|
bool bDuplicate = false;
|
|
//scan through list of existing mismatches,
|
|
//only add a not linked item if it's
|
|
//not there already
|
|
//other types of mismatches need to be added because
|
|
//they need to be resolved on a case by case basis or are unresolvable
|
|
if (Reason == MisMatchReason.NotLinkedToQB)
|
|
{
|
|
|
|
foreach (var m in Mismatches)
|
|
{
|
|
//Have to check ID and type here because for outside service
|
|
//and loans and misc expenses the id is always empty so type is
|
|
//the only way to differentiate
|
|
if (m.ObjectId == ObjectId && m.ObjectType == ObjectType)
|
|
{
|
|
bDuplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bDuplicate)
|
|
{
|
|
Mismatches.Add(new MisMatch
|
|
{
|
|
Name = Name,
|
|
ObjectId = ObjectId,
|
|
ObjectType = ObjectType,
|
|
Reason = Reason,
|
|
AyaPrice = AyaPrice,
|
|
QBPrice = QBPrice,
|
|
WorkOrderItemPartId = WorkOrderItemPartId,
|
|
QBListID = QBListID,
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
#endregion wo_mismatch_scan
|
|
|
|
|
|
#endregion qbi stuff (anything not api)
|
|
|
|
#region general utils
|
|
public static void OpenWebURL(object oUrl)
|
|
{
|
|
|
|
if (oUrl == null) return;
|
|
string sUrl = oUrl.ToString();
|
|
if (sUrl == "") return;
|
|
|
|
|
|
if (sUrl.ToLower().StartsWith("http"))
|
|
System.Diagnostics.Process.Start(sUrl);
|
|
else
|
|
System.Diagnostics.Process.Start("https://" + sUrl);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a semantic versioning display of assembly version information
|
|
/// </summary>
|
|
/// <param name="fileVersion"></param>
|
|
/// <returns></returns>
|
|
public static string DisplayVersion(Version fileVersion)//case 2003
|
|
{
|
|
return fileVersion.Major.ToString() + "." + fileVersion.Minor.ToString();
|
|
}
|
|
|
|
#endregion general utils
|
|
|
|
}//EOC
|
|
}//EONS
|