5350 lines
199 KiB
C#
5350 lines
199 KiB
C#
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;
|
||
|
||
}
|
||
}
|
||
//--------------------------------------------------------------------------------
|
||
}
|
||
}
|