Files
ayanova7/source/Plugins/AyaNova.Plugin.QBOI/Util.cs
2018-06-29 19:47:36 +00:00

5350 lines
199 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Windows.Forms;
using GZTW.AyaNova.BLL;
using System.Data;
using System.Drawing;
using System.Reflection;
using System.Collections;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using System.Text.RegularExpressions;
//auth
using DesktopIppOAuth;
//qbo
using Intuit.Ipp.Core;
using q = Intuit.Ipp.Data;
using Intuit.Ipp.QueryFilter;
using Intuit.Ipp.DataService;
using Intuit.Ipp.Exception;
//extra
using System.Linq;
//QUICKBOOKS ONLINE INTERFACE
//MAIN CASE WITH NOTES:
//https://fog.ayanova.com/default.asp?3217
/*
* Developer account:
*
Go to https://developer.intuit.com/
Sign in using:
email: support@ayanova.com
pw: i8BREAKfast!
*
*
Current test account
* Expires 7/26/2017
* gzmailadmin@gmail.com
Abraxis2017#
*
* "MyTestCo" supports inventory
* OfferingSku="QuickBooks Online Plus"
* US expires 8/26/2017
* cardjohn@ayanova.com
* Abraxis2017#
* 7/27
"MyTestSimpleStart" simple start does not support inventory
* OfferingSku="QuickBooks Online Simple Start"
*
* Sales@ayanova.com
* Abraxis2017#
*
* 7/27
* DOESN"T WORK WITH QBOI as it isn't an accounting program
* Contractor self employed edition
*
* webmaster@ayanova.com
* Abraxis2017#
*
* 7/27 Essentials
* OfferingSku="QuickBooks Online Essentials"
* addressinator@ayanova.com
* Abraxis2017#
*
*
* support@ayanova.com
* AyaNovaTestCompany
* OfferingSku="QuickBooks Online EasyStart"
*
* QBOI_TEST_INVENTORY_COMPANY
* OfferingSku="QuickBooks Online EasyStart"
*
* Dec 4 2017 "Plus"
* webmaster@ayanova.com
* Abraxis2017#
*
*/
namespace AyaNova.PlugIn.QBOI
{
/// <summary>
/// Summary description for Util.
/// </summary>
public class Util
{
public enum pfstat
{
OK = 0,
Failed = 1,
Cancel = 2
}
public static void SetControlBackQBOIColor(Control f)
{
f.BackColor = System.Drawing.Color.PaleGreen;
}
#region Attributes and properties
public static string testAuthToken = string.Empty;
public static Global GlobalSettings = null;
// public static GZTW.AyaNova.BLL.Region RegionalSettings = null;
public static LocalizedTextTable LocaleText = null;
public static Integration QBOIntegration = null;
public static QBIDataEx QDat = null;
public static string QCountry = "US";
public static bool QUSA = true;
public static double QVersion = 1.1;
public static string QCompanyFile = "";
public static string QCompanyName = "";
public static string sLastRequestXML = "";
public static System.Resources.ResourceManager AyaResource = null;
//case 3296
public static Dictionary<string, string> QCompOtherPrefs = new Dictionary<string, string>();
//whether the QB company supports inventory or not
//In testing AU, CA, UK and US all support inventory if OfferingSku is "Quickbooks online plus" and not if any other value
// public static bool QBSupportsInventory = false;
public static string QOfferingSku = "";
//case 1062
public static bool bMainWindowOpen = false;
/// <summary>
/// The official and unchanging integration ID for
/// QBOI
/// </summary>
public static Guid QBID
{
get
{
return new Guid("{DE6694A6-7F78-44A1-A732-83DAF0407AE9}");
}
}
public static Guid _ImportPartToAyaNovaDefaultVendorId = Guid.Empty;
public static string _ImportPartToQBDefaultTaxCodeId = string.Empty;
public static string _ImportRateToQBDefaultTaxCodeId = string.Empty;
public static bool UseInventory//TODO CASE 3296
{
get
{
return AyaBizUtils.GlobalSettings.UseInventory;
}
}
#endregion
static Util()
{
}
/// <summary>
/// Are you sure prompt
/// </summary>
/// <param name="msg"></param>
/// <param name="title"></param>
/// <returns></returns>
public static bool AreYouSure(string msg, string title)
{
if (MessageBox.Show(
msg, title, MessageBoxButtons.YesNo, MessageBoxIcon.Question)
== DialogResult.Yes) return true;
return false;
}
#region General helper methods
static public Image AyaImage(string sImageResourceName)
{
if (AyaResource == null) return new Bitmap(10, 10);//this is strictly for design mode because forms based on setbase throw exception during design
return (Image)AyaResource.GetObject(sImageResourceName);
}
static public Icon AyaIcon(string sImageResourceName)
{
return (Icon)AyaResource.GetObject(sImageResourceName);
}
static public 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("http://" + sUrl);
}
/// <summary>
/// Gets icon image from assembly
/// </summary>
/// <param name="ImageName"></param>
/// <returns></returns>
static public Bitmap xImage(string ImageName)
{
// if(log.IsDebugEnabled)
// //case 1039 //log.Debug("Image(" + ImageName + ")");
//string [] s=Assembly.GetExecutingAssembly().GetManifestResourceNames();
return new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("AyaNovaQBI." + ImageName));
}
/// <summary>
/// Invert a color so it's readable against the passed
/// in color
/// </summary>
/// <param name="col"></param>
/// <returns></returns>
static public Color InvertColor(Color col)
{
// if(log.IsDebugEnabled)
// //case 1039 //log.Debug("InvertColor("+col.ToString()+")");
int nSourceColor = col.R + col.G + col.B;
int r = 255 - col.R;
int g = 255 - col.G;
int b = 255 - col.B;
int nInvertColor = r + g + b;
Color invert;
if (nSourceColor - nInvertColor < 28)
invert = Color.White;
else
invert = Color.FromArgb(r, g, b);
return invert;
}
/// <summary>
/// Checks to see if an item is linked already
/// </summary>
/// <param name="AyaNovaID"></param>
/// <returns></returns>
static public bool IsLinked(Guid AyaNovaID)
{
return (QBOIntegration.Maps[AyaNovaID] != null);
}
#endregion
#region Authentication and connecting
/*
Our developer account info:
Go to https://developer.intuit.com/
Sign in using:
email: support@ayanova.com
pw: i8BREAKfast!
*
*/
static bool _AuthenticationCompleted = false;
//SANDBOX KEYS
//const string KEY_APP_TOKEN = "62925126b2b38b4c00b8e0bbcba877dbbceb";
//const string KEY_O_AUTH_CONSUMER = "qyprd7sqxwIgc5WZ2qT753CwUYwzM4";
//const string KEY_O_AUTH_CONSUMER_SECRET = "bEvWlrSgnQfdOh9JiGMRVeycM083KhZhTmX2Q9pW";
//XOR OBFUSCATED SANDBOX KEYS
//const string KOAC = "8\rT8&\0P4D=TPz-:0Z";
//const string KAT = "\\O]\\RW\fD\rZ[\a}\rF_\v[\0y\f\f\v]~Y\r\v\0\0+";
//const string KOACS = "++\086.\0'\t\r,\rp$($13,\"Y[V,\a=={?O>";
//PRODUCTION KEYS
//const string KEY_APP_TOKEN = "14fdc2bab8e1cb4e94b9168b0e44eee9bbdd";
//const string KEY_O_AUTH_CONSUMER = "qyprdvtMbWorJ5JuVce94uGEHxWvOM";
//const string KEY_O_AUTH_CONSUMER_SECRET = "H7IavvIL8jqpopx0vbBhDZRFBXxAHdABPo3IQSLZ";
//XOR OBFUSCATED PRODUCTION KEYS
const string KOAC = "8\r\f!\0)P \f\fZQ<)3'4#";
const string KAT = "xZ\v\nQ\a(\fN\nX\0\a}\vO[\vZTV_\fWQ,\vV\v-";
const string KOACS = "Y?,V\f1^\0\r+\v!<0-1$\n7-9\fV\0?%#3";
//simple xor encryption
private static string Ec(string text, string key)
{
var result = new StringBuilder();
for (int c = 0; c < text.Length; c++)
result.Append((char)((uint)text[c] ^ (uint)key[c % key.Length]));
return result.ToString();
}
static ServiceContext SC = null;
static DesktopIppOAuth.OAuthConnector CN;
static public void StartAuthorization()
{
//#if(DEBUG)
// string KEY_APP_TOKEN_ENCRYPTED = Ec(KEY_APP_TOKEN, "Invoice");
// string KEY_O_AUTH_CONSUMER_ENCRYPTED = Ec(KEY_O_AUTH_CONSUMER, "Invoice");
// string KEY_O_AUTH_CONSUMER_SECRET_ENCRYPTED = Ec(KEY_O_AUTH_CONSUMER_SECRET, "Invoice");
//#endif
SC = null;
CN = new OAuthConnector();
CN.IppOAuthResultEvent += _authResultEvent;
//CN.Connect(KEY_O_AUTH_CONSUMER, KEY_O_AUTH_CONSUMER_SECRET, "http://www.ayanova.com");
CN.Connect(Ec(KOAC, "Invoice"), Ec(KOACS, "Invoice"), "http://www.ayanova.com");
}
/// <summary>
/// Process auth event
/// </summary>
/// <param name="accessToken"></param>
/// <param name="accessTokenSecret"></param>
/// <param name="realmId"></param>
/// <param name="dataSource"></param>
static void _authResultEvent(
string accessToken,
string accessTokenSecret,
string realmId,
string dataSource)
{
//Intuit.Ipp.Security.OAuthRequestValidator oauthValidator =
// new Intuit.Ipp.Security.OAuthRequestValidator(
// accessToken,
// accessTokenSecret,
// KEY_O_AUTH_CONSUMER,
// KEY_O_AUTH_CONSUMER_SECRET);
Intuit.Ipp.Security.OAuthRequestValidator oauthValidator =
new Intuit.Ipp.Security.OAuthRequestValidator(
accessToken,
accessTokenSecret,
Ec(KOAC, "Invoice"),
Ec(KOACS, "Invoice"));
SC = new ServiceContext(accessToken, realmId, IntuitServicesType.QBO, oauthValidator);
//TODO: set up a proper retry policy look into it
//https://developer.intuit.com/docs/0100_quickbooks_online/0400_tools/0005_sdks/0010.net_tools/0050_retries
//FOr now retry every 10 seconds for 3 times for total of a 30 seconds.
#if(!DEBUG)
//Can't do this because I lose the exception and the end user never sees it.
// SC.IppConfiguration.RetryPolicy = new Intuit.Ipp.Retry.IntuitRetryPolicy(3, new TimeSpan(0, 0, 10));
#endif
#if(DEBUG)
//DANGER DANGER!!! If a retry is specified then the only exception error seen during dev is BadRequest
//and not the actual error.
//Have a case on the go about it:
//https://help.developer.intuit.com/s/case/5000f00001Bu1umAAB
//SC.IppConfiguration.RetryPolicy = new Intuit.Ipp.Retry.IntuitRetryPolicy(3, new TimeSpan(0, 0, 10));
//SC.IppConfiguration.Logger.RequestLog.EnableRequestResponseLogging = true;
//SC.IppConfiguration.Logger.RequestLog.ServiceRequestLoggingLocation = @"C:\temp\QBOLOGS";
#endif
//Notify that we are authenticated
_AuthenticationCompleted = true;
//todo what happens if we don't login??
//test case 3515
//System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; //Add this just to be sure to use TLS1.2
//var result = string.Empty;
//using (var webClient = new System.Net.WebClient())
//{
// result = webClient.DownloadString("https://tlstest.intuit.com");
//}
}
#endregion authentication
#region Pre flight check
/// <summary>
/// Pre flight check, prompt user as required to repair
/// or add missing settings / failed ones
///
///
/// </summary>
/// <returns></returns>
public static pfstat PreFlightCheck()
{
if (!_AuthenticationCompleted)
StartAuthorization();
while (!_AuthenticationCompleted)
{
//wait for authorization,
//TODO: add timeout here
}
if (QBValidate() == pfstat.Cancel)
{
IntegrationLog.Log(QBID, "PFC: Unable to validate QuickBooks connection, user selected cancel");
return pfstat.Cancel;
}
else
{
IntegrationLog.Log(QBID, "PFC: QB validated Country=" + QCountry + ", QBVersion=" + QVersion.ToString() + ", Companyfile=" + QCompanyFile);
PopulateQBListCache();
PopulateAyaListCache();
}
IntegrationObjectCheck();
if (ValidateSettings(false) == pfstat.Cancel)
{
IntegrationLog.Log(QBID, "PFC: User settings not completed, user selected cancel");
return pfstat.Cancel;
}
//Added: 18-Nov-2006 CASE 163
//check that linked items in integration map exist in QB
if (QBOIntegration.Maps.Count == 0) return pfstat.OK;
//Missing links table:
DataTable dtTemp = new DataTable();
dtTemp.Columns.Add("MAPID", typeof(Guid));
dtTemp.Columns.Add("Name", typeof(string));
bool present = true;
foreach (IntegrationMap m in QBOIntegration.Maps)
{
present = true;
switch (m.RootObjectType)
{
case RootObjectTypes.Client:
present = QBClients.Rows.Contains(m.ForeignID);
break;
case RootObjectTypes.Vendor:
present = QBVendors.Rows.Contains(m.ForeignID);
break;
case RootObjectTypes.Rate:
case RootObjectTypes.Part:
present = QBItems.Rows.Contains(m.ForeignID);
break;
}
if (!present)
dtTemp.Rows.Add(new object[] { m.ID, m.RootObjectType.ToString() + ": " + m.Name });
}
if (dtTemp.Rows.Count > 0)
{
if (dtTemp.Rows.Count == QBOIntegration.Maps.Count)
{
//None of the items mapped match offer to remove them all
#region Nothing matches
IntegrationLog.Log(QBID, "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)
{
IntegrationLog.Log(QBID, "PFC: User opted to remove all mappings after double warning.");
foreach (DataRow row in dtTemp.Rows)
{
QBOIntegration.Maps.Remove(row["MAPID"].ToString());
}
QBOIntegration = (Integration)QBOIntegration.Save();
return pfstat.Cancel;
}
}
#endregion
}
else
{
//some items match so iterate them and offer to delete one by one
IntegrationLog.Log(QBID, "PFC: Some integration maps do not match qb database objects");
foreach (DataRow row in dtTemp.Rows)
{
DialogResult dr = MessageBox.Show("Linked object: " + row["Name"].ToString() + "\r\n" +
"Is missing or set Inactive in QuickBooks.\r\n\r\nRemove it's link from AyaNova?", "", MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3);
if (dr == DialogResult.Cancel) return pfstat.Cancel;
if (dr == DialogResult.Yes)
QBOIntegration.Maps.Remove(row["MAPID"].ToString());
}
QBOIntegration = (Integration)QBOIntegration.Save();
}
}
return pfstat.OK;
}
#region PFC AyaNova side
/// <summary>
/// Check if integration object is present in database, if not then
/// add it.
/// </summary>
/// <returns></returns>
public static pfstat IntegrationObjectCheck()
{
if (QBOIntegration == null)
{
if (Integration.IntegrationExists(QBID))
{
QBOIntegration = Integration.GetItem(QBID);
//Get the QuickBooks settings object
//Block below completely modified for
//Case 299
QDat = new QBIDataEx();
if (QBOIntegration.AIObject == null || QBOIntegration.AIObject.ToString() == "")
{
QBOIntegration.AIObject = QDat.XMLData;
QBOIntegration = (Integration)QBOIntegration.Save();
}
else
{
if (QBOIntegration.AIObject is QBIData)
{
QDat.convertFromObjectFormat(QBOIntegration.AIObject);
QBOIntegration.AIObject = QDat.XMLData;
QBOIntegration = (Integration)QBOIntegration.Save();
}
else
{
//All normal, parse xml and move on
QDat.XMLData = (string)QBOIntegration.AIObject;
}
}
//Old pre case 299 block
// QDat = (QBIData)QBI.AIObject;
//if (QDat == null)
//{
// QDat = new QBIData();
// QBI.AIObject = QDat;
// QBI = (Integration)QBI.Save();
//}
}
else
{
QBOIntegration = Integration.NewItem(QBID);
QBOIntegration.Active = true;
QBOIntegration.AppVersion = "7.x+";
QBOIntegration.Name = "AyaNova QBOI - QuickBooks Online integration";
//Case 299
QDat = new QBIDataEx();
QBOIntegration.AIObject = QDat.XMLData;
QBOIntegration = (Integration)QBOIntegration.Save();
IntegrationLog.Log(QBID, "PFC: QBOI Integration object created");
}
}
return pfstat.OK;
}
#endregion
#region PFC QB side
/// <summary>
/// Validate QB data
/// </summary>
public static pfstat QBValidate()
{
/*
* Error reported by user:
*
2017-12-09 17:24:09,339 [11032] FATAL AyaNova.Form1 - Unhandled exception
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at AyaNova.PlugIn.QBOI.Util.QBValidate()
at AyaNova.PlugIn.QBOI.Util.PreFlightCheck()
*
*
* 2018-03-15 customer replied back finally with a duplicate key message:
* Unexpected duplicate value returned by QuickBooks Online company info query
Please copy this information to AyaNova technical support (support@ayanova.com)
Company info returned:
KEY: NeoEnabled, VALUE: true
KEY: IsQbdtMigrated, VALUE: true
KEY: MigrationDate, VALUE: Dec 9, 2017 4:42:19 AM
KEY: MigrationSource, VALUE: QuickBooks-2015
KEY: IndustryType, VALUE: Computer Systems Design and Related Services
KEY: CompanyType, VALUE: Limited Liability
KEY: IndustryCode, VALUE: 5415
KEY: SubscriptionStatus, VALUE: PAID
KEY: OfferingSku, VALUE: QuickBooks Online Essentials
KEY: PayrollFeature, VALUE: false
KEY: AccountantFeature, VALUE: false
KEY: QBOIndustryType, VALUE: Manufacturing Businesses
KEY: ItemCategoriesFeature, VALUE: true
KEY: AssignedTime, VALUE: 12/08/2017 18:52:04
* NOTE that there are not actually any duplicates, so why did this happen???
* Does this data need to be cleared first before the attempt to fetch, is it a re-fetch causing this??
* Maybe this should be changed to show what it *thinks* are the dupes as well??
*
*
*/
try
{
QueryService<q.CompanyInfo> entityQuery = new QueryService<q.CompanyInfo>(SC);
List<q.CompanyInfo> comp = entityQuery.ExecuteIdsQuery("SELECT * FROM CompanyInfo").ToList<q.CompanyInfo>();
//case 3520
bool dupeKeyFoundInCompanyInfo = false;
//Get the country and latest version supported
//QB Online doesn't really have a "version", only a minor version
//for the sake of compatibility just setting this to 3 which is the major api version number
//current to when this code was written and probably best usage in the spirit of what this variable is all about
QVersion = 3;
foreach (Intuit.Ipp.Data.NameValue i in comp[0].NameValue)
{
//case 3520
if (QCompOtherPrefs.ContainsKey(i.Name))
{
dupeKeyFoundInCompanyInfo = true;
}
else
{
QCompOtherPrefs.Add(i.Name, i.Value);
}
//get the exact type of QB Online
if (i.Name == "OfferingSku")
{
QOfferingSku = i.Value;
}
}
//case 3520 display issue
if (dupeKeyFoundInCompanyInfo)
{
StringBuilder sMsg = new StringBuilder();
sMsg.AppendLine("Unexpected duplicate value returned by QuickBooks Online company info query");
sMsg.AppendLine("Please copy this information to AyaNova technical support (support@ayanova.com)");
sMsg.AppendLine("Company info returned:");
foreach (Intuit.Ipp.Data.NameValue i in comp[0].NameValue)
{
sMsg.AppendLine("KEY: " + i.Name + ", VALUE: " + i.Value);
}
CopyableMessageBox cp = new CopyableMessageBox(sMsg.ToString());
cp.ShowDialog();
}
//this is current as of 7/27/2017
// wrong wrong wrong apparently, need to use preferences.ProductAndServicesPrefs.QuantityOnHand is true instead
// QBSupportsInventory = (QOfferingSku == "QuickBooks Online Plus" || QOfferingSku == "QuickBooks Online");
QCountry = comp[0].Country;//In testing it was CA for canada and US for US, GB for UK, AU for australia
QUSA = QCountry == "US";
//Get the company name (file was for desktop but keeping for JIC)
QCompanyName = QCompanyFile = comp[0].CompanyName;
return pfstat.OK;
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PFC: Failed with exception:" + CrackException(ex));
//clean connection
CN.Clean();
throw;
}
}
#endregion
#region PFC Check if user settings are completed and valid
//case 3267
public static string TRANSACTION_CLASS_NO_CLASS_SELECTED = "<GZNOCLASS>";
/// <summary>
/// Validate the users preferences
/// if any are missing or invalid prompt for them
/// </summary>
/// <returns></returns>
public static 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(new Form { TopMost = true },
"AyaNova QBOI has now connected sucessfully to both AyaNova and QuickBooks Online.\r\n\r\n" +
"The next step is to set preferences for how AyaNova QBOI will operate.\r\n\r\n" +
"AyaNova QBOI 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);
}
#region confirm company file
ApproveCompanyFile s0 = new ApproveCompanyFile();
s0.QBCompanyName = QCompanyName.Replace("&", "&&");
s0.QBCompanyPath = QCompanyFile;
if (s0.ShowDialog() == DialogResult.Cancel)
{
IntegrationLog.Log(QBID, "PFC: 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 != Guid.Empty)
{
if (WorkorderStatus.Exists(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 QBOI setup - Choose billable Workorder Status";
s1.OptionTitle = "Billable workorder status";
s1.OptionDescription = "One of AyaNova QBOI'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 QBOI 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 QBOI \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 != Guid.Empty)
{
if (WorkorderStatus.Exists(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 QBOI setup - Choose post billed Workorder Status";
s1.OptionTitle = "Post billing workorder status";
s1.OptionDescription = "After QBOI has billed out a work order, it can change the \r\n" +
"work order status for you automatically if desired.";
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 QBOI setup - Charge outside service as?";
s2.OptionTitle = "Outside service";
s2.OptionDescription = "QBOI 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;
//s2.SelectedQBItem=QDat.PreWOStatus;
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 != "")
{
//case 3292 uncommented the comment that had turned off this check
if (QBItems.Rows.Contains(QDat.MiscExpenseChargeAs))
goto MISCCHARGEASOK;
else
{
MessageBox.Show("The QuickBooks Item previously set for invoicing Misc. Expense 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 Misc expense
s2 = new SetQBChargeAs();
s2.DialogTitle = "AyaNova QBOI setup - Charge Misc. Expense as?";
s2.OptionTitle = "Miscellaneous expenses";
s2.OptionDescription = "QBOI 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 != "")
{
//case 3292 uncommented the comment that had turned off this check
if (QBItems.Rows.Contains(QDat.MiscExpenseChargeAs))
goto LOANCHARGEASOK;
else
{
MessageBox.Show("The QuickBooks Item previously set for invoicing Misc. Expense 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 Misc expense
s2 = new SetQBChargeAs();
s2.DialogTitle = "AyaNova QBOI setup - Charge loan item as?";
s2.OptionTitle = "Work order loans";
s2.OptionDescription = "QBOI 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 && QDat.TransactionClass != null && 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 3267
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.
//todo: make something besides and empty string to indicate
//deliberately non selected items
//case 3228 commented out the following as it's a bad idea
//if (QBClasses.Rows.Count == 1 || SetEverything == false)
// goto TRCLASSOK;
//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 QBOI setup - Transaction class";
s3.OptionTitle = "Transaction class";
s3.OptionDescription = "QBOI 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 (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 QBOI setup - Invoice template";
s3a.OptionTitle = "Invoice template - NOT SUPPORTED";
s3a.OptionDescription =
"QBOI 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 QBOI.\r\n" +
"Supported versions of QuickBooks for using Invoice templates with QBOI 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 QBOI setup - Invoice template";
s3b.OptionTitle = "Invoice template";
s3b.OptionDescription = "QBOI needs to know what QuickBooks Invoice template you want \r\n" +
"QBOI 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
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 QBOI setup - Customer default invoice terms";
termsdialog.OptionTitle = "Default terms";
termsdialog.OptionDescription = "QBOI needs to know what QuickBooks terms you want \r\n" +
"QBOI 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)
{
//if(QBItems.Rows.Contains(QDat.MiscExpenseChargeAs))
goto TBPOK;
// else
// {
// MessageBox.Show("The QuickBooks Item previously set for invoicing Misc. Expense items\r\n" +
// "No longer appears to be valid. You will next be prompted to re-select a valid \r\n" +
// "QuickBooks Item.");
// }
}
SetToBePrinted s4 = new SetToBePrinted();
s4.DialogTitle = "AyaNova QBOI setup - Set invoice to be printed?";
s4.OptionTitle = "Invoice to be printed";
s4.OptionDescription = "QBOI 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";
s4.ToBePrinted = QDat.ToBePrinted;
if (s4.ShowDialog() == DialogResult.Cancel)
{
return pfstat.Cancel;
}
else
QDat.ToBePrinted = s4.ToBePrinted;
s4.Dispose();
s4 = null;
TBPOK:
#endregion
#region ToBePrinted
//No validation possible
//so prompt only if not setup yet
if (!SetEverything)
{
goto TBEMOK;
}
SetToBeEmailed s4B = new SetToBeEmailed();
s4B.DialogTitle = "AyaNova QBOI setup - Set invoice to be emailed?";
s4B.OptionTitle = "Invoice to be emailed";
s4B.OptionDescription = "QBOI needs to know if you want invoices that it creates \r\n" +
"in QuickBooks to be set to \"Send later\".\r\n\r\n";
s4B.ToBeEmailed = QDat.ToBeEmailed;
if (s4B.ShowDialog() == DialogResult.Cancel)
{
return pfstat.Cancel;
}
else
QDat.ToBeEmailed = s4B.ToBeEmailed;
s4B.Dispose();
s4B = null;
TBEMOK:
#endregion
#region SetMemoField
//No validation possible
//so prompt only if not setup yet
if (!SetEverything)
{
//if(QBItems.Rows.Contains(QDat.MiscExpenseChargeAs))
goto SETMEMOOK;
// else
// {
// MessageBox.Show("The QuickBooks Item previously set for invoicing Misc. Expense items\r\n" +
// "No longer appears to be valid. You will next be prompted to re-select a valid \r\n" +
// "QuickBooks Item.");
// }
}
SetMemoField s5 = new SetMemoField();
s5.DialogTitle = "AyaNova QBOI setup - Set Memo field?";
s5.OptionTitle = "Invoice memo field";
s5.OptionDescription =
"QBOI 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 QBOI setup - Close when invoiced?";
s6.OptionTitle = "Close work order after invoicing";
s6.OptionDescription =
"QBOI 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)
{
//Case 299
QBOIntegration.AIObject = QDat.XMLData;
//QBI.AIObject=QDat;
QBOIntegration = (Integration)QBOIntegration.Save();
QDat.IsDirty = false;
}
return pfstat.OK;
}
#endregion
#endregion pfc
#region AyaNova cached lists
public static void PopulateAyaListCache()
{
//Get the cached QB data
Waiting w = new Waiting();
w.Show();
w.Ops = "Reading from AyaNova...";
w.Step = "Clients";
PopulateAyaClientList();
w.Step = "Vendors";
PopulateAyaVendorList();
w.Step = "Rates";
PopulateAyaRateList();
w.Step = "Parts";
PopulateAyaPartList();
w.Close();
}
#region AyaNova clients
private static ClientPickList _clientlist = null;
/// <summary>
/// AyaNova ClientPickList
/// </summary>
public static ClientPickList AyaClientList
{
get
{
return _clientlist;
}
}
public static void PopulateAyaClientList()
{
_clientlist = ClientPickList.GetList();
}
#endregion ayanova clients
#region AyaNova Vendors
private static VendorPickList _vendorlist = null;
/// <summary>
/// AyaNova vendor list
/// </summary>
public static VendorPickList AyaVendorList
{
get
{
return _vendorlist;
}
}
public static void PopulateAyaVendorList()
{
_vendorlist = VendorPickList.GetList();
}
#endregion ayanova vendors
#region AyaNova Rates
private static RatePickList _ratelist = null;
/// <summary>
/// AyaNova rate list
/// </summary>
public static RatePickList AyaRateList
{
get
{
return _ratelist;
}
}
public static void PopulateAyaRateList()
{
_ratelist = RatePickList.GetListAllActiveRates();
}
#endregion ayanova rates
#region AyaNova Parts
private static PartPickList _partlist = null;
/// <summary>
/// AyaNova part list
/// </summary>
public static PartPickList AyaPartList
{
get
{
return _partlist;
}
}
public static void PopulateAyaPartList()
{
_partlist = PartPickList.GetAllParts();
}
#endregion ayanova parts
#endregion
#region QB API helper methods/ attributes/cached lists
/// <summary>
/// Populate or repopulate the list of
/// </summary>
public static void PopulateQBListCache()
{
//Get the cached QB data
Waiting w = new Waiting();
w.Show();
w.Ops = "Reading from QuickBooks Online...";
w.Step = "Company preferences";
PopulateQBPreferencesCache();
////case 3294 / case 3296
//PopulateQBEntitlementCache();
w.Step = "Classes";
PopulateQBClassCache();
w.Step = "Vendors";
PopulateQBVendorCache();
w.Step = "Customers";
PopulateQBClientCache();
w.Step = "Items";
PopulateQBItemCache();
if (!(QVersion < 3))//qbXML 3.0 or higher (QB 2004 any country or newer)
{
w.Step = "Invoice templates";
PopulateQBInvoiceTemplates();
}
//case 632
w.Step = "Accounts";
PopulateQBAccountCache();
//case 519
w.Step = "Terms";
PopulateQBTermsCache();
w.Step = "Tax codes";
PopulateQBTaxCodesCache();
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
/// Guid empty on any problem or not found
/// </summary>
/// <param name="QBItemID"></param>
/// <returns></returns>
public static Guid AyaVendorForQBItem(string QBItemID)
{
if (QBItemID == null || QBItemID == "") return Guid.Empty;
DataRow dr = _dtQBItems.Rows.Find(QBItemID);
if (dr == null || dr["VendorID"] == null || dr["VendorID"].ToString() == "") return Guid.Empty;
DataRow drVendor = _dtQBVendors.Rows.Find(dr["VendorID"].ToString());
if (drVendor == null) return Guid.Empty;
if (!QBOIntegration.Maps.Contains(drVendor["ID"].ToString(), RootObjectTypes.Vendor)) return Guid.Empty;
//Ok we have a matching vendor in the list, return the guid of it
return QBOIntegration.Maps[drVendor["ID"].ToString(), RootObjectTypes.Vendor].RootObjectID;
}
/// <summary>
/// Populate the cached qb data
/// billable
/// </summary>
private static void PopulateQBItemCache()
{
if (_dtQBItems == null)
{
_dtQBItems = new DataTable("QBItems");
//setup the columns
_dtQBItems.Columns.Add("ID", typeof(string));
_dtQBItems.Columns.Add("FullName", typeof(string));
_dtQBItems.Columns.Add("Type", typeof(qbitemtype));
_dtQBItems.Columns.Add("Modified", typeof(DateTime));
_dtQBItems.Columns.Add("Price", typeof(decimal));
_dtQBItems.Columns.Add("Cost", typeof(decimal));
_dtQBItems.Columns.Add("SalesDesc", typeof(string));
_dtQBItems.Columns.Add("ReorderPoint", typeof(decimal));
_dtQBItems.Columns.Add("VendorID", typeof(string));
_dtQBItems.Columns.Add("Taxable", typeof(bool));
_dtQBItems.Columns.Add("TaxId", typeof(string));//non US locales
_dtQBItems.PrimaryKey = new DataColumn[] { _dtQBItems.Columns[0] };
//Case 237
_dtQBItems.DefaultView.Sort = "FullName asc";
}
else
_dtQBItems.Clear();
//Assembly is coming back invalid and they have the bundle feature (known as Group in the api) so going with that for now
string[] qboItemTypes = { "Service", "Inventory", "NonInventory", "Group" };
foreach (string s in qboItemTypes)
{
try
{
qbitemtype itemType = qbitemtype.OtherCharge;
switch (s)
{
case "Service":
itemType = qbitemtype.Service;
break;
case "Inventory":
itemType = qbitemtype.Inventory;
break;
case "NonInventory":
itemType = qbitemtype.NonInventory;
break;
case "Group":
itemType = qbitemtype.Assembly;
break;
}
bool bDone = false;
int nStart = 0;
QueryService<q.Item> entityQuery = new QueryService<q.Item>(SC);
while (!bDone)
{
//GET first 1000
List<q.Item> items = entityQuery.ExecuteIdsQuery("select * from Item where Type='" + s + "' startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Item>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
string salesTaxId = string.Empty;
foreach (q.Item i in items)
{
if (i.SalesTaxCodeRef != null)
salesTaxId = i.SalesTaxCodeRef.Value;
else
salesTaxId = string.Empty;
_dtQBItems.Rows.Add(
new object[] {
i.Id,
i.Name,
itemType,
i.MetaData.LastUpdatedTime,
i.UnitPrice,
i.PurchaseCost,
i.Description,
i.ReorderPoint,
"",//QBO doesn't support vendor id property
i.Taxable,
salesTaxId
});
}
//REPEAT
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBItems: Failed with exception:" + CrackException(ex));
throw;
}
}
}
#endregion quickbooks items
#region QuickBooks "Transactionclasses"
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 void PopulateQBClassCache()
{
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 3267
_dtQBClasses.Rows.Add(new object[] { TRANSACTION_CLASS_NO_CLASS_SELECTED, "< Do not use classes >" });
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.Class> classQuery = new QueryService<q.Class>(SC);
while (!bDone)
{
//GET first 1000
List<q.Class> classes = classQuery.ExecuteIdsQuery("select name from Class startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Class>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (classes.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.Class i in classes)
{
//add a record to the datatable
_dtQBClasses.Rows.Add(new object[] { i.Id, i.Name });
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBClasses: Failed with exception:" + CrackException(ex));
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 void PopulateQBInvoiceTemplates()
{
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 (currently not supported by Online QB) >" });
try
{
//TODO: There appear to be no invoice templates available through the UI
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBInvoiceTemplates: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion quickbooks Templates
#region QuickBooks "Customers"
#region SyncToken fetching
private static string GetQBCustomerSyncToken(string customerid)
{
try
{
QueryService<q.Customer> qs = new QueryService<q.Customer>(SC);
List<q.Customer> items = qs.ExecuteIdsQuery("select synctoken from Customer where id = '" + customerid + "'").ToList<q.Customer>();
if (items.Count < 1)
return string.Empty;
else
return items[0].SyncToken;
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "GetQBCustomerSyncToken: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion SyncToken
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>
private static void PopulateQBClientCache()
{
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.Columns.Add("Taxable", typeof(bool));
_dtQBClients.Columns.Add("TaxId", typeof(string));
//Need to get company wide default tax code then set it for each
//customer that is taxable yes, but has no default tax code set
//or maybe it is set by the .net sdk, have to test that
//i.DefaultTaxCodeRef will be null but taxable can be true (amys bird sanctuary example)
//_dtQBClients.Columns.Add("TaxCode", typeof(string));
_dtQBClients.PrimaryKey = new DataColumn[] { _dtQBClients.Columns[0] };
//Case 237
_dtQBClients.DefaultView.Sort = "FullName asc";
}
else
_dtQBClients.Clear();
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.Customer> qs = new QueryService<q.Customer>(SC);
while (!bDone)
{
//GET first 1000
List<q.Customer> items = qs.ExecuteIdsQuery("select * from Customer startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Customer>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.Customer i in items)
{
//add a record to the datatable
// _dtQBClients.Columns.Add("Taxable", typeof(bool));
//_dtQBClients.Columns.Add("TaxId", typeof(string));
string sTaxCode = TAX_CODE_ID_NO_TAX;
if (i.Taxable)
{
if (i.DefaultTaxCodeRef != null)
{
sTaxCode = i.DefaultTaxCodeRef.Value;
}
else
{
//Use company pref tax code
if (QBPreferences.TaxPrefs.AnyIntuitObject != null)
{
sTaxCode = _QBPreferences.TaxPrefs.AnyIntuitObject.Value;
}
else
{
//There is no company tax code and no customer tax code so we'll just accept the
//already set default of TAX_CODE_ID_NO_TAX
}
// QBPreferences.TaxPrefs..TaxPrefs.
}
}
//case 3565 (related to case 3520)
if (!_dtQBClients.Rows.Contains(i.Id))
{
_dtQBClients.Rows.Add(
new object[]{
i.Id,
i.DisplayName,
ProcessAddress(i.BillAddr),
ProcessAddress(i.ShipAddr),
ProcessQBPhone(i.PrimaryPhone),
ProcessQBPhone(i.Fax),
ProcessQBPhone(i.AlternatePhone),
ProcessQBEmail(i.PrimaryEmailAddr),
i.ContactName,
i.MetaData.CreateTime,
i.MetaData.LastUpdatedTime,
i.AcctNum,
i.Taxable,
sTaxCode
});
}
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBClients: Failed with exception:" + CrackException(ex));
throw;
}
}
/// <summary>
/// handle null phones, just get the digits
/// </summary>
/// <param name="ph"></param>
/// <returns></returns>
private static string ProcessQBPhone(q.TelephoneNumber ph)
{
if (ph == null)
return string.Empty;
else
return ph.FreeFormNumber;
}
/// <summary>
/// handle null phones, just get the digits
/// </summary>
/// <param name="ph"></param>
/// <returns></returns>
private static string ProcessQBEmail(q.EmailAddress em)
{
if (em == null)
return string.Empty;
else
return em.Address;
}
/// <summary>
/// Take a qb address and return an AyaNova friendly
/// address structure
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
private static Address ProcessAddress(q.PhysicalAddress 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 = a.Line1;
b.DeliveryAddress += AyaBizUtils.SS("\r\n", a.Line2, "");
b.DeliveryAddress += AyaBizUtils.SS("\r\n", a.Line3, "");
b.DeliveryAddress += AyaBizUtils.SS("\r\n", a.Line4, "");
b.DeliveryAddress += AyaBizUtils.SS("\r\n", a.Line5, "");
b.City = a.City;
b.StateProv = a.CountrySubDivisionCode;
b.Country = a.Country;
b.Postal = a.PostalCode;
if (b.DeliveryAddress == null)
b.DeliveryAddress = string.Empty;
if (b.City == null)
b.City = string.Empty;
if (b.StateProv == null)
b.StateProv = string.Empty;
if (b.Country == null)
b.Country = string.Empty;
if (b.Postal == null)
b.Postal = string.Empty;
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>
private static void PopulateQBVendorCache()
{
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();
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.Vendor> qs = new QueryService<q.Vendor>(SC);
while (!bDone)
{
//GET first 1000
List<q.Vendor> items = qs.ExecuteIdsQuery("select * from Vendor startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Vendor>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.Vendor i in items)
{
_dtQBVendors.Rows.Add(
new object[]{
i.Id,
i.DisplayName,
ProcessAddress(i.BillAddr),
ProcessAddress(i.ShipAddr),
ProcessQBPhone(i.PrimaryPhone),
ProcessQBPhone(i.Fax),
ProcessQBPhone(i.AlternatePhone),
ProcessQBEmail(i.PrimaryEmailAddr),
i.ContactName,
i.MetaData.CreateTime,
i.MetaData.LastUpdatedTime,
i.AcctNum
});
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBVendors: Failed with exception:" + CrackException(ex));
throw;
}
}
#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 void PopulateQBAccountCache()
{
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();
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.Account> qs = new QueryService<q.Account>(SC);
while (!bDone)
{
//GET first 1000
List<q.Account> items = qs.ExecuteIdsQuery("select * from Account startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Account>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.Account i in items)
{
//add a record to the datatable
_dtQBAccounts.Rows.Add(
new object[] {
i.Id,
i.AccountType + " - " + i.Name,
i.AccountType
});
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBAccounts: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion quickbooks accounts
#region QuickBooks "Terms"
private static DataTable _dtQBTerms = null;
/// <summary>
/// QB terms
/// </summary>
public static DataTable QBTerms
{
get
{
return _dtQBTerms;
}
}
/// <summary>
/// Populate the cached qb terms list data
/// </summary>
private static void 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();
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.Term> qs = new QueryService<q.Term>(SC);
while (!bDone)
{
//GET first 1000
List<q.Term> items = qs.ExecuteIdsQuery("select * from Term startposition " + nStart.ToString() + " maxresults 1000").ToList<q.Term>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.Term i in items)
{
//add a record to the datatable
_dtQBTerms.Rows.Add(
new object[] {
i.Id,
i.Name
});
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBTerms: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion quickbooks Terms
#region QuickBooks "TaxCodes"
private static DataTable _dtQBTaxCodes = null;
/// <summary>
/// QB tax codes
/// </summary>
public static DataTable QBTaxCodes
{
get
{
return _dtQBTaxCodes;
}
}
public static string TAX_CODE_ID_NO_TAX = "<GZNOTAX>";
/// <summary>
/// Populate the cached qb list data
/// </summary>
private static void PopulateQBTaxCodesCache()
{
if (_dtQBTaxCodes == null)
{
_dtQBTaxCodes = new DataTable("QBTaxCodes");
//setup the columns
_dtQBTaxCodes.Columns.Add("ID", typeof(string));
_dtQBTaxCodes.Columns.Add("FullName", typeof(string));
_dtQBTaxCodes.PrimaryKey = new DataColumn[] { _dtQBTaxCodes.Columns[0] };
//Case 237
_dtQBTaxCodes.DefaultView.Sort = "FullName asc";
}
else
_dtQBTaxCodes.Clear();
try
{
bool bDone = false;
int nStart = 0;
QueryService<q.TaxCode> qs = new QueryService<q.TaxCode>(SC);
if (QUSA)
{
_dtQBTaxCodes.Rows.Add(
new object[] {
TAX_CODE_ID_NO_TAX,
"< NOT TAXABLE >"
});
}
while (!bDone)
{
//GET first 1000
List<q.TaxCode> items = qs.ExecuteIdsQuery("select * from TaxCode startposition " + nStart.ToString() + " maxresults 1000").ToList<q.TaxCode>();
//IF WE GOT 1000 THEN GET THE NEXT 1000
if (items.Count < 999)
bDone = true;
else
nStart += 1000;
foreach (q.TaxCode i in items)
{
//add a record to the datatable
_dtQBTaxCodes.Rows.Add(
new object[] {
i.Id,
i.Name
});
}
}
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBTaxCodes: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion quickbooks TaxCodes
#region QuickBooks "Preferences"
private static q.Preferences _QBPreferences = null;
public static q.Preferences QBPreferences
{
get
{
return _QBPreferences;
}
}
//this is how you're supposed to check if inventory is enabled in this edition
//https://help.developer.intuit.com/s/question/0D50f00004n4Srg/what-are-the-possible-values-of-offeringsku-in-companyinfo-api-i-could-see-the-values-quickbooks-online-plus-and-quickbooks-plus-for-offeringsku-is-there-any-difference-between-them-if-yes-what-is-the-difference?s1oid=00DG0000000COk8&OpenCommentForEdit=1&s1nid=0DBG0000000blK7&emkind=chatterCommentNotification&s1uid=0050f000008K29c&emtm=1501196262050&fromEmail=1&s1ext=0
public static bool QBSupportsInventory
{
get
{
return _QBPreferences.ProductAndServicesPrefs.QuantityOnHand;
}
}
/// <summary>
/// Populate the cached qb data
/// </summary>
private static void PopulateQBPreferencesCache()
{
try
{
QueryService<q.Preferences> qs = new QueryService<q.Preferences>(SC);
//GET the one item
List<q.Preferences> items = qs.ExecuteIdsQuery("select * from preferences").ToList<q.Preferences>();
_QBPreferences = items[0];
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "PopulateQBPreferences: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion quickbooks TaxCodes
#region QuickBooks Online "Entitlements"
////Added for case 3294 / case 3296
//private static q.Entitlement _QBEntitlement = null;
//public static q.Entitlement QBEntitlement
//{
// get
// {
// return _QBEntitlement;
// }
//}
///// <summary>
///// Populate the cached qb data
///// </summary>
//private static void PopulateQBEntitlementCache()
//{
// try
// {
// QueryService<q.Entitlement> qs = new QueryService<q.Entitlement>(SC);
// //GET the one item
// List<q.Entitlement> items = qs.ExecuteIdsQuery("select * from entitlements").ToList<q.Entitlement>();
// _QBEntitlement = items[0];
// }
// catch (Exception ex)
// {
// IntegrationLog.Log(QBID, "PopulateQBEntitlement: Failed with exception:" + CrackException(ex));
// throw;
// }
//}
#endregion quickbooks TaxCodes
#endregion
#region Import Export/refresh
#region Import / refresh to AyaNova
#region Import / refresh customer
#region Refresh Customer
public static void RefreshAyaNovaClientFromQB(List<Guid> objectIDList)
{
PopulateQBClientCache();
foreach (Guid g in objectIDList)
{
try
{
Client c = Client.GetItemNoMRU(g);
RefreshAyaNovaClientFromQB(c);
if (c.IsSavable)
c.Save();
}
catch { };
}
}
public static void RefreshAyaNovaClientFromQB(Client c)
{
PopulateQBClientCache();
IntegrationMap im = QBOIntegration.Maps[c.ID];
if (im == null) return;//this client is not linked
DataRow dr = _dtQBClients.Rows.Find(im.ForeignID);
if (dr == null) return; //QBListID not found in client list?
CopyQBCustomerInfoToAyaNovaClient(dr, c);
string sName = dr["FullName"].ToString();
if (sName.Length > 255)
sName = sName.Substring(0, 255);
c.Name = sName;
}
#endregion refresh customer
#region Import customer
/// <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 void 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);
}
try
{
//already a client by that name
if (Client.Exists(Guid.Empty, dr["FullName"].ToString()))
{
alErrors.Add("ImportQBCustomer: " + dr["FullName"].ToString() + " already exists in AyaNova");
return;
}
//Import seems safe...
Client c = Client.NewItem();
c.Name = sName;//1
CopyQBCustomerInfoToAyaNovaClient(dr, c);
if (!c.IsSavable)
{
alErrors.Add("ImportQBCustomer: AyaNova won't allow import of " + c.Name + "\r\n" +
"Due to the following broken rules:\r\n" + c.GetBrokenRulesString());
return;
}
c = (Client)c.Save();
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = sName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Client;
m.LastSync = DateTime.Now;
m.ForeignID = QuickBooksID;
QBOIntegration = (Integration)QBOIntegration.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));
}
}
#endregion Import customer
#region Copy QB Customer info to AyaNova client
public static void CopyQBCustomerInfoToAyaNovaClient(DataRow dr, Client c)
{
Address a = (Address)dr["MailAddress"];
if (!string.IsNullOrEmpty(a.DeliveryAddress))
c.MailToAddress.DeliveryAddress = a.DeliveryAddress;//2
if (!string.IsNullOrEmpty(a.City))
c.MailToAddress.City = a.City;//3
if (!string.IsNullOrEmpty(a.StateProv))
c.MailToAddress.StateProv = a.StateProv;//4
if (!string.IsNullOrEmpty(a.Country))
c.MailToAddress.Country = a.Country;//5
if (!string.IsNullOrEmpty(a.Postal))
c.MailToAddress.Postal = a.Postal;//6
a = (Address)dr["StreetAddress"];
if (!string.IsNullOrEmpty(a.DeliveryAddress))
c.GoToAddress.DeliveryAddress = a.DeliveryAddress;//7
if (!string.IsNullOrEmpty(a.City))
c.GoToAddress.City = a.City;//8
if (!string.IsNullOrEmpty(a.StateProv))
c.GoToAddress.StateProv = a.StateProv;//9
if (!string.IsNullOrEmpty(a.Country))
c.GoToAddress.Country = a.Country;//10
if (!string.IsNullOrEmpty(a.Postal))
c.GoToAddress.Postal = a.Postal;//11
//Case 518
c.PopulateBothAddresses();
//Contact cn=c.Contacts.Add(RootObjectTypes.Client,c.ID);
if (!string.IsNullOrEmpty(dr["Contact"].ToString()))
c.Contact = dr["Contact"].ToString();
//Phone field
if (!string.IsNullOrEmpty(dr["Phone"].ToString()))
{
c.Phone1 = dr["Phone"].ToString();
}
//Fax field
if (!string.IsNullOrEmpty(dr["Fax"].ToString()))
{
c.Phone2 = dr["Fax"].ToString();//15
}
//AltPhone field
if (!string.IsNullOrEmpty(dr["AltPhone"].ToString()))
{
c.Phone3 = dr["AltPhone"].ToString();//16
}
//Email field
if (!string.IsNullOrEmpty(dr["Email"].ToString()))
{
c.Email = dr["Email"].ToString();//17
}
//Account number field
if (!string.IsNullOrEmpty(dr["Account"].ToString()))
{
c.AccountNumber = dr["Account"].ToString();//18
}
}
#endregion copy qb customer info to aya client
#endregion import refresh customer
#region Vendor
/// <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 void ImportQBVendor(string QuickBooksID, VendorTypes AsVendorType, 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()))
{
alErrors.Add("ImportQBVendor: " + dr["FullName"].ToString() + " already exists in AyaNova");
return;
}
//Import seems safe...
Vendor c = Vendor.NewItem();
c.VendorType = AsVendorType;
c.Name = sName;
Address a = (Address)dr["MailAddress"];
c.MailToAddress.DeliveryAddress = a.DeliveryAddress;
c.MailToAddress.City = a.City;
c.MailToAddress.StateProv = a.StateProv;
c.MailToAddress.Country = a.Country;
c.MailToAddress.Postal = a.Postal;
a = (Address)dr["StreetAddress"];
c.GoToAddress.DeliveryAddress = a.DeliveryAddress;
c.GoToAddress.City = a.City;
c.GoToAddress.StateProv = a.StateProv;
c.GoToAddress.Country = a.Country;
c.GoToAddress.Postal = a.Postal;
//Contact cn=c.Contacts.Add(RootObjectTypes.Vendor,c.ID);
c.Contact = dr["Contact"].ToString();
////if it's completely empty we'll substitute the word Contact instead
//if(qbcontact=="")
// cn.FirstName="Contact";
//else
//{
// //default it first
// cn.FirstName=qbcontact;
// string [] contactnames=null;
// if(qbcontact.IndexOf(" ")!=-1)
// contactnames=qbcontact.Replace(",","").Split(' ');//replace any commas if present
// else
// if(qbcontact.IndexOf(",")!=-1)
// contactnames=qbcontact.Split(',');
// //Quickbooks just has one field for contact so
// //we'll assume the english speaking tradition of firstname space lastname
// //if there is a space otherwise we'll assume it's just a first name if there are no spaces in it
// if(contactnames!=null && contactnames.GetLength(0)>1)
// {
// cn.FirstName=contactnames[0];
// cn.LastName=contactnames[1];
// }
//}
//Phone field
if (dr["Phone"].ToString() != "")
{
//ContactPhone cp=cn.Phones.Add(cn);
//cp.ContactPhoneType=ContactPhoneTypes.Business;
c.Phone1 = dr["Phone"].ToString();
//case 124
//cp.PhoneDefault=true;
}
//Fax field
if (dr["Fax"].ToString() != "")
{
//ContactPhone cp=cn.Phones.Add(cn);
//cp.ContactPhoneType=ContactPhoneTypes.Fax;
c.Phone2 = dr["Fax"].ToString();
}
//AltPhone field
if (dr["AltPhone"].ToString() != "")
{
//ContactPhone cp=cn.Phones.Add(cn);
//cp.ContactPhoneType=ContactPhoneTypes.Business;
c.Phone3 = dr["AltPhone"].ToString();
}
//Email field
if (dr["Email"].ToString() != "")
{
c.Email = dr["Email"].ToString();
}
//Account number
if (dr["Account"].ToString() != "")
{
c.AccountNumber = dr["Account"].ToString();
}
if (!c.IsSavable)
{
alErrors.Add("ImportQBVendor: AyaNova won't allow import of " + c.Name + "\r\n" +
"Due to the following broken rules:\r\n" + c.GetBrokenRulesString());
return;
}
c = (Vendor)c.Save();
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = sName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Vendor;
m.LastSync = DateTime.Now;
m.ForeignID = QuickBooksID;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportQBVendor: AyaNova won't allow import / link of " + sName + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion
#region Rate
/// <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 void ImportQBRate(string QuickBooksID, RateTypes AsRateType, Guid MostLikelyRateUnitChargeDescriptionID, 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();
if (sName.Length > 255)
{
alErrors.Add("ImportQBRate: QuickBooks Rate 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 Rate by that name
if (Rate.Exists(Guid.Empty, dr["FullName"].ToString()))
{
alErrors.Add("ImportQBRate: " + dr["FullName"].ToString() + " already exists in AyaNova");
return;
}
//Import seems safe...
Rates rates = Rates.GetItems(false);
Rate c = rates.Add();
c.RateType = AsRateType;
c.Name = sName;
c.Charge = (decimal)dr["Price"];
c.ContractRate = false;
c.Cost = (decimal)dr["Cost"];
c.Description = T(255, dr["SalesDesc"].ToString());
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 = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = sName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Rate;
m.LastSync = DateTime.Now;
m.ForeignID = QuickBooksID;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportQBRate: AyaNova won't allow import / link of " + sName + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion
#region Part
public static void RefreshAyaNovaPartFromQB(List<Guid> objectIDList)
{
PopulateQBItemCache();
foreach (Guid g in objectIDList)
{
try
{
Part c = Part.GetItem(g);
RefreshAyaNovaPartFromQB(c);
if (c.IsSavable)
c.Save();
}
catch { };
}
}
public static void RefreshAyaNovaPartFromQB(Part c)
{
PopulateQBItemCache();
IntegrationMap im = QBOIntegration.Maps[c.ID];
if (im == null) return;//this part is not linked
DataRow dr = _dtQBItems.Rows.Find(im.ForeignID);
if (dr == null) return; //QBListID not found in part list?
string sName = dr["FullName"].ToString();
if (sName.Length > 255)
sName = sName.Substring(0, 255);
c.PartNumber = sName;
c.Name = T(255, dr["SalesDesc"].ToString());
c.WholesalerID = AyaVendorForQBItem(im.ForeignID);
c.Retail = (decimal)dr["Price"];
c.Cost = (decimal)dr["Cost"];
}
/// <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 void 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();
if (sName.Length > 255)
{
alErrors.Add("ImportQBPart: QuickBooks Part name exceeds 255 character limit for AyaNova part number\r\n" +
"Name: " + dr["FullName"].ToString() + "\r\n" +
"was imported as: " + sName);
sName = sName.Substring(0, 255);
}
try
{
//already a Part by that number?
if (Part.Exists(Guid.Empty, dr["FullName"].ToString()))
{
alErrors.Add("ImportQBPart: Part number " + dr["FullName"].ToString() + " already exists in AyaNova");
return;
}
//Import seems safe...
Part c = Part.NewItem();
c.PartNumber = sName;
c.Name = T(255, dr["SalesDesc"].ToString());
//Set vendor ID based on prompted value
c.WholesalerID = _ImportPartToAyaNovaDefaultVendorId;
c.Retail = (decimal)dr["Price"];
c.Cost = (decimal)dr["Cost"];
if (!c.IsSavable)
{
alErrors.Add("ImportQBPart: AyaNova won't allow import of " + c.Name + "\r\n" +
"Due to the following broken rules:\r\n" + c.GetBrokenRulesString());
return;
}
//Save and get ready to provide the ID
c = (Part)c.Save();
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = sName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Part;
m.LastSync = DateTime.Now;
m.ForeignID = QuickBooksID;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportQBPart: AyaNova won't allow import / link of " + sName + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion
#endregion Import to AyaNova
#region Export / refresh to QuickBooks
#region Customer
#region Refresh to QB
public static void RefreshQBClientFromAyaNova(List<Guid> objectIDList)
{
foreach (Guid g in objectIDList)
{
try
{
Client c = Client.GetItemNoMRU(g);
RefreshQBClientFromAyaNova(c);
}
catch { };
}
}
/// <summary>
/// Refresh the indicated AyaNova client
/// to it's linked QuickBooks customer record
/// </summary>
public static void RefreshQBClientFromAyaNova(Client c)
{
////////////////////////////////////////////////////////////
//NEW API TODO:
//The DisplayName, Title, GivenName, MiddleName, FamilyName, Suffix, and PrintOnCheckName attributes must not contain colon (:), tab (\t), or newline (\n) characters.
//https://developer.intuit.com/docs/api/accounting/customer
IntegrationMap im = QBOIntegration.Maps[c.ID];
if (im == null) return;//this client is not linked
DataRow dr = _dtQBClients.Rows.Find(im.ForeignID);
if (dr == null) return; //QBListID not found in client list?
try
{
string strSyncToken = GetQBCustomerSyncToken(im.ForeignID);
if (string.IsNullOrEmpty(strSyncToken))
{
MessageBox.Show("RefreshQBClientFromAyaNova -> Error: unable to fetch SyncToken from QuickBooks Online. No changes made.");
return;
}
q.Customer ca = new q.Customer();
CopyAyClientToQBOCustomer(c, ca);
ca.Id = im.ForeignID;
ca.SyncToken = strSyncToken;
//case 519
ca.SalesTermRef = new q.ReferenceType();
ca.SalesTermRef.Value = QDat.TermsDefault;
//----------- UPDATE VIA API -------------
DataService service = new DataService(SC);
q.Customer resultCustomer = service.Update(ca) as q.Customer;
//after online record update, in turn update some local data:
im.Name = resultCustomer.DisplayName;
im.LastSync = DateTime.Now;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
MessageBox.Show("RefreshQBClientFromAyaNova: QuickBooks won't allow refresh of " + c.Name + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion Refresh to qb
#region Copy customer from AY to QB
/// <summary>
/// Used for both update and create
/// </summary>
/// <param name="c"></param>
/// <param name="im"></param>
/// <param name="strSyncToken"></param>
/// <param name="ca"></param>
/// <param name="sName"></param>
private static void CopyAyClientToQBOCustomer(Client c, q.Customer ca)
{
ca.sparse = true;
ca.sparseSpecified = true;
string sName = T(100, c.Name); //100 char limit in QBO
//CLEAN THE NAME TO MEET QBO STANDARDS
sName = sName.Replace(':', '-').Replace('\t', ' ').Replace('\n', ' ');
ca.DisplayName = sName;
ca.ContactName = c.Contact;
ca.PrimaryPhone = new q.TelephoneNumber();
if (c.Phone1 != null)
ca.PrimaryPhone.FreeFormNumber = T(21, c.Phone1);
else
ca.PrimaryPhone.FreeFormNumber = string.Empty;
ca.Fax = new q.TelephoneNumber();
ca.Fax.FreeFormNumber = T(21, c.Phone2);
ca.AlternatePhone = new q.TelephoneNumber();
ca.AlternatePhone.FreeFormNumber = T(21, c.Phone3);
ca.PrimaryEmailAddr = new q.EmailAddress();
ca.PrimaryEmailAddr.Address = T(100, c.Email);
#region Addresses
bool bHasBillAddress = false;
ca.BillAddr = new q.PhysicalAddress();
if (c.MailToAddress.DeliveryAddress != "")
{
bHasBillAddress = true;
//QBO Max 2000 chars for all 5 line addresses combined but 500 per line of the 5 lines (? bizarre)
string[] sad = T(2000, c.MailToAddress.DeliveryAddress).Replace("\r\n", "\n").Split('\n');
int sadLines = sad.GetLength(0);
if (sadLines > 0)
ca.BillAddr.Line1 = T(500, sad[0].TrimEnd());
if (sadLines > 1)
ca.BillAddr.Line2 = T(500, sad[1].TrimEnd());
if (sadLines > 2)
ca.BillAddr.Line3 = T(500, sad[2].TrimEnd());
if (sadLines > 3)
ca.BillAddr.Line4 = T(500, sad[3].TrimEnd());
if (sadLines > 4)
ca.BillAddr.Line5 = T(500, sad[4].TrimEnd());
}
ca.BillAddr.City = T(255, c.MailToAddress.City);
ca.BillAddr.Country = T(255, c.MailToAddress.Country);
ca.BillAddr.PostalCode = T(31, c.MailToAddress.Postal);
ca.BillAddr.CountrySubDivisionCode = T(255, c.MailToAddress.StateProv);
bool bHasShipAddress = false;
ca.ShipAddr = new q.PhysicalAddress();
if (c.GoToAddress.DeliveryAddress != "")
{
bHasShipAddress = true;
//QBO Max 2000 chars for all 5 line addresses combined but 500 per line of the 5 lines (? bizarre)
string[] sad = T(2000, c.GoToAddress.DeliveryAddress).Replace("\r\n", "\n").Split('\n');
int sadLines = sad.GetLength(0);
if (sadLines > 0)
ca.ShipAddr.Line1 = T(500, sad[0].TrimEnd());
if (sadLines > 1)
ca.ShipAddr.Line2 = T(500, sad[1].TrimEnd());
if (sadLines > 2)
ca.ShipAddr.Line3 = T(500, sad[2].TrimEnd());
if (sadLines > 3)
ca.ShipAddr.Line4 = T(500, sad[3].TrimEnd());
if (sadLines > 4)
ca.ShipAddr.Line5 = T(500, sad[4].TrimEnd());
}
ca.ShipAddr.City = T(255, c.GoToAddress.City);
ca.ShipAddr.Country = T(255, c.GoToAddress.Country);
ca.ShipAddr.PostalCode = T(31, c.GoToAddress.Postal);
ca.ShipAddr.CountrySubDivisionCode = T(255, c.GoToAddress.StateProv);
#endregion
//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.ShipAddr, ca.BillAddr);
}
else if (bHasBillAddress && !bHasShipAddress)
{
CopyAddress(ca.BillAddr, ca.ShipAddr);
}
}
ca.AcctNum = c.AccountNumber;
}
#endregion
#region Export to QB
//this code is called from AyaNova via plugin command menu on selected clients
public static void ImportAyaClient(List<Guid> objectIDList)
{
//case 3278 ask for taxes
//WARNING: BEFORE MAKING CHANGES HERE SEE
//MAP.CS line 1606 same code
//AND QBOI.CS single client command code line 378
//Only USA uses one tax code for the whole customer
//other regions are taxed by item and set with items
if (Util.QUSA)
{
SetQBImportCustomerTaxCode s = new SetQBImportCustomerTaxCode();
if (s.ShowDialog() != DialogResult.OK)
{
s.Dispose();
return;
}
s.Dispose();
}
ArrayList alErrors = new ArrayList();
foreach (Guid g in objectIDList)
{
try
{
ImportAyaClient(g, alErrors);
}
catch { };
}
if (alErrors.Count != 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("Export 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 client
/// to QuickBooks customer record
/// </summary>
/// <param name="ClientID"></param>
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
public static void ImportAyaClient(Guid ClientID, ArrayList alErrors)
{
////////////////////////////////////////////////////////////
//NEW API TODO:
//The DisplayName, Title, GivenName, MiddleName, FamilyName, Suffix, and PrintOnCheckName attributes must not contain colon (:), tab (\t), or newline (\n) characters.
if (!Client.Exists(ClientID, ""))
{
alErrors.Add("ImportAyaClient: Client not found in AyaNova (deleted recently?) (" + ClientID.ToString() + ")");
return;
}
Client c = Client.GetItem(ClientID);
try
{
q.Customer ca = new q.Customer();
CopyAyClientToQBOCustomer(c, ca);
if (ca.DisplayName != c.Name)
{
alErrors.Add("ImportAyaClient: AyaNova client name needed to be changed to meet QBOnline standards\r\n" +
"Name: " + c.Name + "\r\n" +
"will be imported as: " + ca.DisplayName);
}
//create a new row to add to the client cache datatable
DataRow dr = _dtQBClients.NewRow();
dr["FullName"] = ca.DisplayName;
dr["MailAddress"] = new Address();
dr["StreetAddress"] = new Address();
dr["Phone"] = "";
dr["Fax"] = "";
dr["AltPhone"] = "";
dr["Email"] = c.Email;
dr["Contact"] = "";
dr["Created"] = DateTime.MinValue;//flag indicating fresh record incomplete
dr["Modified"] = DateTime.MinValue;//ditto
dr["Account"] = "";
//case 519
ca.SalesTermRef = new q.ReferenceType();
ca.SalesTermRef.Value = QDat.TermsDefault;
//Only USA QB Online companies have a default tax code
//non USA do it by item only and this field is not even present
if (QUSA)
{
if (QDat.CustomerTaxCodeDefault != TAX_CODE_ID_NO_TAX)
{
ca.DefaultTaxCodeRef = new q.ReferenceType();
ca.DefaultTaxCodeRef.Value = QDat.CustomerTaxCodeDefault;
ca.Taxable = true;
ca.TaxableSpecified = true;
}
else
{
ca.Taxable = false;
ca.TaxableSpecified = true;
}
}
//============ ADD VIA API================================
DataService service = new DataService(SC);
q.Customer resultCustomer = service.Add(ca) as q.Customer;
//========================================================
//catch the new ID for the QB Item
dr["ID"] = resultCustomer.Id;
//add the new row for the newly imported object
_dtQBClients.Rows.Add(dr);
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = resultCustomer.DisplayName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Client;
m.LastSync = DateTime.Now;
m.ForeignID = resultCustomer.Id;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportAyaClient: QuickBooks won't allow import of " + c.Name + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion export to qb
/// <summary>
/// Copy the contents of one address to the other
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
private static void CopyAddress(q.PhysicalAddress from, q.PhysicalAddress to)
{
to.City = from.City;
to.Country = from.Country;
to.CountryCode = from.CountryCode;
to.CountrySubDivisionCode = from.CountrySubDivisionCode;
to.Lat = from.Lat;
to.Line1 = from.Line1;
to.Line2 = from.Line2;
to.Line3 = from.Line3;
to.Line4 = from.Line4;
to.Line5 = from.Line5;
to.Long = from.Long;
to.Note = from.Note;
to.PostalCode = from.PostalCode;
to.PostalCodeSuffix = from.PostalCodeSuffix;
to.Tag = from.Tag;
//CopyStringIfSet(from.City, to.City);
//CopyStringIfSet(from.Country, to.Country);
//CopyStringIfSet(from.CountryCode, to.CountryCode);
//CopyStringIfSet(from.CountrySubDivisionCode, to.CountrySubDivisionCode);
//CopyStringIfSet(from.Lat, to.Lat);
//CopyStringIfSet(from.Line1, to.Line1);
//CopyStringIfSet(from.Line2, to.Line2);
//CopyStringIfSet(from.Line3, to.Line3);
//CopyStringIfSet(from.Line4, to.Line4);
//CopyStringIfSet(from.Line5, to.Line5);
//CopyStringIfSet(from.Long, to.Long);
//CopyStringIfSet(from.Note, to.Note);
//CopyStringIfSet(from.PostalCode, to.PostalCode);
//CopyStringIfSet(from.PostalCodeSuffix, to.PostalCodeSuffix);
//CopyStringIfSet(from.Tag, to.Tag);
}
///// <summary>
///// Convenience helper for copying address fields above
///// </summary>
///// <param name="from"></param>
///// <param name="to"></param>
//private static void CopyStringIfSet(string from, string to)
//{
// if (!string.IsNullOrWhiteSpace(from))
// to = from;
//}
#endregion customer
#region Vendor
#region Copy Vendor from AY to QB
/// <summary>
/// Used for both update and create
/// </summary>
/// <param name="c"></param>
/// <param name="im"></param>
/// <param name="strSyncToken"></param>
/// <param name="ca"></param>
/// <param name="sName"></param>
private static void CopyAyVendorToQBOVendor(Vendor c, q.Vendor ca)
{
ca.sparse = true;
ca.sparseSpecified = true;
string sName = c.Name;
//CLEAN THE NAME TO MEET QBO STANDARDS
sName = sName.Replace(':', '-').Replace('\t', ' ').Replace('\n', ' ');
ca.DisplayName = sName;
ca.ContactName = c.Contact;
ca.PrimaryPhone = new q.TelephoneNumber();
if (c.Phone1 != null)
ca.PrimaryPhone.FreeFormNumber = c.Phone1;
else
ca.PrimaryPhone.FreeFormNumber = string.Empty;
ca.Fax = new q.TelephoneNumber();
ca.Fax.FreeFormNumber = c.Phone2;
ca.AlternatePhone = new q.TelephoneNumber();
ca.AlternatePhone.FreeFormNumber = c.Phone3;
ca.PrimaryEmailAddr = new q.EmailAddress();
ca.PrimaryEmailAddr.Address = c.Email;
#region Addresses
ca.BillAddr = new q.PhysicalAddress();
if (c.MailToAddress.DeliveryAddress != "")
{
string[] sad = c.MailToAddress.DeliveryAddress.Replace("\r\n", "\n").Split('\n');
int sadLines = sad.GetLength(0);
if (sadLines > 0)
ca.BillAddr.Line1 = sad[0].TrimEnd();
if (sadLines > 1)
ca.BillAddr.Line2 = sad[1].TrimEnd();
if (sadLines > 2)
ca.BillAddr.Line3 = sad[2].TrimEnd();
if (sadLines > 3)
ca.BillAddr.Line4 = sad[3].TrimEnd();
if (sadLines > 4)
ca.BillAddr.Line5 = sad[4].TrimEnd();
}
ca.BillAddr.City = c.MailToAddress.City;
ca.BillAddr.Country = c.MailToAddress.Country;
ca.BillAddr.PostalCode = c.MailToAddress.Postal;
ca.BillAddr.CountrySubDivisionCode = c.MailToAddress.StateProv;
//NO SHIP ADDRESS IN VENDOR FOR QBO (not in api docs)
#endregion
ca.AcctNum = c.AccountNumber;
}
#endregion
/// <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 void ImportAyaVendor(Guid VendorID, ArrayList alErrors)
{
if (!Vendor.Exists(VendorID, ""))
{
alErrors.Add("ImportAyaVendor: Vendor not found in AyaNova (deleted recently?) (" + VendorID.ToString() + ")");
return;
}
Vendor c = Vendor.GetItem(VendorID);
try
{
q.Vendor ca = new q.Vendor();
CopyAyVendorToQBOVendor(c, ca);
if (ca.DisplayName != c.Name)
{
alErrors.Add("ImportAyaVendor: AyaNova Vendor name needed to be changed to meet QBOnline standards\r\n" +
"Name: " + c.Name + "\r\n" +
"will be imported as: " + ca.DisplayName);
}
//create a new row to add to the client cache datatable
DataRow dr = _dtQBVendors.NewRow();
dr["FullName"] = ca.DisplayName;
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"] = "";
//============ ADD VIA API================================
DataService service = new DataService(SC);
q.Vendor resultVendor = service.Add(ca) as q.Vendor;
//========================================================
//catch the new ID for the QB Item
dr["ID"] = resultVendor.Id;
//add the new row for the newly imported object
_dtQBVendors.Rows.Add(dr);
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = resultVendor.DisplayName;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Vendor;
m.LastSync = DateTime.Now;
m.ForeignID = resultVendor.Id;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportAyaVendor: QuickBooks won't allow import of " + c.Name + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion vendor
#region Part case 632
#region Copy Part from AY to QB
private static void CopyAyPartToQBOItem(Part c, q.Item ca)
{
ca.sparse = true;
ca.sparseSpecified = true;
//CLEAN THE NAME TO MEET QBO STANDARDS
ca.Name = T(100, c.PartNumber.Replace(':', '-').Replace('\t', ' ').Replace('\n', ' '));
ca.Description = T(4000, PartPickList.GetOnePart(c.ID)[0].DisplayName(AyaBizUtils.GlobalSettings.DefaultPartDisplayFormat));
//Here we need to set the vendor ID
//parseable from purchase desc.
if (c.WholesalerID != Guid.Empty)
{
if (AyaVendorList.Contains(c.WholesalerID))
{
ca.PurchaseDesc = "AyaNova Vendor:" + Util.AyaVendorList[c.WholesalerID].Name;
}
}
ca.UnitPrice = c.Retail;
ca.PurchaseCost = c.Cost;
}
#endregion
/// <summary>
/// Refresh the list of AyaNova parts to their linked QuickBooks item records
/// </summary>
/// <param name="objectIDList"></param>
public static void RefreshQBPartFromAyaNova(List<Guid> objectIDList)
{
foreach (Guid g in objectIDList)
{
try
{
Part c = Part.GetItem(g);
RefreshQBPartFromAyaNova(c);
}
catch { };
}
}
/// <summary>
/// Refresh the indicated AyaNova part
/// to it's linked QuickBooks item record
/// </summary>
public static void RefreshQBPartFromAyaNova(Part c)
{
IntegrationMap im = QBOIntegration.Maps[c.ID];
if (im == null) return;//this part is not linked
DataRow dr = _dtQBItems.Rows.Find(im.ForeignID);
if (dr == null) return; //QBListID not found in part list?
try
{
string strSyncToken = GetInventoryItemSyncToken(im.ForeignID);
if (string.IsNullOrEmpty(strSyncToken))
{
MessageBox.Show("RefreshQBPartFromAyaNova -> Error: unable to fetch SyncToken from QuickBooks Online. No changes made.");
return;
}
//----CREATE ITEM, SET API STUFF -------
q.Item ca = new q.Item();
ca.Id = im.ForeignID;
ca.SyncToken = strSyncToken;
CopyAyPartToQBOItem(c, ca);
//----------- UPDATE VIA API -------------
DataService service = new DataService(SC);
q.Item resultItem = service.Update(ca) as q.Item;
//---------AFTER UPDATE----------
im.LastSync = DateTime.Now;
im.Name = resultItem.Name;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
CopyableMessageBox cp = new CopyableMessageBox(
"RefreshQBPartFromAyaNova: QuickBooks Online won't allow import of " + c.PartNumber + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
cp.ShowDialog();
}
}
#region AyaNova Part to Quickbooks
/// <summary>
/// Import a list of ayanova parts to QuickBooks
/// </summary>
/// <param name="objectIDList"></param>
public static void ImportAyaPart(List<Guid> objectIDList, bool ImportAsTypeInventoryItem)
{
ArrayList alErrors = new ArrayList();
foreach (Guid g in objectIDList)
{
try
{
ImportAyaPart(g, alErrors, ImportAsTypeInventoryItem);
}
catch { };
}
if (alErrors.Count != 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("Export AyaNova parts to QuickBooks Online 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 Online item record
/// </summary>
/// <param name="VendorID"></param>
/// <param name="alErrors">An arraylist to hold strings indicating errors on fail</param>
public static void ImportAyaPart(Guid PartID, ArrayList alErrors, bool ImportAsTypeInventoryItem)//case 3296
{
if (!Part.Exists(PartID, ""))
{
alErrors.Add("ImportAyaPart: Part not found in AyaNova (deleted recently?) (" + PartID.ToString() + ")");
return;
}
//Vet the tax code if non USA
if (!QUSA)
{
if (string.IsNullOrWhiteSpace(_ImportPartToQBDefaultTaxCodeId))
{
alErrors.Add("ImportAyaPart: There is no tax code selected, can't export to QB Online");
return;
}
}
Part c = Part.GetItem(PartID);
try
{
q.Item ca = new q.Item();
CopyAyPartToQBOItem(c, ca);
//create a new row to add to the client cache datatable
DataRow dr = _dtQBItems.NewRow();
dr["FullName"] = ca.Name;
dr["Type"] = qbitemtype.Inventory;
dr["Price"] = c.Retail;
dr["Cost"] = c.Cost;
dr["SalesDesc"] = ca.Description;
dr["ReorderPoint"] = 0;
dr["Modified"] = DateTime.MinValue;
dr["VendorID"] = c.WholesalerID.ToString();
//------------------------
//Set the qb item values not set by copy earlier
//since this is create we need to set some other stuff
//REQUIRED ACCOUNTS
//From the docs: https://developer.intuit.com/docs/api/accounting/item
//IncomeAccountRef: required for Inventory and Service item types
//ExpenseAccountRef: required for Inventory, NonInventory, and Service item types
//AssetAccountRef: required for Inventory item types
//Already know a rate imports with only income account ref set,
//so despite docs assuming this is the case for a non-inventory part until otherwise
ca.IncomeAccountRef = new q.ReferenceType();
ca.IncomeAccountRef.Value = QDat.QBInventoryIncomeAccountReference;
//TAXES
if (QUSA)
{
//Taxable
ca.Taxable = Util.QDat.ImportItemTaxable;
ca.TaxableSpecified = true;
}
else
{
ca.SalesTaxCodeRef = new q.ReferenceType();
ca.SalesTaxCodeRef.Value = _ImportPartToQBDefaultTaxCodeId;
}
//case 3296
if (ImportAsTypeInventoryItem)
{
ca.Type = q.ItemTypeEnum.Inventory;
ca.TypeSpecified = true;
//Inventory needs cogs and asset
//set Cogs as ExpenseAccount not COGS account which is not present in online
//even though it's surfaced in the API for desktop version
ca.ExpenseAccountRef = new q.ReferenceType();
ca.ExpenseAccountRef.Value = QDat.QBInventoryCOGSAccountRef;
ca.AssetAccountRef = new q.ReferenceType();
ca.AssetAccountRef.Value = QDat.QBInventoryAssetAccountRef;
//in qbo you have to state if you want q on hand tracked or not when creating the item, cannot be reversed
ca.TrackQtyOnHand = true;
ca.TrackQtyOnHandSpecified = true;
PartInventoryValuesFetcher pbw = PartInventoryValuesFetcher.GetItem(c.ID);
ca.QtyOnHand = pbw.QuantityOnHand;
ca.QtyOnHandSpecified = true;
ca.InvStartDate = DateTime.Now;
ca.InvStartDateSpecified = true;
ca.ReorderPoint = pbw.MinStockLevel;
ca.ReorderPointSpecified = true;
ca.UnitPrice = c.Retail;
ca.UnitPriceSpecified = true;
ca.PurchaseCost = c.Cost;
ca.PurchaseCostSpecified = true;
}
else
{
//QBO requires other type if not tracking inventory
ca.Type = q.ItemTypeEnum.NonInventory;
ca.TypeSpecified = true;
ca.TrackQtyOnHand = false;
ca.TrackQtyOnHandSpecified = true;
ca.UnitPrice = c.Retail;
ca.UnitPriceSpecified = true;
}
//============ ADD VIA API================================
DataService service = new DataService(SC);
q.Item resultItem = service.Add(ca) as q.Item;
/*
* ValidationException was thrown.Details:Required parameter An inventory cost-of-goods-sold account is required if you are tracking inventory quantities for this product. is missing in the request
* ValidationException was thrown.Details:Business Validation Error: When you create an item, if Track quantity on hand is turned off, the item cannot be of type Inventory.
* ValidationException was thrown.Details:Value should be a valid date value:Supplied value: As of date for initial quantity on hand is required.
*/
//========================================================
//catch the new ID for the QB Item
dr["ID"] = resultItem.Id;
//add the new row for the newly imported object
_dtQBItems.Rows.Add(dr);
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = resultItem.Name;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Part;
m.LastSync = DateTime.Now;
m.ForeignID = resultItem.Id;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportAyaPart: QuickBooks Online won't allow import of " + c.PartNumber + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion AyaNova part to Quickbooks
#endregion part
#region Rate case 632
private static void CopyAyRateToQBOItem(RatePickList.RatePickListInfo c, q.Item ca)
{
ca.sparse = true;
ca.sparseSpecified = true;
//CLEAN THE NAME TO MEET QBO STANDARDS
ca.Name = T(100, c.Name.Replace(':', '-').Replace('\t', ' ').Replace('\n', ' '));
ca.Description = T(4000, c.Description);
ca.PurchaseDesc = ca.Description;
ca.UnitPrice = c.Charge;
ca.PurchaseCost = c.Cost;
}
/// <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 void ImportAyaRate(Guid RateID, RatePickList ratelist, ArrayList alErrors)
{
if (!ratelist.Contains(RateID))
{
alErrors.Add("ImportAyaRate: Rate not found in AyaNova (deleted recently?) (" + RateID.ToString() + ")");
return;
}
//Vet the tax code if non USA
if (!QUSA)
{
if (string.IsNullOrWhiteSpace(_ImportRateToQBDefaultTaxCodeId))
{
alErrors.Add("ImportAyaRate: There is no tax code selected, can't export to QB Online");
return;
}
}
RatePickList.RatePickListInfo c = ratelist[RateID];
try
{
q.Item ca = new q.Item();
CopyAyRateToQBOItem(c, ca);
//create a new row to add to the client cache datatable
DataRow dr = _dtQBItems.NewRow();
dr["FullName"] = ca.Name;
dr["Type"] = qbitemtype.Service;
dr["Price"] = c.Charge;
dr["Cost"] = c.Cost;
dr["SalesDesc"] = ca.Description;
dr["ReorderPoint"] = 0;
dr["Modified"] = DateTime.MinValue;
dr["VendorID"] = "";
//------------------------
//Set the qb item values
ca.IncomeAccountRef = new q.ReferenceType();
ca.IncomeAccountRef.Value = QDat.QBServiceIncomeAccountRef;
ca.Type = q.ItemTypeEnum.Service;
ca.TypeSpecified = true;
ca.UnitPrice = c.Charge;
ca.UnitPriceSpecified = true;
ca.PurchaseCost = c.Cost;
ca.PurchaseCostSpecified = true;
//TAXES
if (QUSA)
{
//Taxable
ca.Taxable = Util.QDat.ImportItemTaxable;
ca.TaxableSpecified = true;
}
else
{
ca.SalesTaxCodeRef = new q.ReferenceType();
ca.SalesTaxCodeRef.Value = _ImportRateToQBDefaultTaxCodeId;
}
//============ ADD VIA API================================
DataService service = new DataService(SC);
q.Item resultItem = service.Add(ca) as q.Item;
//========================================================
//catch the new ID for the QB Item
dr["ID"] = resultItem.Id;
//add the new row for the newly imported object
_dtQBItems.Rows.Add(dr);
//Link
IntegrationMap m = QBOIntegration.Maps.Add(QBOIntegration);
m.Name = resultItem.Name;
m.RootObjectID = c.ID;
m.RootObjectType = RootObjectTypes.Rate;
m.LastSync = DateTime.Now;
m.ForeignID = resultItem.Id;
QBOIntegration = (Integration)QBOIntegration.Save();
}
catch (Exception ex)
{
alErrors.Add("ImportAyaRate: QuickBooks Online won't allow import of " + c.Name + "\r\n" +
"Due to the following error:\r\n" + CrackException(ex));
}
}
#endregion rate
private static string T(int nLength, string s)
{
if (s == null || s == "") return "";
if (s.Length <= nLength) return s;
else
return s.Substring(0, nLength);
}
#endregion export to quickbooks
#endregion importexport
#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 struct MisMatch
{
//public Guid WorkorderID;
public Guid mRootObjectID;
public RootObjectTypes mObjectType;
public string mName;
public MisMatchReason mReason;
public decimal mAyaPrice;
public decimal mQBPrice;
public Guid mWorkorderItemPartID;
public string mQBListID;
public Guid RootObjectID
{
get
{
return mRootObjectID;
}
}
public RootObjectTypes ObjectType
{
get
{
return mObjectType;
}
}
public string Name
{
get
{
return mName;
}
}
public MisMatchReason Reason
{
get
{
return mReason;
}
}
public decimal AyaPrice
{
get
{
return mAyaPrice;
}
}
public decimal QBPrice
{
get
{
return mQBPrice;
}
}
public Guid WorkorderItemPartID
{
get
{
return mWorkorderItemPartID;
}
}
public string QBListID
{
get
{
return mQBListID;
}
}
}
/// <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 arraylist of mismatch objects</param>
/// <param name="PriceOverrides">An array of GUID 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 bool ScanLinksOK(Guid WorkorderID, ArrayList MisMatches, ArrayList PriceOverrides)
{
bool bReturn = true;
bool bSomethingToInvoice = false;
Workorder w = Workorder.GetItem(WorkorderID);
//Client ok?
if (!QBOIntegration.Maps.Contains(w.ClientID))
{
bReturn = false;
AddMisMatch(AyaClientList[w.ClientID].Name, w.ClientID, RootObjectTypes.Client, MisMatchReason.NotLinkedToQB, MisMatches);
}
//Service rates:
foreach (WorkorderItem wi in w.WorkorderItems)
{
#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 (wl.ServiceRateID == Guid.Empty)
throw new System.ApplicationException("ERROR: Workorder " + w.WorkorderService.ServiceNumber.ToString() + " has a labor item with no rate selected\r\n" +
"This is a serious problem for QBOI and needs to be rectified before QBOI can be used.\r\n");
if (!QBOIntegration.Maps.Contains(wl.ServiceRateID))
{
bReturn = false;
AddMisMatch(AyaRateList[wl.ServiceRateID].Name, wl.ServiceRateID, RootObjectTypes.Rate, 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 guid.empty
//it's possible that some users have not selected a rate on the workorder
if (wt.TravelRateID == Guid.Empty)
throw new System.ApplicationException("ERROR: Workorder " + w.WorkorderService.ServiceNumber.ToString() + " has a travel item with no rate selected\r\n" +
"This is a serious problem for QBOI and needs to be rectified before QBOI can be used.\r\n");
if (!QBOIntegration.Maps.Contains(wt.TravelRateID))
{
bReturn = false;
AddMisMatch(AyaRateList[wt.TravelRateID].Name, wt.TravelRateID, RootObjectTypes.Rate, 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
if (!QBOIntegration.Maps.Contains(wp.PartID) || QBItems.Rows.Find(QBOIntegration.Maps[wp.PartID].ForeignID) == null)
{
bReturn = false;
//Changed: 21-June-2006 to use display formatted name
AddMisMatch(AyaPartList[wp.PartID].DisplayName(Util.GlobalSettings.DefaultPartDisplayFormat), wp.PartID, RootObjectTypes.Part, MisMatchReason.NotLinkedToQB, MisMatches);
}
else
{
//check the price
if (!PriceOverrides.Contains(wp.ID))
{
decimal qbPrice = (decimal)QBItems.Rows.Find(QBOIntegration.Maps[wp.PartID].ForeignID)["Price"];
//------------DISCOUNT-----------------
string disco = "";
//Added:20-July-2006 to incorporate discounts on parts into qb invoice
decimal charge;
//Changed: 18-Nov-2006 CASE 158
//this is all wrong, it was multiplying price by quantity to calculate charge when it shouldn't
//removed quanty * price in next line to just price
charge = decimal.Round(wp.Price, 2, MidpointRounding.AwayFromZero);
charge = charge - (decimal.Round(charge * wp.Discount, 2, MidpointRounding.AwayFromZero));
if (wp.Discount != 0)
{
disco = " (Price " + wp.Price.ToString("c") + " discounted on workorder " + wp.Discount.ToString("p") + ") \r\n";
}
//-----------------------------
//It's a match, let's see if the price matches as well
if (charge != qbPrice)
{
bReturn = false;
AddMisMatch("WO: " + w.WorkorderService.ServiceNumber.ToString() + disco + " Part: " + AyaPartList[wp.PartID].DisplayName(Util.GlobalSettings.DefaultPartDisplayFormat),//Changed: 21-June-2006 to use display formatted name
wp.PartID, RootObjectTypes.Part, MisMatchReason.PriceDifferent, MisMatches, qbPrice, charge, wp.ID,
QBOIntegration.Maps[wp.PartID].ForeignID);
}
}
}
}
#endregion
#region Outside service charges
if (wi.HasOutsideService)
{
if (wi.OutsideService.RepairPrice != 0 || wi.OutsideService.ShippingPrice != 0)
{
bSomethingToInvoice = true;
//there is something billable, just need to make sure
//that there is a QB charge defined for outside service
if (QDat.OutsideServiceChargeAs == null ||
QDat.OutsideServiceChargeAs == "" ||
!QBItems.Rows.Contains(QDat.OutsideServiceChargeAs))
{
bReturn = false;
AddMisMatch("Outside service", Guid.Empty, RootObjectTypes.WorkorderItemOutsideService, MisMatchReason.NotLinkedToQB, MisMatches);
}
}
}
#endregion
#region Workorder item loan charges
if (wi.HasLoans)
{
foreach (WorkorderItemLoan wil in wi.Loans)
{
if (wil.Charges != 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 (QDat.WorkorderItemLoanChargeAs == null ||
QDat.WorkorderItemLoanChargeAs == "" ||
!QBItems.Rows.Contains(QDat.WorkorderItemLoanChargeAs))
{
bReturn = false;
AddMisMatch("Workorder item loan", Guid.Empty, RootObjectTypes.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, RootObjectTypes.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, RootObjectTypes.Nothing, MisMatchReason.NothingToInvoice, MisMatches);
}
return bReturn;
}
private static void AddMisMatch(string Name, Guid RootObjectID, RootObjectTypes RootObjectType, MisMatchReason Reason, ArrayList Mismatches)
{
AddMisMatch(Name, RootObjectID, RootObjectType, Reason, Mismatches, 0m, 0m, Guid.Empty, "");
}
private static void AddMisMatch(string Name, Guid RootObjectID, RootObjectTypes RootObjectType,
MisMatchReason Reason, ArrayList Mismatches, decimal QBPrice, decimal AyaPrice, Guid 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 (object o in Mismatches)
{
MisMatch m = (MisMatch)o;
//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.RootObjectID == RootObjectID && m.ObjectType == RootObjectType)
{
bDuplicate = true;
break;
}
}
}
if (!bDuplicate)
{
MisMatch m = new MisMatch();
m.mName = Name;
m.mRootObjectID = RootObjectID;
m.mObjectType = RootObjectType;
m.mReason = Reason;
m.mAyaPrice = AyaPrice;
m.mQBPrice = QBPrice;
m.mWorkorderItemPartID = WorkorderItemPartID;
m.mQBListID = QBListID;
Mismatches.Add(m);
}
}
#endregion wo_mismatch_scan
#region Invoice
/// <summary>
/// Invoice out the workorders contained in the array list
/// as one single invoice
///
/// Put in descriptive text as necessary and when sucessfully invoiced
/// set workorders in list to status selected for post invoicing and
/// set invoice number and then close them
/// </summary>
/// <param name="alWorkorders"></param>
/// <returns></returns>
public static void Invoice(ArrayList alWorkorders, ArrayList alErrors)
{
//Example of how to make an invoice using .net api
//https://gist.github.com/IntuitDeveloperRelations/6500373
if (alWorkorders.Count == 0) return;
try
{
//Create invoice
q.Invoice i = new q.Invoice();
//Private note?
i.PrivateNote = "Imported from AyaNova by: " + Thread.CurrentPrincipal.Identity.Name + " @ " + DateTime.Now.ToString();
//set sales terms
if (!string.IsNullOrWhiteSpace(QDat.TermsDefault))
{
i.SalesTermRef = new q.ReferenceType();
i.SalesTermRef.Value = QDat.TermsDefault;
}
//a list of Line objects to hold the details of the invoice
List<q.Line> AllInvoiceLines = new List<q.Line>();
//a string to hold the memo field
StringBuilder sbMemo = new StringBuilder();
if (QDat.SetMemoField)
{
if (alWorkorders.Count > 1)
{
sbMemo.Append(" Workorders: ");
}
else
{
sbMemo.Append(" Workorder: ");
}
}
//Keep track if first time through so
//we can set invoice header stuff based on first workorder's client etc
bool bFirstLoop = true;
//Loop through alworkorders
foreach (object o in alWorkorders)
{
Workorder w = Workorder.GetItem((Guid)o);
if (bFirstLoop)
{
bFirstLoop = false;
#region Set header info for invoice
//Set client
string QBCustomerId = QBOIntegration.Maps[w.ClientID].ForeignID;
i.CustomerRef = new q.ReferenceType();
i.CustomerRef.Value = QBCustomerId;
//Get cached client datarow
DataRow drCustomerCache = QBClients.Rows.Find(QBCustomerId);
//if USA set whole invoice taxes
if (QUSA)
{
bool bTaxable = (bool)drCustomerCache["Taxable"];
string sTaxCode = drCustomerCache["TaxId"].ToString();
if (bTaxable && sTaxCode != TAX_CODE_ID_NO_TAX)
{
//Set a tax
q.TxnTaxDetail txnTaxDetail = new q.TxnTaxDetail();
txnTaxDetail.TxnTaxCodeRef = new q.ReferenceType()
{
Value = sTaxCode
};
i.TxnTaxDetail = txnTaxDetail;
}
}
//Set email values
//Line description
if (QBClients.Rows.Contains(i.CustomerRef.Value))
{
//Using QB description for row
if (drCustomerCache != null && drCustomerCache["Email"] != null)
{
i.BillEmail = new q.EmailAddress();
i.BillEmail.Address = drCustomerCache["Email"].ToString();
//Set ToBeEmailed (if there is an address set)
if (QDat.ToBeEmailed)
{
i.EmailStatusSpecified = true;
i.EmailStatus = q.EmailStatusEnum.NeedToSend;
}
}
}
//Set QB Invoice template
//NO TEMPLATES IN QB ONLINE SO FAR
//Set Class (in docs it's settable for header (here) and for each line item. Header seems to do nothing but keeping this in just in case
//and also setting it on each billable line item)
//case 3267
if (QDat.TransactionClass != null && QDat.TransactionClass != TRANSACTION_CLASS_NO_CLASS_SELECTED)
{
i.ClassRef = new q.ReferenceType();
i.ClassRef.Value = QDat.TransactionClass;
}
//Set ToBePrinted
if (QDat.ToBePrinted)
{
i.PrintStatus = q.PrintStatusEnum.NeedToPrint;
i.PrintStatusSpecified = true;
}
#endregion set header info
}
else
{
//put a comma after the last workorder number that was
//added to the memo string
if (QDat.SetMemoField)
sbMemo.Append(", ");
}
//if set memo true then build memo string from workorder service numbers etc
if (QDat.SetMemoField)
{
sbMemo.Append(w.WorkorderService.ServiceNumber.ToString());
}
#region Invoice header text
if (QDat.HasInvoiceHeaderTemplate)
{
string s = QDat.InvoiceHeaderTemplate;
if (s.IndexOf("~WO#~") != -1)
{
s = s.Replace("~WO#~", w.WorkorderService.ServiceNumber.ToString());
}
if (s.IndexOf("~CONTACT~") != -1)
{
s = s.Replace("~CONTACT~", w.CustomerContactName);
}
if (s.IndexOf("~CREF#~") != -1)
{
s = s.Replace("~CREF#~", w.CustomerReferenceNumber);
}
if (s.IndexOf("~OURREF#~") != -1)
{
s = s.Replace("~OURREF#~", w.InternalReferenceNumber);
}
if (s.IndexOf("~PROJ~") != -1)
{
if (w.ProjectID == Guid.Empty)
s = s.Replace("~PROJ~", "");
else
s = s.Replace("~PROJ~", NameFetcher.GetItem("aProject", "aName", w.ProjectID).RecordName);
}
if (s.IndexOf("~CLIENT~") != -1)
{
s = s.Replace("~CLIENT~", NameFetcher.GetItem("aClient", "aName", w.ClientID).RecordName);
}
if (s.IndexOf("~SERVDATE~") != -1)
{
s = s.Replace("~SERVDATE~", w.WorkorderService.ServiceDate.ToString());
}
if (s.IndexOf("~STAT~") != -1)
{
if (w.WorkorderService.WorkorderStatusID == Guid.Empty)
s = s.Replace("~STAT~", "");
else
s = s.Replace("~STAT~", NameFetcher.GetItem("aWorkorderStatus", "aName", w.WorkorderService.WorkorderStatusID).RecordName);
}
if (s.IndexOf("~DESC~") != -1)
{
s = s.Replace("~DESC~", w.Summary);
}
InvoiceAddText(AllInvoiceLines, s);
}
#endregion header text
#region Part charges
//case 3296
PartDisplayFormats defaultPartDisplayFormat = AyaBizUtils.GlobalSettings.DefaultPartDisplayFormat;
foreach (WorkorderItem it in w.WorkorderItems)
{
foreach (WorkorderItemPart p in it.Parts)
{
//------------DISCOUNT-----------------
//Added:20-July-2006 to incorporate discounts on parts into qb invoice
decimal charge;
//Case 269 this is incorrect:
//charge = decimal.Round(p.Quantity * p.Price, 2, MidpointRounding.AwayFromZero);
charge = decimal.Round(1 * p.Price, 2, MidpointRounding.AwayFromZero);
charge = charge - (decimal.Round(charge * p.Discount, 2, MidpointRounding.AwayFromZero));
//-----------------------------
//case 3296 - override description?
string QBListID = QBOIntegration.Maps[p.PartID].ForeignID;
DataRow drItemCacheData = _dtQBItems.Rows.Find(QBListID);
qbitemtype qbType = (qbitemtype)drItemCacheData["type"];
string sOverrideDescription = "";
if (qbType == qbitemtype.NonInventory)
{
sOverrideDescription = PartPickList.GetOnePart(p.PartID)[0].DisplayName(defaultPartDisplayFormat);
}
InvoiceAddCharge(AllInvoiceLines, QBListID, p.Quantity, charge, sOverrideDescription);//case 3296
string sn = "";
if (p.PartSerialID != Guid.Empty)
{
sn = PartSerial.GetSerialNumberFromPartSerialID(p.PartSerialID);
if (sn != "")
InvoiceAddText(AllInvoiceLines, "SN: " + sn);
}
//Added:18-Nov-2006 case 125
//checks for nonempty description, also checks to see if description is
//same as serial number because it's copied there in some cases and no sense
//in showing it twice
if (p.Description != "" && sn != p.Description)
InvoiceAddText(AllInvoiceLines, p.Description);
}
}
#endregion part charges
#region Service charges
foreach (WorkorderItem it in w.WorkorderItems)
{
foreach (WorkorderItemLabor l in it.Labors)
{
//Added 20-July-2006 to not charge for banked hours
if (l.ServiceBankID != Guid.Empty)
{
InvoiceAddCharge(AllInvoiceLines, QBOIntegration.Maps[l.ServiceRateID].ForeignID, l.ServiceRateQuantity, 0);
}
else
InvoiceAddCharge(AllInvoiceLines, QBOIntegration.Maps[l.ServiceRateID].ForeignID, l.ServiceRateQuantity,
AyaRateList[l.ServiceRateID].Charge);
}
}
#endregion Service charges
#region Travel charges
foreach (WorkorderItem it in w.WorkorderItems)
{
foreach (WorkorderItemTravel l in it.Travels)
{
InvoiceAddCharge(AllInvoiceLines, QBOIntegration.Maps[l.TravelRateID].ForeignID, l.TravelRateQuantity, AyaRateList[l.TravelRateID].Charge);
}
}
#endregion Travel charges
#region MiscExpense charges
foreach (WorkorderItem it in w.WorkorderItems)
{
foreach (WorkorderItemMiscExpense l in it.Expenses)
{
if (l.ChargeToClient)
{
InvoiceAddCharge(AllInvoiceLines, QDat.MiscExpenseChargeAs, 1, l.ChargeAmount);
}
}
}
#endregion MiscExpense charges
#region Loaner charges
foreach (WorkorderItem it in w.WorkorderItems)
{
foreach (WorkorderItemLoan l in it.Loans)
{
InvoiceAddCharge(AllInvoiceLines, QDat.WorkorderItemLoanChargeAs, 1, l.Charges);
}
}
#endregion Loaner charges
#region OutsideService charges
foreach (WorkorderItem it in w.WorkorderItems)
{
if (it.HasOutsideService && (it.OutsideService.ShippingPrice != 0 || it.OutsideService.RepairPrice != 0))
{
InvoiceAddCharge(AllInvoiceLines, QDat.OutsideServiceChargeAs, 1, it.OutsideService.ShippingPrice + it.OutsideService.RepairPrice);
}
}
#endregion OutsideService charges
#region Descriptive footer text
//Loop through workorder items
//inserting descriptive text as required
if (QDat.HasAnyInvoiceFooterTemplateFields)
{
foreach (WorkorderItem it in w.WorkorderItems)
{
#region Item (footer) fields
if (QDat.InvoiceFooterTemplate != "")
{
string s = QDat.InvoiceFooterTemplate;
if (s.IndexOf("~ITEM_SUMMARY~") != -1)
{
s = s.Replace("~ITEM_SUMMARY~", it.Summary);
}
if (s.IndexOf("~ITEM_SERVICE_NOTES~") != -1)
{
s = s.Replace("~ITEM_SERVICE_NOTES~", it.TechNotes);
}
if (s.IndexOf("~ITEM_TYPE~") != -1)
{
if (it.TypeID == Guid.Empty)
s = s.Replace("~ITEM_TYPE~", "");
else
s = s.Replace("~ITEM_TYPE~", NameFetcher.GetItem("aWorkorderItemType", "aName", it.TypeID).RecordName);
}
if (s.IndexOf("~ITEM_REQUEST_DATE~") != -1)
{
s = s.Replace("~ITEM_REQUEST_DATE~", it.RequestDate.ToString());
}
if (s.IndexOf("~ITEM_STATUS~") != -1)
{
if (it.WorkorderStatusID == Guid.Empty)
s = s.Replace("~ITEM_STATUS~", "");
else
s = s.Replace("~ITEM_STATUS~", NameFetcher.GetItem("aWorkorderStatus", "aName", it.WorkorderStatusID).RecordName);
}
InvoiceAddText(AllInvoiceLines, s);
}
#endregion item
#region Unit fields
if (QDat.InvoiceUnitTemplate != "" && it.UnitID != Guid.Empty)
{
string s = QDat.InvoiceUnitTemplate;
UnitPickList up = UnitPickList.GetListOfOneSpecificUnit(it.UnitID);
if (s.IndexOf("~AYAFORMAT~") != -1)
{
s = s.Replace("~AYAFORMAT~", up[0].UnitName());
}
if (s.IndexOf("~UNIT_SN~") != -1)
{
s = s.Replace("~UNIT_SN~", up[0].Serial);
}
if (s.IndexOf("~UNIT_METER~") != -1)
{
if (!up[0].Metered)
s = s.Replace("~UNIT_METER~", "");
else
s = s.Replace("~UNIT_METER~", Unit.LastMeterReading(up[0].ID).ToString());
}
if (s.IndexOf("~UNIT_MAKE~") != -1)
{
s = s.Replace("~UNIT_MAKE~", up[0].VendorName);
}
if (s.IndexOf("~UNIT_MODEL_NAME~") != -1)
{
s = s.Replace("~UNIT_MODEL_NAME~", up[0].ModelName);
}
if (s.IndexOf("~UNIT_MODEL_NUMBER~") != -1)
{
s = s.Replace("~UNIT_MODEL_NUMBER~", up[0].ModelNumber);
}
InvoiceAddText(AllInvoiceLines, s);
}
#endregion unit
#region Labor fields
if (QDat.InvoiceServiceTemplate != "" && it.HasLabor)
{
foreach (WorkorderItemLabor wl in it.Labors)
{
string s = QDat.InvoiceServiceTemplate;
if (s.IndexOf("~SERVICE_START~") != -1)
{
s = s.Replace("~SERVICE_START~", wl.ServiceStartDate.ToString());
}
if (s.IndexOf("~SERVICE_STOP~") != -1)
{
s = s.Replace("~SERVICE_STOP~", wl.ServiceStopDate.ToString());
}
if (s.IndexOf("~SERVICE_QUANTITY~") != -1)
{
s = s.Replace("~SERVICE_QUANTITY~", wl.ServiceRateQuantity.ToString());
}
if (s.IndexOf("~NO_CHARGE_QUANTITY~") != -1)
{
s = s.Replace("~NO_CHARGE_QUANTITY~", wl.NoChargeQuantity.ToString());
}
if (s.IndexOf("~RATE_NAME~") != -1)
{
s = s.Replace("~RATE_NAME~", AyaRateList[wl.ServiceRateID].Name);
}
if (s.IndexOf("~SERVICE_TECH~") != -1)
{
s = s.Replace("~SERVICE_TECH~", UserPickList.GetListOfOneSpecificUser(wl.UserID)[0].Name);
}
if (s.IndexOf("~DETAILS~") != -1)
{
s = s.Replace("~DETAILS~", wl.ServiceDetails);
}
InvoiceAddText(AllInvoiceLines, s);
}
}
#endregion service
#region Travel fields
if (QDat.InvoiceTravelTemplate != "" && it.HasTravel)
{
foreach (WorkorderItemTravel wt in it.Travels)
{
string s = QDat.InvoiceTravelTemplate;
if (s.IndexOf("~TRAVEL_START~") != -1)
{
s = s.Replace("~TRAVEL_START~", wt.TravelStartDate.ToString());
}
if (s.IndexOf("~TRAVEL_STOP~") != -1)
{
s = s.Replace("~TRAVEL_STOP~", wt.TravelStopDate.ToString());
}
if (s.IndexOf("~TRAVEL_QUANTITY~") != -1)
{
s = s.Replace("~TRAVEL_QUANTITY~", wt.TravelRateQuantity.ToString());
}
if (s.IndexOf("~TRAVEL_NO_CHARGE_QUANTITY~") != -1)
{
s = s.Replace("~TRAVEL_NO_CHARGE_QUANTITY~", wt.NoChargeQuantity.ToString());
}
if (s.IndexOf("~TRAVEL_RATE_NAME~") != -1)
{
s = s.Replace("~TRAVEL_RATE_NAME~", AyaRateList[wt.TravelRateID].Name);
}
if (s.IndexOf("~TRAVEL_TECH~") != -1)
{
s = s.Replace("~TRAVEL_TECH~", UserPickList.GetListOfOneSpecificUser(wt.UserID)[0].Name);
}
if (s.IndexOf("~TRAVEL_DETAILS~") != -1)
{
s = s.Replace("~TRAVEL_DETAILS~", wt.TravelDetails);
}
if (s.IndexOf("~TRAVEL_DISTANCE~") != -1)
{
s = s.Replace("~TRAVEL_DISTANCE~", wt.Distance.ToString());
}
InvoiceAddText(AllInvoiceLines, s);
}
}
#endregion travel fields
#region Outside fields
if (QDat.OutsideServiceChargeAs != "" && it.HasOutsideService)
{
string s = QDat.InvoiceOutsideServiceTemplate;
if (s.IndexOf("~REPAIR_PRICE~") != -1)
{
s = s.Replace("~REPAIR_PRICE~", it.OutsideService.RepairPrice.ToString("c"));
}
if (s.IndexOf("~SHIP_CHARGE~") != -1)
{
s = s.Replace("~SHIP_CHARGE~", it.OutsideService.ShippingPrice.ToString("c"));
}
if (s.IndexOf("~SENT~") != -1)
{
s = s.Replace("~SENT~", it.OutsideService.DateSent.ToString());
}
if (s.IndexOf("~RETURNED~") != -1)
{
s = s.Replace("~RETURNED~", it.OutsideService.DateReturned.ToString());
}
if (s.IndexOf("~NOTES~") != -1)
{
s = s.Replace("~NOTES~", it.OutsideService.Notes);
}
InvoiceAddText(AllInvoiceLines, s);
}
#endregion outside service
#region Misc expense fields
if (QDat.InvoiceMiscExpenseTemplate != "" && it.HasExpenses)
{
foreach (WorkorderItemMiscExpense e in it.Expenses)
{
string s = QDat.InvoiceMiscExpenseTemplate;
if (s.IndexOf("~CHARGES~") != -1)
{
s = s.Replace("~CHARGES~", e.ChargeAmount.ToString("c"));
}
if (s.IndexOf("~SUMMARY~") != -1)
{
s = s.Replace("~SUMMARY~", e.Name);
}
if (s.IndexOf("~DESCRIPTION~") != -1)
{
s = s.Replace("~DESCRIPTION~", e.Description);
}
if (s.IndexOf("~TECH~") != -1)
{
s = s.Replace("~TECH~", UserPickList.GetListOfOneSpecificUser(e.UserID)[0].Name);
}
InvoiceAddText(AllInvoiceLines, s);
}
}
#endregion misc expense
#region Loan item fields
if (QDat.InvoiceLoanItemTemplate != "" && it.HasLoans)
{
foreach (WorkorderItemLoan l in it.Loans)
{
string s = QDat.InvoiceLoanItemTemplate;
if (s.IndexOf("~CHARGE~") != -1)
{
s = s.Replace("~CHARGE~", l.Charges.ToString("c"));
}
if (s.IndexOf("~ITEM~") != -1)
{
s = s.Replace("~ITEM~", NameFetcher.GetItem("aLoanItem", "aName", l.LoanItemID).RecordName);
}
if (s.IndexOf("~LOANED~") != -1)
{
s = s.Replace("~LOANED~", l.OutDate.ToString());
}
if (s.IndexOf("~LOAN_RETURNED~") != -1)
{
s = s.Replace("~LOAN_RETURNED~", l.ReturnDate.ToString());
}
if (s.IndexOf("~LOAN_NOTES~") != -1)
{
s = s.Replace("~LOAN_NOTES~", l.Notes);
}
InvoiceAddText(AllInvoiceLines, s);
}
}
#endregion loan item expense
}
}
#endregion footer text
}//Bottom of foreach workorder loop
//Set all invoice lines
i.Line = AllInvoiceLines.ToArray();
//Set memo field
if (QDat.SetMemoField)
{
i.CustomerMemo = new q.MemoRef();
i.CustomerMemo.Value = T(1000, sbMemo.ToString());
}
//============ ADD VIA API================================
DataService service = new DataService(SC);
q.Invoice resultInvoice = service.Add(i) as q.Invoice;
//========================================================
/*
*/
//--------- UPDATE OBJECTS FROM RESULT OF ADDING INVOICE ---------------
string InvoiceNumber = resultInvoice.DocNumber;
//Loop through all workorders again and set their invoice number, status and close them
foreach (object o in alWorkorders)
{
Workorder w = Workorder.GetItem((Guid)o);
if (QDat.PostWOStatus != Guid.Empty)
{
w.WorkorderService.WorkorderStatusID = QDat.PostWOStatus;
}
w.WorkorderService.InvoiceNumber = InvoiceNumber;
//Case 7
if (QDat.AutoClose)
w.Closed = true;
w.Save();
}
}//end try block
catch (Exception ex)
{
alErrors.Add("Invoice: Invoicing failed due to the following error:\r\n" + CrackException(ex));
}
}
/// <summary>
/// Add text to the invoice
/// chopping into chunks less than the max 4095 characters limit
/// as necessary
/// </summary>
private static void InvoiceAddText(List<q.Line> lineList, string Text)
{
if (Text == null || Text == "") return;
List<string> textLines = new List<string>();
//break into 4000 character chunks as that's the limit for a detail line descripttion
while (Text.Length > 4000)
{
textLines.Add(Text.Substring(0, 4000));
Text = Text.Remove(0, 4000);
}
//Get the last bit
if (Text.Length > 0)
textLines.Add(Text);
//Loop through lines and add them one at a time to the list
foreach (string s in textLines)
{
q.Line line = new q.Line();
line.Description = s;
line.DetailType = q.LineDetailTypeEnum.DescriptionOnly;
line.DetailTypeSpecified = true;
lineList.Add(line);
}
}
/// <summary>
/// Add charge line to the invoice
///
/// </summary>
private static void InvoiceAddCharge(List<q.Line> lineList, string QBListID,
decimal Quantity, decimal rate, string OverrideDescriptionText = "")
{
q.Line line = new q.Line();
line.Amount = rate * Quantity;
line.AmountSpecified = true;
//Line Sales Item Line Detail - UnitPrice
//https://gist.github.com/IntuitDeveloperRelations/6500373
//Get item from cache
DataRow drItemCacheData = _dtQBItems.Rows.Find(QBListID);
//case 3296
//Line item DESCRIPTION
if (string.IsNullOrWhiteSpace(OverrideDescriptionText))
{
line.Description = drItemCacheData["SalesDesc"].ToString();
}
else
{
line.Description = OverrideDescriptionText;
}
//TAXES
string sItemTaxStatus = string.Empty;
//US is different than all others looked at
if (QCountry == "US")
{
sItemTaxStatus = "NON";
if ((bool)drItemCacheData["Taxable"])
{
sItemTaxStatus = "TAX";
}
}
else
{ //NON-US means we should take the tax code specified on the QB Item and apply it here
sItemTaxStatus = drItemCacheData["TaxId"].ToString();
}
line.DetailType = q.LineDetailTypeEnum.SalesItemLineDetail;
line.DetailTypeSpecified = true;
q.ReferenceType transClass = null;
if (QDat.TransactionClass != null && QDat.TransactionClass != TRANSACTION_CLASS_NO_CLASS_SELECTED)
{
transClass = new q.ReferenceType();
transClass.Value = QDat.TransactionClass;
}
line.AnyIntuitObject = new q.SalesItemLineDetail()
{
Qty = Quantity,
QtySpecified = true,
ItemRef = new q.ReferenceType()
{
Value = QBListID
},
AnyIntuitObject = rate,
ItemElementName = q.ItemChoiceType.UnitPrice,
TaxCodeRef = new q.ReferenceType()
{
Value = sItemTaxStatus
},
ClassRef = transClass
};
lineList.Add(line);
//QBO MAY BE RELEVANT: For Invoice objects in global locales: when updating Amount, remove the TxnTaxDetail element in the object before submitting it in the update request payload.
}
#endregion
#region Change QB Item price
public static void ChangeQBItemPrice(string QBListID, decimal NewPrice)
{
//TODO: QBO likely doesn't have the issues that caused case 92 in the first place, but
//not sure if it's an issue either way.
//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;
}
try
{
//----Get the SyncToken-----
string strSyncToken = GetInventoryItemSyncToken(QBListID);
if (string.IsNullOrEmpty(strSyncToken))
{
MessageBox.Show("ChangeQBItemPrice -> Error: unable to fetch SyncToken from QuickBooks Online. No changes made.");
return;
}
//----CREATE ITEM, SET API STUFF -------
q.Item ca = new q.Item();
ca.sparse = true;
ca.sparseSpecified = true;
ca.Id = QBListID;
ca.SyncToken = strSyncToken;
//------- SET DATA -----------------
ca.UnitPrice = NewPrice;
//----------- UPDATE VIA API -------------
DataService service = new DataService(SC);
q.Item resultItem = service.Update(ca) as q.Item;
//---------AFTER UPDATE----------
QBItems.Rows.Find(QBListID)["Price"] = NewPrice;
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "ChangeQBItemPrice: Failed with exception:" + CrackException(ex));
throw;
}
}
public static string GetInventoryItemSyncToken(string QBListID)
{
try
{
QueryService<q.Item> qs = new QueryService<q.Item>(SC);
List<q.Item> items = qs.ExecuteIdsQuery("select synctoken from Item where id = '" + QBListID + "'").ToList<q.Item>();
if (items.Count < 1)
return string.Empty;
else
return items[0].SyncToken;
}
catch (Exception ex)
{
IntegrationLog.Log(QBID, "GetInventoryItemSyncToken: Failed with exception:" + CrackException(ex));
throw;
}
}
#endregion
public static string CrackException(Exception ex)
{
StringBuilder sb = new StringBuilder();
//More graceful error if it's an business rule exception
if (ex is Intuit.Ipp.Exception.IdsException && ex.InnerException != null)
{
if (ex.InnerException is Intuit.Ipp.Exception.ValidationException)
{
sb.AppendLine("QuickBooks Online validation rule broken:");
if (ex.InnerException.Message.Contains("Make sure all your transactions have a GST/HST rate before you save."))
{
sb.AppendLine("-----------------------------------");
sb.AppendLine("Most likely reason for this error:");
sb.AppendLine("You are attempting to Invoice one or more items that do not have a sales tax set in QuickBooks Online.");
sb.AppendLine("-----------------------------------");
}
}
else
sb.AppendLine("QuickBooks Online returned this error message:");
sb.AppendLine(ex.InnerException.Message.Replace("ValidationException was thrown.Details:", ""));
}
else
{
sb.AppendLine(ex.Message);
sb.AppendLine("-------TRACE------");
sb.AppendLine(ex.StackTrace);
while (ex.InnerException != null)
{
ex = ex.InnerException;
sb.AppendLine("--- INNER EXCEPTION ---");
sb.AppendLine(ex.Message);
sb.AppendLine("-------TRACE------");
sb.AppendLine(ex.StackTrace);
}
}
return sb.ToString();
}
/// <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;
}
//case 632 (sort of)
public static void SaveQBIData()
{
if (QDat.IsDirty)
{
QBOIntegration.AIObject = QDat.XMLData;
QBOIntegration = (Integration)QBOIntegration.Save();
QDat.IsDirty = false;
}
}
//--------------------------------------------------------------------------------
}
}