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

764 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AyaNova.PlugIn;
using GZTW.AyaNova.BLL;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
using System.Data;
using System.IO;
namespace AyaNova.Plugin.Yamaha
{
class yamaha : IAyaNovaPlugin
{
//Keep all the object types we want to deal with in a collection
//so that we can quickly check it when asked
private static List<RootObjectTypes> ObjectsWeCanDealWith = null;
//Holds the image resources from AyaNova
//so we can display the correct icons in our plugin
System.Resources.ResourceManager resman = null;
//Holds the current logged in user's localized text
//lookup object
LocalizedTextTable LocaleText = null;
#region IAyaNovaPlugin Members
#region interface properties
public string PluginName
{
get { return "eWarranty - import"; }
}
public string PluginVersion
{
get { return "7.5"; }//was 7.2.1.1 before changing for general 7.3 RI release
}
public string About
{
get
{
return "AyaNova eWarranty - import plugin\r\n" +
"Copyright 2013 Ground Zero Tech-Works Inc.";
}
}
public Guid PluginID
{
get { return new Guid("{2260ECC2-88CD-4630-BF96-A98AC10B8BE1}"); }
}
public System.Drawing.Image PluginSmallIcon
{
get { return null; }
}
public System.Drawing.Image PluginLargeIcon
{
get { return null; }
}
public System.Resources.ResourceManager AyaNovaResourceManager
{
set { resman = value; }
get { return resman; }
}
#endregion interface properties
#region Initialization and Close
public bool Initialize(Version AyaNovaVersion, LocalizedTextTable localizedText)
{
LocaleText = localizedText;
ObjectsWeCanDealWith = new List<RootObjectTypes>();
ObjectsWeCanDealWith.Add(RootObjectTypes.Nothing);
return true;
}
public void Close()
{
;
}
#endregion Initialization and close
#region ShowMenu?
public bool SingleObjectMenuShow(RootObjectTypes objectType)
{
return (ObjectsWeCanDealWith.Contains(objectType));
}
public bool MultipleObjectsMenuShow(RootObjectTypes objectType)
{
return false;
}
#endregion show menu?
#region Menu options
public List<AyaNovaPluginMenuItem> SingleObjectMenuOptions(RootObjectTypes objectType, object ayaNovaObject)
{
if (!ObjectsWeCanDealWith.Contains(objectType)) return null;
List<AyaNovaPluginMenuItem> list = new List<AyaNovaPluginMenuItem>();
list.Add(new AyaNovaPluginMenuItem("YAMAHAIMPORT", "eWarranty - import", null, null));
return list;
}
public List<AyaNovaPluginMenuItem> MultipleObjectsMenuOptions(RootObjectTypes objectType)
{
return null;
}
#endregion
#region Menu Commands
#region LIST OBJECT COMMAND
/// <summary>
/// LIST OBJECT
/// </summary>
/// <param name="commandKey"></param>
/// <param name="objectType"></param>
/// <param name="objectIDList"></param>
/// <param name="listObject"></param>
/// <returns></returns>
public bool CommandSelectedForList(string commandKey, RootObjectTypes objectType, List<Guid> objectIDList, object listObject)
{
return false;
}
#endregion list object command
#region SINGLE OBJECT COMMAND
/// <summary>
/// SINGLE OBJECT
/// </summary>
/// <param name="commandKey"></param>
/// <param name="objectType"></param>
/// <param name="ayaNovaObject"></param>
public void CommandSelectedForSingleObject(string commandKey, RootObjectTypes objectType, object ayaNovaObject)
{
switch (commandKey)
{
case "YAMAHAIMPORT":
{
ImportData();
}
break;
}
}
#endregion single object command
#endregion menu commands
#endregion
#region Import clients and unit records from csv file
#region specifications
/*
*
*
* Changes: Sept 24th 2013
* Turns out they don't want to set all groups for the client group, they mean a region called all groups which should be set
* updating accordingly.
*
*
* Changes: Sept 13 2013 (friday the 13th!!)
* ==============================
*
* Please make the following changes.
I told him we would do all at no charge:
#1 - I didn't catch this in testing either.
The "ClientMobile" field (third field in the record) is incorrectly being placed into the Business number field.
Its supposed to be in the Mobile number field (see screenshot of client entry screen at http://www.ayanova.com/AyaNova7webHelp/clients_entry_screen.htm -
* the Mobile field:)
#2 - My fault for not realizing the difference in case - the Client Group that is to be default selected is supposed to be with the text ALL Groups
note the upper case for ALL
He really doesn't want to change the case himself, not understanding his english. He wants it to be ALL Groups
#3- you had offered this, and as we have to provide a fix I gave him this option - change the name of the utility when selecting it to be
* "eWarranty - import" instead of "Yamaha - import"
*
*
*
* ===============================
*
*
*
Purpose of the custom plugin is to import client record, unit record, unit model record (unless already present in the AyaNova database) for each record
* provided in a CSV format file in location to be determined that is provided by Yamaha Music Malaysia Sdn Bhd
Find attached copy of an example of their CSV file that has the following requirements ClientUnit2.csv
And below is what has been agreed to:
CSV file will be comma delimited (a comma separates every field) AND if a comma is present in a field (i.e. in the Street field) the field MUST be quoted.
The quote is based on your CSV file fields being in the following order and 14 specific number of fields - if a field is empty of data, the field must STILL
* be accounted for in the import file:
"ClientName", "ClientContact", "ClientMobile", "ClientEmail", "ClientPhysicalStreet", "ClientPhysicalCity", "ClientPhysicalState", "UnitModelName",
* "UnitModelWarrantyLength", "UnitSerialNumber", "UnitPurchasedDate", " UnitRecieptNumber", "UnitOverrideLength", "UnitText1"
The CSV file will have no header.
Each record in the import file MUST have at minimum data in the following fields (the other 12 fields could certainly be empty, but
* those fields still must be accounted for in the record in your import file):
ClientName
UnitSerialNumber
Each record in the import file is located on a separate line, delimited by a line break (CRLF)
Each record to contain the same number of fields separated each by a comma with no comma at the end of the line as per the example order above
The plugin will provide the ability to browse and select on the local computer's hard drive the specific CSV file.
And this plugin will also write to this same folder a text file if any issues (i.e. the import results including which client is duplicated and already present in AyaNova, so you know need to merge) so the computer user must have read/write/delete access to that folder.
Details about the fields from the import file and how the import plugin is to work in certain circumstances:
Client Name:
this is a required field in the import file
the custom plugin will check to see if there is an existing client with the existing Client Name.
If there is not an existing client record with that name, then a new client record will be created in AyaNova
if there are duplicate client records with the same client name, the custom plugin will select one of these to link the imported unit record to.
* AND will advise at the end of the import what client is duplicated, so that you can merge
Client Contact:
IF client record is created by plugin, whatever in this field will also be entered in the new client record
Client Mobile:
IF client record is created by plugin, whatever in this field will also be entered in the new client record
Client Email:
IF client record is created by plugin, whatever in this field will also be entered in the new client record
Client Physical Street:
NOTE - there is ONLY the one Street field in AyaNova to import to. Your import file will also have only one street field.
IF client record is created by plugin, whatever in this field will also be entered
IF client record is created by plugin, this will also be imported into Client Postal Street:
Client Physical City:
IF client record is created by plugin, whatever in this field will also be entered
IF client record is created by plugin, this will also be imported into Client Postal City:
Client Physical State:
IF client record is created by plugin, whatever in this field will also be entered
IF client record is created by plugin, this will also be imported into Client Postal State:
Unit Model Name:
if Unit Model Name does not match an existing Unit Model for selection, the custom plugin will create new Unit Model with that Unit Model Name:
* and the Unit Model Warranty Length stated in the import file
Unit Model Warranty Length:
Unit Serial Number:
this is a required field in the import file
if a unit with the same serial number already exists, the custom plugin will override the existing
* Unit Override Warranty, Unit Override Length if different that being imported
Unit Purchased Date:
Unit Reciept Number:
Unit Override Length:
Unit Text1:
Additional about other fields
Client General Notes:
custom plugin will automatically enter text of "Record imported on YYYY/MM/DD" where YYYY is four digit year, MM is the month, and DD is the today etc if the client record was created by the import
Unit Model Notes:
custom plugin will automatically enter text of "Record imported on YYYY/MM/DD" where YYYY is four digit year etc if the unit model record was created by the import
Unit Notes:
custom plugin will automatically enter text of "Record imported on YYYY/MM/DD" where YYYY is four digit year etc if the unit record was created by the import
Client Group:
the custom plugin will automatically select the already created "All Groups" if the client is newly created by the plugin
Unit Override Warranty:
IF there is a number greater than 0 in the Unit Override Length in the record, the custom plugin will checkmark this in the newly created Unit
* and import the Unit Override Length
IF the number for Unit Override Length is empty or 0, then this is not checkmarked
*
* IMPLEMENTATION NOTES:
* A log will be written to the same folder as the import file was found in, the name will be importlog.txt and it will be overwritten on every import.
* If a single error or warning takes more than one line it will be grouped between a row of asterisks: "***********"
* The plugin is accessible from the main plugins menu and is named in the menu as: "Yamaha - import"
* If Yamaha would like a different name to display in the menu for the import command let us know, it's 2 seconds to change it, no extra charge
* Any problem that prevents importing will be logged with the prefix "* ERROR!....", any problem that does not prevent importing but should be examined by the user will be prefaced in the log with "* WARNING: "
* For importing dates: I'm assuming based on the sample data provided that the plugin should expect all dates to be in the format of "M/D/YYYY" which is to say no leading zeros in month or day numbers and 4 digit years and it will always be in the order month/day/year separated by forward slashes
* If a date is not empty but is not able to be parsed based on the above rule, a warning message will be displayed but all the other fields will be processed normally.
* If a unit serial number is found in the database already that unit's client will be used for importing even if there are multiple clients with the same name
* If a unit serial number is found in the database already before import but the AyaNova client name does not match the one in the import file, that line will *not* be imported and a warning will be generated as this might indicate something needs to be fixed in the csv or the AyaNOva database when it comes to that client's name. Both the import name and the AyaNova name will be output in the error message so the user can fix it
*
***************************************
* */
#endregion specifications
/// <summary>
/// Import data as per spec
/// </summary>
private void ImportData()
{
//track ops to report upon completion
System.Text.StringBuilder sb = new StringBuilder();
sb.AppendLine("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
sb.AppendLine("IMPORT LOG - " + DateTime.Now.ToString("M/d/yyyy") + ":");
string importFolderLocation = string.Empty;
//table to hold the records used by import util
DataTable dtImportData=new DataTable();
#region Open and read the CSV file into memory
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
ofd.Title = "";
ofd.DefaultExt = "csv";
ofd.FileName = "*.csv";
if (ofd.ShowDialog() != DialogResult.OK) return;
try
{
ReadCSV(ref dtImportData, ofd.FileName, ref sb);
importFolderLocation = System.IO.Path.GetDirectoryName(ofd.FileName);
}
catch (Exception ex)
{
sb.AppendLine("Error opening file:\r\n" + ex.ToString());
ReportResults(sb, importFolderLocation);
return;
}
ofd.Dispose();
#endregion read the csv file
Cursor.Current = Cursors.WaitCursor;
//===================================================
#region Locate the id of the "ALL Groups" region
//Get the ID for the "All Groups" region and bail if not found
Guid allGroupsRegionId = Guid.Empty;
GenericNVList lRegions = GenericNVList.GetList("aRegion", "aID", "aName", true, false, false);
foreach (System.Collections.DictionaryEntry d in lRegions.BindableList)
{
if (d.Value.ToString().Equals("All Groups", StringComparison.InvariantCultureIgnoreCase))
{
allGroupsRegionId = new Guid(d.Key.ToString());
break;
}
}
lRegions = null;
//If we didn't get the All Groups REGION then we must bail as it's a requirement
if (allGroupsRegionId == Guid.Empty)
{
sb.AppendLine("***********");
sb.AppendLine("* ERROR! Can not find a Region named \"All Groups\" in the AyaNova database.");
sb.AppendLine("* A Region with that name is required in order to set the imported clients to that region.");
sb.AppendLine("* STOPPING IMPORT DUE TO PREVIOUS ERROR *");
sb.AppendLine("***********");
}
#endregion region id code
List<Guid> duplicateClientList=new List<Guid>();
foreach (DataRow dr in dtImportData.Rows)
{
#region get fields
//1st get fields into variables
string fldClientName=dr[0].ToString();
string fldClientContact=dr[1].ToString();
string fldClientMobile=dr[2].ToString();
string fldClientEmail=dr[3].ToString();
string fldClientPhysicalStreet=dr[4].ToString();
string fldClientPhysicalCity=dr[5].ToString();
string fldClientPhysicalState=dr[6].ToString();
string fldUnitModelName=dr[7].ToString();
string fldUnitModelWarrantyLength=dr[8].ToString();
string fldUnitSerialNumber=dr[9].ToString();
string fldUnitPurchasedDate=dr[10].ToString();
string fldUnitRecieptNumber=dr[11].ToString();
string fldUnitOverrideLength=dr[12].ToString();
string fldUnitText1 = dr[13].ToString();
#endregion get fields
//tag for imported objects
string sTag = "Record imported on " + DateTime.Now.ToString("yyyy/M/d");
#region Screen if unit already exists and who it belongs to
//See if units serial number belongs to any existing clients
Guid unitBelongsToClientId = Guid.Empty;
Guid existingUnitId = Guid.Empty;
UnitPickList unpl = UnitPickList.GetListOfAll();
foreach (UnitPickList.UnitPickListInfo i in unpl)
{
if (String.Equals(i.Serial, fldUnitSerialNumber, StringComparison.CurrentCultureIgnoreCase))
{
unitBelongsToClientId = i.ClientID;
existingUnitId = i.ID;
break;
}
}
//Make sure it's the same client as is listed in the import file, if not there could be something funky going on
//so skip import and report warning if so
if (unitBelongsToClientId != Guid.Empty)
{
List<Guid> existingList = new List<Guid>();
existingList.Add(unitBelongsToClientId);
ClientPickList cpExisting = ClientPickList.GetList(existingList, false);
if (!String.Equals(cpExisting[0].Name, fldClientName, StringComparison.CurrentCultureIgnoreCase))
{
//this is a problem, the unit exists already but under a different client name
sb.AppendLine("***********");
sb.AppendLine("* WARNING: Unit with serial number \"" + fldUnitSerialNumber + "\" is already in AyaNova but under a different client name.");
sb.AppendLine("* The unit is already assigned to client \"" + cpExisting[0].Name + "\" but the import file has the client name: \"" + fldClientName);
sb.AppendLine("* This line of the import file will be skipped and not imported. Please check your records for this unit.");
sb.AppendLine("***********");
continue;
}
}
#endregion check if unit pre-exists
#region Client
//Id of client that will be used to import data
//pre-set to unitBelongsToClientId which was pre-determined in the unit pre-screening above
//if it's set here then no need to look up the client or create it
Guid clientId = unitBelongsToClientId;
//if it's not empty then we already have a valid client from the unit pre-screen above
//otherwise we need to do the normal find or create
if (clientId == Guid.Empty)
{
//get a client pick list
ClientPickList cpl = ClientPickList.GetList();
List<Guid> matchingClientList = new List<Guid>();
foreach (ClientPickList.ClientPickListInfo i in cpl)
{
if (String.Equals(i.Name, fldClientName, StringComparison.CurrentCultureIgnoreCase))
matchingClientList.Add(i.ID);
}
//Do we have a match?
if (matchingClientList.Count > 0)
{
//yes we do so import to first one and if there are others report them
clientId = matchingClientList[0];
//We have duplicates so need to report on it
if (matchingClientList.Count > 1)
{
sb.AppendLine("* WARNING: \"" + cpl[clientId].Name + "\" is duplicated in the database " + matchingClientList.Count.ToString() + " times. Please MERGE them.");
}
}
else
{
#region Import new client
//No match so it needs to be imported
Client c = Client.NewItem();
c.Name = fldClientName;
c.RegionID = allGroupsRegionId;
c.Notes = sTag;
c.Contact = fldClientContact;
c.Phone4 = fldClientMobile;
c.Email = fldClientEmail;
c.GoToAddress.DeliveryAddress = fldClientPhysicalStreet;
c.GoToAddress.City = fldClientPhysicalCity;
c.GoToAddress.StateProv = fldClientPhysicalState;
//Now copy the gotoaddress to the mail address
c.PopulateBothAddresses();
if (c.IsSavable)
{
c = (Client)c.Save();
clientId = c.ID;
}
else
{
sb.AppendLine("***********");
sb.AppendLine("* ERROR! Can not save Client: \"" + fldClientName + "\" due to broken rules. Error is:");
sb.AppendLine("--------------");
sb.AppendLine(c.BrokenRulesText);
sb.AppendLine("--------------");
sb.AppendLine("* STOPPING IMPORT DUE TO PREVIOUS ERROR *");
sb.AppendLine("***********");
break;
}
#endregion import new client
}
}
#endregion client
#region Unit model
Guid unitModelId = Guid.Empty;
UnitModelPickList upl = UnitModelPickList.GetList();
foreach (UnitModelPickList.UnitModelPickListInfo i in upl)
{
if (String.Equals(i.Name.Trim(), fldUnitModelName, StringComparison.CurrentCultureIgnoreCase))
{
unitModelId = i.ID;
break;
}
}
//was a match found?
if (unitModelId == Guid.Empty)
{
//nope, so import it now
UnitModel u = UnitModel.NewItem();
u.Name = fldUnitModelName;
u.Notes = sTag;
if(!string.IsNullOrWhiteSpace(fldUnitModelWarrantyLength))
{
int n=0;
if(int.TryParse(fldUnitModelWarrantyLength,out n) && n>0)
{
u.WarrantyLength = n;
u.LifeTimeWarranty = false;
}
}
if (u.IsSavable)
{
u = (UnitModel)u.Save();
unitModelId = u.ID;
}
else
{
sb.AppendLine("***********");
sb.AppendLine("* ERROR! Can not save UnitModel: \"" + fldUnitModelName + "\" due to broken rules. Error is:");
sb.AppendLine("--------------");
sb.AppendLine(u.BrokenRulesText);
sb.AppendLine("--------------");
sb.AppendLine("* STOPPING IMPORT DUE TO PREVIOUS ERROR *");
sb.AppendLine("***********");
break;
}
}
#endregion unit model
#region Unit
//Units are pre-screened above so no need to check for it,
//if existing unitId is empty then it definitely doesn't exist and needs to be
//imported
Guid unitId = existingUnitId;
Unit importUnit = null;
if (unitId == Guid.Empty)
{
//nope, so import it now
Unit u = Unit.NewItem();
u.ClientID = clientId;
u.Serial = fldUnitSerialNumber;
u.Notes = sTag;
u.UnitModelID = unitModelId;
if (u.IsSavable)
{
importUnit = (Unit)u.Save();
}
else
{
sb.AppendLine("***********");
sb.AppendLine("* ERROR! Can not save Unit: \"" + fldUnitSerialNumber + "\" due to broken rules. Error is:");
sb.AppendLine("--------------");
sb.AppendLine(u.BrokenRulesText);
sb.AppendLine("--------------");
sb.AppendLine("* STOPPING IMPORT DUE TO PREVIOUS ERROR *");
sb.AppendLine("***********");
break;
}
}
else
{
importUnit = Unit.GetItem(unitId);
}
//In either case we have the unit here and now need to set or fixup the other fields like warranty etc
importUnit.Receipt = fldUnitRecieptNumber;
importUnit.Text1 = fldUnitText1;
//unit warranty override
if (!string.IsNullOrWhiteSpace(fldUnitOverrideLength) && fldUnitOverrideLength!="0")
{
int n = 0;
if (int.TryParse(fldUnitOverrideLength, out n) && n > 0)
{
importUnit.OverrideModelWarranty = true; ;
importUnit.WarrantyLength = n;
}
else
{
sb.AppendLine("* WARNING: csv file row with unit serial number \"" + importUnit.Serial + "\" has unparseable Unit override length field: " + fldUnitOverrideLength);
sb.AppendLine("* The value is not empty but also was not parseable so it will not be set. Check your import file.");
}
}
//purchase date
if (!string.IsNullOrWhiteSpace(fldUnitPurchasedDate))
{
DateTime dtResult=DateTime.MinValue;
if (!DateTime.TryParseExact(
fldUnitPurchasedDate,
"d/M/yyyy",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out dtResult))
{
sb.AppendLine("* WARNING: csv file row with unit serial number \"" + importUnit.Serial + "\" has unparseable purchase date: " + fldUnitPurchasedDate );
sb.AppendLine("* The date was not recognized so won't be set. Dates should be in the format of month/day/year, no leading zeroes and 4 digit year to be recognized.");
}
else
{
importUnit.PurchasedDate = dtResult;
}
}
if (importUnit.IsDirty)
{
if (importUnit.IsSavable)
{
importUnit = (Unit)importUnit.Save();
}
else
{
sb.AppendLine("***********");
sb.AppendLine("* ERROR! Can not save Unit: \"" + fldUnitSerialNumber + "\" due to broken rules. Error is:");
sb.AppendLine("--------------");
sb.AppendLine(importUnit.BrokenRulesText);
sb.AppendLine("--------------");
sb.AppendLine("* STOPPING IMPORT DUE TO PREVIOUS ERROR *");
sb.AppendLine("***********");
break;
}
}
#endregion unit model
}
//===================================================
Cursor.Current = Cursors.Default;
sb.AppendLine("Import completed.");
//Report results
ReportResults(sb, importFolderLocation);
}
private void ReportResults(StringBuilder sb, string logFilePath)
{
string log = sb.ToString();
//write results to report file and also display them here
File.WriteAllText(Path.Combine(logFilePath, "importlog.txt"), log);
CopyableMessageBox dlgCMB = new CopyableMessageBox(log);
dlgCMB.ShowDialog();
}
/// <summary>
/// Read the csv file from disk into a DataTable object for further processing and import into AyaNova
/// </summary>
/// <param name="dt">Table to contain import data</param>
/// <param name="csvPath">Path to the import file</param>
/// <param name="sbLog">Log StringBuilder</param>
public static void ReadCSV(ref System.Data.DataTable dt, string csvPath, ref StringBuilder sbLog)
{
if (dt != null)
{
dt.Clear();
dt = null;
}
bool bHasErrors = false;
long lRecordsRead = 0;
long lRecordsBad = 0;
Microsoft.VisualBasic.FileIO.TextFieldParser rdr = new Microsoft.VisualBasic.FileIO.TextFieldParser(csvPath);
rdr.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
rdr.SetDelimiters(",");
rdr.TrimWhiteSpace = true;
rdr.HasFieldsEnclosedInQuotes = true;
List<string[]> lsRows = new List<string[]>();
int nMaxRowFields = 0;
string[] sCurrentRow;
while (!rdr.EndOfData)
{
try
{
sCurrentRow = rdr.ReadFields();
if (sCurrentRow.Length > nMaxRowFields)
nMaxRowFields = sCurrentRow.Length;
lsRows.Add(sCurrentRow);
lRecordsRead++;
}
catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex)
{
bHasErrors = true;
sbLog.Append("Line ");
sbLog.Append(ex.Message);
sbLog.AppendLine(" is not valid and will be skipped.");
lRecordsBad++;
}
}
if (bHasErrors)
{
sbLog.AppendLine(lRecordsBad.ToString() + " records were malformed and not able to be read, they will be skipped and not imported.");
}
sbLog.AppendLine(lRecordsRead.ToString() + " records were successfully read from the .csv file.");
dt = new System.Data.DataTable("CSVData");
for (int x = 0; x < nMaxRowFields; x++)
{
dt.Columns.Add("Column" + (x + 1).ToString(), typeof(string));
}
foreach (string[] sRow in lsRows)
{
System.Data.DataRow dr = dt.NewRow();
int nField = 0;
foreach (string sField in sRow)
{
dr[nField] = sField;
nField++;
}
dt.Rows.Add(dr);
}
//at this point all records are in the datatable
}
#endregion update
}
}