diff --git a/BealeEngineering/BealeEngineering.Accounts/App.config b/BealeEngineering/BealeEngineering.Accounts/App.config
new file mode 100644
index 0000000..a0f10b3
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/App.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Accounts/BealeEngineering.Accounts.csproj b/BealeEngineering/BealeEngineering.Accounts/BealeEngineering.Accounts.csproj
new file mode 100644
index 0000000..33499df
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/BealeEngineering.Accounts.csproj
@@ -0,0 +1,90 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F025483F-53D1-47FC-9691-EB771FD845EF}
+ WinExe
+ BealeEngineering.Accounts
+ BealeEngineering.Accounts
+ v4.7.2
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+ {fd88a52a-fde5-4d0a-abdf-ee87d19f21bd}
+ BealeEngineering.Core
+
+
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Accounts/Form1.Designer.cs b/BealeEngineering/BealeEngineering.Accounts/Form1.Designer.cs
new file mode 100644
index 0000000..d98a3a8
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Form1.Designer.cs
@@ -0,0 +1,61 @@
+namespace BealeEngineering.Accounts
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.btnTest = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+ //
+ // btnTest
+ //
+ this.btnTest.Location = new System.Drawing.Point(61, 39);
+ this.btnTest.Name = "btnTest";
+ this.btnTest.Size = new System.Drawing.Size(139, 42);
+ this.btnTest.TabIndex = 0;
+ this.btnTest.Text = "Test it!";
+ this.btnTest.UseVisualStyleBackColor = true;
+ this.btnTest.Click += new System.EventHandler(this.button1_Click);
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(800, 450);
+ this.Controls.Add(this.btnTest);
+ this.Name = "Form1";
+ this.Text = "Form1";
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Button btnTest;
+ }
+}
+
diff --git a/BealeEngineering/BealeEngineering.Accounts/Form1.cs b/BealeEngineering/BealeEngineering.Accounts/Form1.cs
new file mode 100644
index 0000000..8a0c46a
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Form1.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Configuration;
+using BealeEngineering.Core;
+
+namespace BealeEngineering.Accounts
+{
+ public partial class Form1 : Form
+ {
+ public Form1()
+ {
+ InitializeComponent();
+ }
+
+ private void button1_Click(object sender, EventArgs e)
+ {
+ string conString = ConfigurationManager.ConnectionStrings["BealeEngSQLDb"].ToString();
+
+ var inst = new Core.Test.Autoexec(conString);
+ inst.Start();
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Accounts/Form1.resx b/BealeEngineering/BealeEngineering.Accounts/Form1.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Form1.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Accounts/Program.cs b/BealeEngineering/BealeEngineering.Accounts/Program.cs
new file mode 100644
index 0000000..f56d26e
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace BealeEngineering.Accounts
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Accounts/Properties/AssemblyInfo.cs b/BealeEngineering/BealeEngineering.Accounts/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..44daf93
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BealeEngineering.Accounts")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BealeEngineering.Accounts")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f025483f-53d1-47fc-9691-eb771fd845ef")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.Designer.cs b/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..ae83c8d
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace BealeEngineering.Accounts.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BealeEngineering.Accounts.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.resx b/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.Designer.cs b/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..032928b
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace BealeEngineering.Accounts.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.settings b/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Accounts/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/BealeEngineering/BealeEngineering.Core/BealeEngineering.Core.csproj b/BealeEngineering/BealeEngineering.Core/BealeEngineering.Core.csproj
new file mode 100644
index 0000000..b42d248
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/BealeEngineering.Core.csproj
@@ -0,0 +1,80 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}
+ Library
+ Properties
+ BealeEngineering.Core
+ BealeEngineering.Core
+ v4.7.2
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Dapper.2.0.30\lib\net461\Dapper.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationCreate.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationCreate.cs
new file mode 100644
index 0000000..dfb6200
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationCreate.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Transactions;
+
+namespace BealeEngineering.Core.Data.Database.Client
+{
+ public class PurchaseOrderAllocationCreate : Connection
+ {
+ public PurchaseOrderAllocationCreate(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ public int ByDictionary(Dictionary saleInvoiceIdToPoLineId)
+ {
+ int rowsCreated = 0;
+
+ if (saleInvoiceIdToPoLineId == null || !saleInvoiceIdToPoLineId.Any())
+ {
+ throw new Exception("No data passed to function.");
+ }
+
+ using (TransactionScope scope = new TransactionScope())
+ {
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ foreach (var item in saleInvoiceIdToPoLineId)
+ {
+ using (SqlCommand cmd = new SqlCommand(@"
+ INSERT INTO ClientPurchaseOrderLineSalesInvoice (
+ ClientPurchaseOrderLineID
+ ,SaleInvoiceID
+ )
+ VALUES (
+ @clientPurchaseOrderLineID
+ ,@saleInvoiceID
+ )
+ ", conn))
+ {
+ cmd.Parameters.AddWithValue("@clientPurchaseOrderLineID", item.Value);
+ cmd.Parameters.AddWithValue("@saleInvoiceID", item.Key);
+
+ rowsCreated = rowsCreated + cmd.ExecuteNonQuery();
+ }
+ }
+ }
+ scope.Complete();
+ }
+ return rowsCreated;
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationGet.cs
new file mode 100644
index 0000000..4d10a63
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderAllocationGet.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database.Client
+{
+ public class PurchaseOrderAllocationGet : Connection
+ {
+ public PurchaseOrderAllocationGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ public int GetUnallocatedInvoiceCount()
+ {
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ using (SqlCommand cmd = new SqlCommand(@"
+ SELECT Count(SaleInvoice.SaleInvoiceID) AS CountOfSaleInvoiceID
+ FROM SaleInvoice
+ LEFT OUTER JOIN ClientPurchaseOrderLineSalesInvoice ON SaleInvoice.SaleInvoiceID = ClientPurchaseOrderLineSalesInvoice.SaleInvoiceID
+ WHERE (ClientPurchaseOrderLineSalesInvoice.SaleInvoiceID IS NULL)
+ AND (SaleInvoice.Reference IS NOT NULL)
+ ", conn))
+ {
+ object obj = cmd.ExecuteScalar();
+
+ return (int)obj;
+ }
+ }
+ }
+ public List GetUnallocatedInvoiceId()
+ {
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ using (SqlCommand cmd = new SqlCommand(@"
+ SELECT SaleInvoice.SaleInvoiceID
+ FROM SaleInvoice
+ LEFT OUTER JOIN ClientPurchaseOrderLineSalesInvoice ON SaleInvoice.SaleInvoiceID = ClientPurchaseOrderLineSalesInvoice.SaleInvoiceID
+ WHERE (ClientPurchaseOrderLineSalesInvoice.SaleInvoiceID IS NULL)
+ AND (SaleInvoice.Reference IS NOT NULL)
+ ", conn))
+ {
+ using (SqlDataReader reader = cmd.ExecuteReader())
+ {
+ if (!reader.HasRows)
+ {
+ // do nothing
+ return null;
+ }
+ else
+ {
+ var returnList = new List();
+ while (reader.Read())
+ {
+ returnList.Add(reader.GetInt32(0));
+ }
+ return returnList;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderGet.cs
new file mode 100644
index 0000000..eab592c
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderGet.cs
@@ -0,0 +1,102 @@
+using Dapper;
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database.Client
+{
+ public class PurchaseOrderGet : PurchaseOrderHeaderGet
+ {
+ public PurchaseOrderGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ public List PurchaseOrderHeader { get; set; }
+ public bool PurchaseOrderHeaderIsSet
+ {
+ get
+ {
+ if (PurchaseOrderHeader == null || !PurchaseOrderHeader.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public new List GetByClientPurchaseOrderId(List orderIdList)
+ {
+ ClientPurchaseOrderIdList = orderIdList;
+ try
+ {
+ return GetByFilters();
+ }
+ catch (Exception ex)
+ {
+ throw ex;
+ }
+ finally
+ {
+ ClientPurchaseOrderIdList = null;
+ }
+ }
+ public new List GetByFilters()
+ {
+ // build the sql string and dapper parameters
+ var parameters = new DynamicParameters();
+ string sqlString = @"
+ SELECT ClientPurchaseOrder.ClientPurchaseOrderID
+ ,ClientPurchaseOrder.PurchaseOrderDate
+ ,ClientPurchaseOrder.ContactID
+ ,ClientPurchaseOrder.ClientReference
+ ,ClientPurchaseOrder.RequestorEmail
+ ,ClientPurchaseOrder.OrderTotal
+ ,ClientPurchaseOrder.IsClosed
+ ,ClientPurchaseOrderLine.ClientPurchaseOrderLineID
+ ,ClientPurchaseOrderLine.LineNumber
+ ,ClientPurchaseOrderLine.ProjectJobID
+ ,ClientPurchaseOrderLine.Description
+ ,ClientPurchaseOrderLine.LineNetAmount
+ FROM ClientPurchaseOrder
+ LEFT OUTER JOIN ClientPurchaseOrderLine ON ClientPurchaseOrder.ClientPurchaseOrderID = ClientPurchaseOrderLine.ClientPurchaseOrderID";
+
+ AddSqlWhereString(ref sqlString, ref parameters);
+
+ sqlString = sqlString + @"
+ ORDER BY ClientPurchaseOrder.ClientPurchaseOrderID
+ ,ClientPurchaseOrderLine.LineNumber";
+
+ // make the call
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ var orderDictionary = new Dictionary();
+
+ var orderList = conn.Query
+ (
+ sqlString,
+ (order, orderDetail) =>
+ {
+ Model.Client.PurchaseOrder orderEntry;
+
+ if (!orderDictionary.TryGetValue(order.ClientPurchaseOrderID, out orderEntry))
+ {
+ orderEntry = order;
+ orderEntry.OrderLineList = new List();
+ orderDictionary.Add(orderEntry.ClientPurchaseOrderID, orderEntry);
+ }
+
+ orderEntry.OrderLineList.Add(orderDetail);
+ return orderEntry;
+ },
+ parameters,
+ splitOn: "ClientPurchaseOrderLineID")
+ .Distinct()
+ .ToList();
+
+ return orderList;
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderHeaderGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderHeaderGet.cs
new file mode 100644
index 0000000..4b80262
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Client/PurchaseOrderHeaderGet.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Data;
+using System.Data.SqlClient;
+using System.Text;
+using Dapper;
+
+namespace BealeEngineering.Core.Data.Database.Client
+{
+ public class PurchaseOrderHeaderGet : Connection
+ {
+ public PurchaseOrderHeaderGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ ///
+ /// Setting this will override he other filters, within the sql statement.
+ ///
+ protected List ClientPurchaseOrderIdList { get; set; }
+ private bool ClientPurchaseOrderIdListIsSet
+ {
+ get
+ {
+ if (ClientPurchaseOrderIdList == null || !ClientPurchaseOrderIdList.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public DateTime DateFrom { get; set; }
+ public bool DateFromIsSet
+ {
+ get
+ {
+ if (DateFrom == null || DateFrom == default(DateTime)) { return false; }
+ else { return true; }
+ }
+ }
+ public DateTime DateTo { get; set; }
+ public bool DateToIsSet
+ {
+ get
+ {
+ if (DateTo == null || DateTo == default(DateTime)) { return false; }
+ else { return true; }
+ }
+ }
+ public List Reference { get; set; }
+ public bool ReferenceIsSet
+ {
+ get
+ {
+ if (Reference == null || !Reference.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public bool ReturnIsClosed { get; set; } = true;
+ public List GetByClientPurchaseOrderId(List orderIdList)
+ {
+ ClientPurchaseOrderIdList = orderIdList;
+ try
+ {
+ return GetByFilters();
+ }
+ catch (Exception ex)
+ {
+ throw ex;
+ }
+ finally
+ {
+ ClientPurchaseOrderIdList = null;
+ }
+ }
+ protected void AddSqlWhereString(ref string sqlString, ref DynamicParameters parameters)
+ {
+ if (string.IsNullOrWhiteSpace(sqlString))
+ { throw new Exception("SQL string is empty."); }
+
+ sqlString = sqlString + @"
+ WHERE 1=1";
+
+ if (ClientPurchaseOrderIdListIsSet)
+ {
+ sqlString = sqlString + @"
+ AND ClientPurchaseOrder.ClientPurchaseOrderID IN @purchaseOrderId";
+
+ parameters.Add("@purchaseOrderId", ClientPurchaseOrderIdList);
+ }
+ else
+ {
+ if (DateFromIsSet)
+ {
+ sqlString = sqlString + @"
+ AND PurchaseOrderDate >= @dateFrom";
+
+ parameters.Add("@dateFrom", DateFrom);
+ }
+ if (DateToIsSet)
+ {
+ sqlString = sqlString + @"
+ AND PurchaseOrderDate <= @dateTo";
+
+ parameters.Add("@dateTo", DateTo);
+ }
+ if (ReferenceIsSet)
+ {
+ sqlString = sqlString + @"
+ AND ClientReference IN @reference";
+
+ parameters.Add("@reference", Reference);
+ }
+ if (ReturnIsClosed == false)
+ {
+ sqlString = sqlString + @"
+ AND IsClosed = @isClosed";
+
+ parameters.Add("@isClosed", false);
+ }
+ }
+ }
+ public List GetByFilters()
+ {
+ // build the sql string and dapper parameters
+ var parameters = new DynamicParameters();
+ string sqlString = @"
+ SELECT ClientPurchaseOrderID
+ ,PurchaseOrderDate
+ ,ContactID
+ ,ClientReference
+ ,RequestorEmail
+ ,OrderTotal
+ ,IsClosed
+ FROM ClientPurchaseOrder";
+
+ AddSqlWhereString(ref sqlString, ref parameters);
+
+ sqlString = sqlString + @"
+ ORDER BY ClientPurchaseOrderID";
+
+ // make the call
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ var purchaseOrders = conn.Query(sqlString, parameters).ToList();
+ return purchaseOrders;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Connection.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Connection.cs
new file mode 100644
index 0000000..aeeaf0f
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Connection.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database
+{
+ public class Connection
+ {
+ protected readonly string sqlConnectionString;
+ public Connection(string sqlConnectionString)
+ {
+ // setup sql parameters
+ if (sqlConnectionString.Length == 0)
+ { throw new Exception("Zero length sql connectionstring passed"); }
+ this.sqlConnectionString = sqlConnectionString;
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Contact/ContactHeaderGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Contact/ContactHeaderGet.cs
new file mode 100644
index 0000000..4bc183c
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Contact/ContactHeaderGet.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database.Contact
+{
+ public class ContactHeaderGet : Connection
+ {
+ public ContactHeaderGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+
+ private Dictionary cacheContact = new Dictionary();
+ public int NumberRequested = 0;
+ public int NumberRetrived = 0;
+ public int NumberRetrivedUnique = 0;
+ public int NumberFailed = 0;
+ public Model.Contact.ContactHeader ById(int contactId, bool cacheClear = false)
+ {
+ var contactIdList = new List();
+ contactIdList.Add(contactId);
+ var resultList = ByIdList(contactIdList, cacheClear);
+ if (cacheContact.ContainsKey(contactId))
+ {
+ return cacheContact[contactId];
+ }
+ else
+ {
+ return null;
+ }
+ }
+ public Dictionary ByIdList(List contactIdList, bool cacheClear = false)
+ {
+ ClearStats();
+
+ // check list for values
+ if (!contactIdList.Any())
+ {
+ return null;
+ }
+
+ if (cacheClear)
+ {
+ cacheContact = new Dictionary();
+ }
+
+ // build list of contactIds to lookup from db
+ var idLookupList = new List();
+ foreach (int contactId in contactIdList)
+ {
+ if (!cacheContact.ContainsKey(contactId))
+ {
+ idLookupList.Add(contactId);
+ }
+ }
+
+ // query db and add to cache
+ if (idLookupList.Any())
+ {
+ //build sql string
+ string sqlString = null;
+ foreach (int id in idLookupList)
+ {
+ int count = 0;
+ if (count == 0)
+ {
+ sqlString = " WHERE ContactId=@contactId" + count;
+ }
+ else
+ {
+ sqlString = sqlString + " OR ContactId=@contactId" + count;
+ }
+ count = count + 1;
+ }
+
+ sqlString = @"
+ SELECT
+ ContactID
+ ,ContactName
+ ,EmailAddress
+ FROM Contact
+ " + sqlString;
+
+ // run query
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ using (SqlCommand cmd = new SqlCommand(sqlString, conn))
+ {
+ foreach (int id in idLookupList)
+ {
+ int count = 0;
+ cmd.Parameters.AddWithValue("@contactId" + count, id);
+ count = count + 1;
+ }
+
+ using (SqlDataReader reader = cmd.ExecuteReader())
+ {
+ if (!reader.HasRows)
+ {
+ // do something
+ }
+ else
+ {
+ while (reader.Read())
+ {
+ // load data into cache
+ var contact = new Model.Contact.ContactHeader();
+ contact.ContactId = reader.GetInt32(0);
+ contact.ContactName = reader.GetString(1);
+ contact.EmailAddress = reader.GetString(2);
+
+ if (cacheContact.ContainsKey(contact.ContactId))
+ {
+ cacheContact.Remove(contact.ContactId);
+ }
+
+ cacheContact.Add(contact.ContactId, contact);
+ }
+ }
+ }
+ }
+ }
+ }
+ // build and return list
+ var returnList = new Dictionary();
+ foreach (var contactId in contactIdList)
+ {
+ NumberRequested = NumberRequested + 1;
+ if (cacheContact.ContainsKey(contactId))
+ {
+ if (!returnList.ContainsKey(contactId))
+ {
+ returnList.Add(contactId, cacheContact[contactId]);
+ }
+ }
+ else
+ {
+ NumberFailed = NumberFailed + 1;
+ }
+ }
+ NumberRetrived = NumberRequested - NumberFailed;
+ NumberRetrivedUnique = returnList.Count();
+
+ return returnList;
+ }
+ private void ClearStats()
+ {
+ NumberRequested = 0;
+ NumberRetrived = 0;
+ NumberRetrivedUnique = 0;
+ NumberFailed = 0;
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceGet.cs
new file mode 100644
index 0000000..2ff0e1e
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceGet.cs
@@ -0,0 +1,111 @@
+using Dapper;
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database.Sale
+{
+ public class InvoiceGet : InvoiceHeaderGet
+ {
+ public InvoiceGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ public List SaleInvoiceHeader { get; set; }
+ public bool SaleInvoiceHeaderIsSet
+ {
+ get
+ {
+ if (SaleInvoiceHeader == null || !SaleInvoiceHeader.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public new List GetBySaleInvoiceId(List invoiceIdList)
+ {
+ SalesInvoiceIdList = invoiceIdList;
+ try
+ {
+ return GetByFilters();
+ }
+ catch (Exception ex)
+ {
+ throw ex;
+ }
+ finally
+ {
+ SalesInvoiceIdList = null;
+ }
+ }
+ public new List GetByFilters()
+ {
+ // build the sql string and dapper parameters
+ var parameters = new DynamicParameters();
+ string sqlString = @"
+ SELECT SaleInvoice.SaleInvoiceID
+ ,SaleInvoice.ContactID
+ ,SaleInvoice.SaleInvoiceNumber
+ ,SaleInvoice.InvoiceDate
+ ,SaleInvoice.DateDue
+ ,SaleInvoice.Reference
+ ,SaleInvoice.CurrencyCode
+ ,SaleInvoice.InvoiceTotal
+ ,SaleInvoice.TaxTotal
+ ,SaleInvoiceLine.SaleInvoiceLineID
+ ,SaleInvoiceLine.SaleInvoiceID
+ ,SaleInvoiceLine.LineNumber
+ ,SaleInvoiceLine.InventoryItemCode
+ ,SaleInvoiceLine.Description
+ ,SaleInvoiceLine.Quantity
+ ,SaleInvoiceLine.UnitAmount
+ ,SaleInvoiceLine.Discount
+ ,SaleInvoiceLine.AccountCode
+ ,SaleInvoiceLine.TaxType
+ ,SaleInvoiceLine.TaxAmount
+ ,SaleInvoiceLine.LineAmount
+ FROM SaleInvoice
+ LEFT OUTER JOIN SaleInvoiceLine ON SaleInvoice.SaleInvoiceID = SaleInvoiceLine.SaleInvoiceID";
+
+ AddSqlWhereString(ref sqlString, ref parameters);
+
+ sqlString = sqlString + @"
+ ORDER BY SaleInvoice.SaleInvoiceID
+ ,SaleInvoiceLine.LineNumber
+ ,SaleInvoiceLine.SaleInvoiceLineID";
+
+ // make the call
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ var invoiceDictionary = new Dictionary();
+
+ var invoiceList = conn.Query
+ (
+ sqlString,
+ (invoice, invoiceDetail) =>
+ {
+ Model.Sale.Invoice invoiceEntry;
+
+ if (!invoiceDictionary.TryGetValue(invoice.SaleInvoiceID, out invoiceEntry))
+ {
+ invoiceEntry = invoice;
+ invoiceEntry.InvoiceLineList = new List();
+ invoiceDictionary.Add(invoiceEntry.SaleInvoiceID, invoiceEntry);
+ }
+
+ invoiceEntry.InvoiceLineList.Add(invoiceDetail);
+ return invoiceEntry;
+ },
+ parameters,
+ splitOn: "SaleInvoiceLineID")
+ .Distinct()
+ .ToList();
+
+ return invoiceList;
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceHeaderGet.cs b/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceHeaderGet.cs
new file mode 100644
index 0000000..c710717
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Data/Database/Sale/InvoiceHeaderGet.cs
@@ -0,0 +1,160 @@
+using Dapper;
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Data.Database.Sale
+{
+ public class InvoiceHeaderGet : Connection
+ {
+ public InvoiceHeaderGet(string sqlConnectionString) : base(sqlConnectionString)
+ {
+
+ }
+ ///
+ /// Setting this will override he other filters, within the sql statement.
+ ///
+ protected List SalesInvoiceIdList { get; set; }
+ private bool SalesInvoiceIdListListIsSet
+ {
+ get
+ {
+ if (SalesInvoiceIdList == null || !SalesInvoiceIdList.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public DateTime DateFrom { get; set; }
+ public bool DateFromIsSet
+ {
+ get
+ {
+ if (DateFrom == null || DateFrom == default(DateTime)) { return false; }
+ else { return true; }
+ }
+ }
+ public DateTime DateTo { get; set; }
+ public bool DateToIsSet
+ {
+ get
+ {
+ if (DateTo == null || DateTo == default(DateTime)) { return false; }
+ else { return true; }
+ }
+ }
+ public List InvoiceNumber { get; set; }
+ public bool InvoiceNumberIsSet
+ {
+ get
+ {
+ if (InvoiceNumber == null || !InvoiceNumber.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public List Reference { get; set; }
+ public bool ReferenceIsSet
+ {
+ get
+ {
+ if (Reference == null || !Reference.Any()) { return false; }
+ else { return true; }
+ }
+ }
+ public List GetBySaleInvoiceId(List orderIdList)
+ {
+ SalesInvoiceIdList = orderIdList;
+ try
+ {
+ return GetByFilters();
+ }
+ catch (Exception ex)
+ {
+ throw ex;
+ }
+ finally
+ {
+ SalesInvoiceIdList = null;
+ }
+ }
+ protected void AddSqlWhereString(ref string sqlString, ref DynamicParameters parameters)
+ {
+ if (string.IsNullOrWhiteSpace(sqlString))
+ { throw new Exception("SQL string is empty."); }
+
+ sqlString = sqlString + @"
+ WHERE 1=1";
+
+ if (SalesInvoiceIdListListIsSet)
+ {
+ sqlString = sqlString + @"
+ AND SaleInvoice.SaleInvoiceID IN @saleInvoiceId";
+
+ parameters.Add("@saleInvoiceId", SalesInvoiceIdList);
+ }
+ else
+ {
+ if (DateFromIsSet)
+ {
+ sqlString = sqlString + @"
+ AND InvoiceDate >= @dateFrom";
+
+ parameters.Add("@dateFrom", DateFrom);
+ }
+ if (DateToIsSet)
+ {
+ sqlString = sqlString + @"
+ AND InvoiceDate <= @dateTo";
+
+ parameters.Add("@dateTo", DateTo);
+ }
+ if (InvoiceNumberIsSet)
+ {
+ sqlString = sqlString + @"
+ AND SaleInvoiceNumber IN @salesInvoiceNumber";
+
+ parameters.Add("@salesInvoiceNumber", InvoiceNumber);
+ }
+ if (ReferenceIsSet)
+ {
+ sqlString = sqlString + @"
+ AND Reference IN @reference";
+
+ parameters.Add("@reference", Reference);
+ }
+ }
+ }
+ public List GetByFilters()
+ {
+ // build the sql string and dapper parameters
+ var parameters = new DynamicParameters();
+ string sqlString = @"
+ SELECT SaleInvoiceID
+ ,ContactID
+ ,SaleInvoiceNumber
+ ,InvoiceDate
+ ,DateDue
+ ,Reference
+ ,CurrencyCode
+ ,InvoiceTotal
+ ,TaxTotal
+ ,IsCreditNote
+ FROM SaleInvoice";
+
+ AddSqlWhereString(ref sqlString, ref parameters);
+
+ sqlString = sqlString + @"
+ ORDER BY SaleInvoiceID";
+
+ // make the call
+ using (SqlConnection conn = new SqlConnection(sqlConnectionString))
+ {
+ conn.Open();
+
+ var invoiceHeaders = conn.Query(sqlString, parameters).ToList();
+ return invoiceHeaders;
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Logic/Client/PurchaseOrderAutoAllocate.cs b/BealeEngineering/BealeEngineering.Core/Logic/Client/PurchaseOrderAutoAllocate.cs
new file mode 100644
index 0000000..9e8baf7
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Logic/Client/PurchaseOrderAutoAllocate.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Logic.Client
+{
+ public class PurchaseOrderAutoAllocate
+ {
+ public PurchaseOrderAutoAllocate(string sqlConnectionString)
+ {
+ SqlConnectionString = sqlConnectionString;
+ Init();
+ }
+ public bool IsComplete { get; private set; }
+ public int InvoiceMatched { get; private set; }
+ public int InvoiceProcessed { get; private set; }
+ public int InvoiceUnmatched
+ {
+ get { return InvoiceProcessed - InvoiceMatched; }
+ }
+ private string SqlConnectionString { get; set; }
+ public void Execute()
+ {
+ Init();
+
+ // get list of unallocated invoices
+ var poAlloInstance = new Data.Database.Client.PurchaseOrderAllocationGet(SqlConnectionString);
+ var invoiceIdList = poAlloInstance.GetUnallocatedInvoiceId();
+
+ // nothing to allocate
+ if (invoiceIdList == null || !invoiceIdList.Any())
+ {
+ IsComplete = true;
+ return;
+ }
+ else
+ {
+ InvoiceProcessed = invoiceIdList.Count();
+ }
+
+ // get invoice header info
+ var invoiceInst = new Data.Database.Sale.InvoiceHeaderGet(SqlConnectionString);
+ var invoiceList = invoiceInst.GetBySaleInvoiceId(invoiceIdList);
+
+ // create lookup list for matching
+ var lookupList = new List>();
+
+ for (int i = 0; i < invoiceList.Count; i++)
+ {
+ lookupList.Add(new Tuple
+ (invoiceList[i], invoiceList[i].Reference, null));
+
+ // check if a line number can be potentially parsed off reference
+ // if it can, add a second lookup entry (leave the first as it might be right)
+ string parsedReference = string.Copy(invoiceList[i].Reference);
+ int? parsedLineNumber = null;
+ ParseOutLineNumbes(ref parsedReference, ref parsedLineNumber);
+ if (parsedLineNumber != null)
+ {
+ lookupList.Add(new Tuple
+ (invoiceList[i], parsedReference, parsedLineNumber));
+ }
+ }
+
+ // for PO with multiple lines, sort lookup list with potential parsed line numbers first
+ // try to match on ref and line# first, then try ref and line total
+ lookupList = lookupList.OrderByDescending(t => t.Item3).ToList();
+
+ //get client POs from db that match reference list
+ var referenceList = new List();
+ foreach(var item in lookupList) { referenceList.Add(item.Item2); }
+ var poInstance = new Data.Database.Client.PurchaseOrderGet(SqlConnectionString);
+ poInstance.Reference = referenceList.Distinct().ToList();
+ poInstance.ReturnIsClosed = true; // <--------------------------------------------------change to false after
+ var clientPoList = poInstance.GetByFilters();
+
+ // create dictionary for matched items
+ var invoiceIdToPoLineId = new Dictionary();
+
+ // loop through list and do the matching
+ for (int i = 0; i < lookupList.Count; i++)
+ {
+ // skip any invoice ids already done
+ int invoiceId = lookupList[i].Item1.SaleInvoiceID;
+ if (!invoiceIdToPoLineId.ContainsKey(invoiceId))
+ {
+ // match contact ID
+ foreach (var po in clientPoList)
+ {
+ if (po.ContactID == lookupList[i].Item1.ContactID)
+ {
+ // match PO reference
+ string reference = lookupList[i].Item2;
+ if (po.ClientReference == reference)
+ {
+ // match PO line(s)
+ int? parsedLineNumber = lookupList[i].Item3;
+ // only one line in PO, jus match invoice to it
+ if (po.OrderLineCount == 1)
+ {
+ invoiceIdToPoLineId.Add(invoiceId, po.OrderLineList.First().ClientPurchaseOrderLineID);
+ }
+ // for multiple line, try to match invoice to line number
+ else if (po.OrderLineCount > 1 && parsedLineNumber != null)
+ {
+ foreach (var line in po.OrderLineList)
+ {
+ if (line.LineNumber == parsedLineNumber)
+ {
+ invoiceIdToPoLineId.Add(invoiceId, line.ClientPurchaseOrderLineID);
+ }
+ }
+ }
+ // for multiple line, try to match invoice to line total
+ else if(po.OrderLineCount > 1 && parsedLineNumber == null)
+ {
+ int matchCount = 0;
+ int poLineId = 0;
+ decimal invoiceNetTotal = lookupList[i].Item1.InvoiceTotal - lookupList[i].Item1.TaxTotal;
+ foreach (var line in po.OrderLineList)
+ {
+ if (line.LineNetAmount == invoiceNetTotal)
+ {
+ matchCount = matchCount + 1;
+ poLineId = line.ClientPurchaseOrderLineID;
+ }
+ }
+ // only do match if one line is the same
+ if (matchCount == 1)
+ {
+ invoiceIdToPoLineId.Add(invoiceId, poLineId);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // update db table
+ if (invoiceIdToPoLineId.Count > 0)
+ {
+ var instance2 = new Data.Database.Client.PurchaseOrderAllocationCreate(SqlConnectionString);
+ InvoiceMatched = instance2.ByDictionary(invoiceIdToPoLineId);
+ }
+ IsComplete = true;
+ }
+ public void Init()
+ {
+ IsComplete = false;
+ InvoiceMatched = 0;
+ InvoiceProcessed = 0;
+ }
+ ///
+ /// Parses out any possible line numbers from end of PO reference and adds to them the end of the reference list.
+ ///
+ ///
+ ///
+ private void ParseOutLineNumbes(ref string reference, ref int? lineNumber)
+ {
+ int dashIndex = reference.LastIndexOf('-');
+ if (dashIndex > 0)
+ {
+ var newReference = reference.Substring(0, dashIndex);
+ if (newReference != reference)
+ {
+ string lineNoString = reference.Substring(dashIndex + 1);
+ int lineNoInt = 0;
+ if (Int32.TryParse(lineNoString, out lineNoInt))
+ {
+ // it can, and the end string can be converted into an int, so we add it.
+ reference = newReference;
+ lineNumber = lineNoInt;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Logic/Utilities/Reflection.cs b/BealeEngineering/BealeEngineering.Core/Logic/Utilities/Reflection.cs
new file mode 100644
index 0000000..ea23b50
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Logic/Utilities/Reflection.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Reflection;
+
+namespace BealeEngineering.Core.Logic.Utilities
+{
+ public static class Reflection
+ {
+ ///
+ /// Extension for 'Object' that copies the properties to a destination object.
+ ///
+ /// The source.
+ /// The destination.
+ public static void CopyProperties(this object source, object destination)
+ {
+ // If any this null throw an exception
+ if (source == null || destination == null)
+ throw new Exception("Source or/and Destination Objects are null");
+ // Getting the Types of the objects
+ Type typeDest = destination.GetType();
+ Type typeSrc = source.GetType();
+
+ // Iterate the Properties of the source instance and
+ // populate them from their desination counterparts
+ PropertyInfo[] srcProps = typeSrc.GetProperties();
+ foreach (PropertyInfo srcProp in srcProps)
+ {
+ if (!srcProp.CanRead)
+ {
+ continue;
+ }
+ PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name);
+ if (targetProperty == null)
+ {
+ continue;
+ }
+ if (!targetProperty.CanWrite)
+ {
+ continue;
+ }
+ if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate)
+ {
+ continue;
+ }
+ if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
+ {
+ continue;
+ }
+ if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
+ {
+ continue;
+ }
+ // Passed all tests, lets set the value
+ targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
+ }
+ }
+ }
+}
+
+/* https://stackoverflow.com/questions/930433
+ * USAGE:
+ *
+///
+/// ExampleCopyObject
+///
+///
+public object ExampleCopyObject()
+{
+ object destObject = new object();
+ this.CopyProperties(destObject); // inside a class you want to copy from
+
+ Reflection.CopyProperties(this, destObject); // Same as above but directly calling the function
+
+ TestClass srcClass = new TestClass();
+ TestStruct destStruct = new TestStruct();
+ srcClass.CopyProperties(destStruct); // using the extension directly on a object
+
+ Reflection.CopyProperties(srcClass, destObject); // Same as above but directly calling the function
+
+ //so on and so forth.... your imagination is the limits :D
+ return srcClass;
+}
+
+public class TestClass
+{
+ public string Blah { get; set; }
+}
+public struct TestStruct
+{
+ public string Blah { get; set; }
+}
+*/
diff --git a/BealeEngineering/BealeEngineering.Core/Logic/Utilities/StringCheck.cs b/BealeEngineering/BealeEngineering.Core/Logic/Utilities/StringCheck.cs
new file mode 100644
index 0000000..3ad4403
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Logic/Utilities/StringCheck.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Logic.Utilities
+{
+ class StringCheck
+ {
+ public bool AllowEmpty { get; set; } = false;
+ public bool AllowWhiteSpace { get; set; } = false;
+ public string ErrorMessage { get; private set; }
+ public void ResetToDefault()
+ {
+ ErrorMessage = null;
+ AllowEmpty = false;
+ AllowWhiteSpace = false;
+ }
+ public bool IsAlpha(string stringToCheck, bool upperCaseOnly = false, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (stringToCheck == null)
+ {
+ if (!allowNull)
+ {
+ ErrorMessage = "String is null";
+ return false;
+ }
+ }
+ else
+ {
+ foreach (char c in stringToCheck)
+ {
+ if (!((c >= 'A' && c <= 'Z')
+ || ((c >= 'a' && c <= 'z') && !upperCaseOnly)))
+ {
+ if ((c >= 'a' && c <= 'z') && upperCaseOnly)
+ {
+ ErrorMessage = "String contains lower case numerical charater(s).";
+ return false;
+ }
+ else
+ {
+ ErrorMessage = "String contains non-alpha charater(s).";
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ public bool IsAlphaNumeric(string stringToCheck, bool upperCaseOnly = false, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (stringToCheck == null)
+ {
+ if (!allowNull)
+ {
+ ErrorMessage = "String is null";
+ return false;
+ }
+ }
+ else
+ {
+ foreach (char c in stringToCheck)
+ {
+ if (!((c >= '0' && c <= '9')
+ || (c >= 'A' && c <= 'Z')
+ || ((c >= 'a' && c <= 'z') && !upperCaseOnly)))
+ {
+ if ((c >= 'a' && c <= 'z') && upperCaseOnly)
+ {
+ ErrorMessage = "String contains lower case numerical charater(s).";
+ return false;
+ }
+ else
+ {
+ ErrorMessage = "String contains non-alphanumeric charater(s).";
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ public bool IsNumeric(string stringToCheck, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (stringToCheck == null)
+ {
+ if (allowNull == false)
+ {
+ ErrorMessage = "String is null";
+ return false;
+ }
+ }
+ else
+ {
+ foreach (char c in stringToCheck)
+ {
+ if (c < '0' || c > '9')
+ {
+ ErrorMessage = "String contains non-numeric charater(s).";
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ public bool Length(string stringToCheck, int stringLength, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (!NullOrWhiteSpaceCheck(stringToCheck, allowNull))
+ {
+ return false;
+ }
+
+ int length = stringToCheck.Length;
+ if (length != stringLength)
+ {
+ ErrorMessage = "String length (" + length + ") does not equal " + stringLength + " charaters.";
+ return false;
+ }
+ return true;
+ }
+ public bool MaxLength(string stringToCheck, int maxLength, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (!NullOrWhiteSpaceCheck(stringToCheck, allowNull))
+ {
+ return false;
+ }
+
+ int length = stringToCheck.Length;
+ if (length > maxLength)
+ {
+ ErrorMessage = "String length (" + length + ") is greater than " + maxLength + " charaters.";
+ return false;
+ }
+ return true;
+ }
+ public bool MinLength(string stringToCheck, int minLength, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (!NullOrWhiteSpaceCheck(stringToCheck, allowNull))
+ {
+ return false;
+ }
+
+ int length = stringToCheck.Length;
+ if (length <= minLength)
+ {
+ ErrorMessage = "String length (" + length + ") is less than " + minLength + " charaters.";
+ return false;
+ }
+ return true;
+ }
+
+ private bool NullOrWhiteSpaceCheck(string stringToCheck, bool allowNull = false)
+ {
+ ErrorMessage = null;
+ if (string.IsNullOrWhiteSpace(stringToCheck))
+ {
+ if (stringToCheck == null)
+ {
+ if (!allowNull)
+ {
+ ErrorMessage = "String is null, empty or white space.";
+ return false;
+ }
+ }
+ else if (stringToCheck == "")
+ {
+ if (!AllowEmpty)
+ {
+ ErrorMessage = "String is empty.";
+ return false;
+ }
+ }
+ else
+ {
+ if (!AllowWhiteSpace)
+ {
+ ErrorMessage = "String is white space.";
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrder.cs b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrder.cs
new file mode 100644
index 0000000..2a5ddba
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrder.cs
@@ -0,0 +1,20 @@
+using BealeEngineering.Core.Logic.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BealeEngineering.Core.Model.Client
+{
+ public class PurchaseOrder : PurchaseOrderHeader
+ {
+ public int OrderLineCount
+ {
+ get
+ {
+ if (OrderLineList == null) { return 0; }
+ else { return OrderLineList.Count; }
+ }
+ }
+ public List OrderLineList { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderHeader.cs b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderHeader.cs
new file mode 100644
index 0000000..e61bcf1
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderHeader.cs
@@ -0,0 +1,38 @@
+using BealeEngineering.Core.Logic.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Model.Client
+{
+ public class PurchaseOrderHeader
+ {
+ public int ClientPurchaseOrderID { get; set; }
+ public DateTime PurchaseOrderDate { get; set; }
+ public int ContactID { get; set; }
+ public string ClientReference { get; set; }
+ public string RequestorEmail { get; set; }
+ public decimal OrderTotal { get; set; }
+ public bool IsClosed { get; set; }
+ //sealed PurchaseOrder CreatePurchaseOrder()
+ //{
+ // PurchaseOrder destObject = new PurchaseOrder();
+ // this.CopyProperties(destObject); // inside a class you want to copy from
+
+ // return destObject;
+
+ // Reflection.CopyProperties(this, destObject); // Same as above but directly calling the function
+
+ // TestClass srcClass = new TestClass();
+ // TestStruct destStruct = new TestStruct();
+ // srcClass.CopyProperties(destStruct); // using the extension directly on a object
+
+ // Reflection.CopyProperties(srcClass, destObject); // Same as above but directly calling the function
+
+ // //so on and so forth.... your imagination is the limits :D
+ // return srcClass;
+ //}
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderLine.cs b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderLine.cs
new file mode 100644
index 0000000..25141ed
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Client/PurchaseOrderLine.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BealeEngineering.Core.Model.Client
+{
+ public class PurchaseOrderLine
+ {
+ public int ClientPurchaseOrderLineID { get; set; }
+ public int ClientPurchaseOrderID { get; set; }
+ public int LineNumber { get; set; }
+ public int ProjectJobID { get; set; }
+ public string Description { get; set; }
+ public decimal LineNetAmount { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Contact/ContactHeader.cs b/BealeEngineering/BealeEngineering.Core/Model/Contact/ContactHeader.cs
new file mode 100644
index 0000000..3937b5c
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Contact/ContactHeader.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Model.Contact
+{
+ public class ContactHeader
+ {
+ public int ContactId { get; set; }
+ public string ContactName { get; set; }
+ public string EmailAddress { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Sale/Invoice.cs b/BealeEngineering/BealeEngineering.Core/Model/Sale/Invoice.cs
new file mode 100644
index 0000000..022e23d
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Sale/Invoice.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Model.Sale
+{
+ public class Invoice : InvoiceHeader
+ {
+ public List InvoiceLineList { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceHeader.cs b/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceHeader.cs
new file mode 100644
index 0000000..6eaa918
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceHeader.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Model.Sale
+{
+ public class InvoiceHeader
+ {
+ public int SaleInvoiceID { get; set; }
+ public int ContactID { get; set; }
+ public string SaleInvoiceNumber { get; set; }
+ public DateTime InvoiceDate { get; set; }
+ public DateTime DueDate { get; set; }
+ public string Reference { get; set; }
+ public string CurrencyCode { get; set; }
+ public decimal InvoiceTotal { get; set;}
+ public decimal TaxTotal { get; set; }
+ public bool IsCreditNote { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceLine.cs b/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceLine.cs
new file mode 100644
index 0000000..e9d61fd
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Model/Sale/InvoiceLine.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Model.Sale
+{
+ public class InvoiceLine
+ {
+ public int SaleInvoiceLineID { get; set; }
+ public int SaleInvoiceID { get; set; }
+ public string InventoryItemCode { get; set; }
+ public string Description { get; set; }
+ public int Quantity { get; set; }
+ public decimal UnitAmount { get; set; }
+ public int Discount { get; set; }
+ public string AccountCode { get; set; }
+ public string TaxType { get; set; }
+ public decimal TaxAmount { get; set; }
+ public decimal LineAmount { get; set; }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Properties/AssemblyInfo.cs b/BealeEngineering/BealeEngineering.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..23cbc07
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BealeEngineering.Core")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BealeEngineering.Core")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("fd88a52a-fde5-4d0a-abdf-ee87d19f21bd")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BealeEngineering/BealeEngineering.Core/Test/AUtoexec.cs b/BealeEngineering/BealeEngineering.Core/Test/AUtoexec.cs
new file mode 100644
index 0000000..26004e1
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Test/AUtoexec.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Test
+{
+ public class Autoexec
+ {
+ public Autoexec(string sqlConnectionString)
+ {
+ SqlConnectionString = sqlConnectionString;
+ }
+ private string SqlConnectionString { get; set; }
+
+ public void Start()
+ {
+ // thing you want to run from the form button
+
+ //var inst = new Core.Test.Sales.Invoice(SqlConnectionString);
+ //inst.GetInvoice();
+
+ var inst2 = new Test.Client.PurchaseOrder(SqlConnectionString);
+ inst2.AllocateInvoicesToPurchaseOrders();
+
+
+
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Test/Client/PurchaseOrder.cs b/BealeEngineering/BealeEngineering.Core/Test/Client/PurchaseOrder.cs
new file mode 100644
index 0000000..9cc347a
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Test/Client/PurchaseOrder.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Test.Client
+{
+ public class PurchaseOrder
+ {
+ public List PurchaseOrderIdList { get; set; }
+ public string SqlConnectionString { get; set; }
+ public PurchaseOrder(string sqlConnectionString)
+ {
+ SqlConnectionString = sqlConnectionString;
+ PurchaseOrderIdList = new List { 92, 17, 140 };
+ }
+ public void Start()
+ {
+ GetPurchaseOrderById();
+ }
+ public void GetPurchaseOrderById()
+ {
+ var inst = new Core.Data.Database.Client.PurchaseOrderGet(SqlConnectionString);
+ var newList = inst.GetByClientPurchaseOrderId(PurchaseOrderIdList);
+ }
+ public void AllocateInvoicesToPurchaseOrders()
+ {
+ var inst = new Logic.Client.PurchaseOrderAutoAllocate(SqlConnectionString);
+ inst.Execute();
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/Test/Sales/Invoice.cs b/BealeEngineering/BealeEngineering.Core/Test/Sales/Invoice.cs
new file mode 100644
index 0000000..d4c4b1d
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/Test/Sales/Invoice.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BealeEngineering.Core.Test.Sales
+{
+ public class Invoice
+ {
+ public Invoice(string sqlConnectionString)
+ {
+ SqlConnectionString = sqlConnectionString;
+ SaleInvoiceIdList = new List { 131, 481, 105, 324 };
+ }
+ private string SqlConnectionString { get; set; }
+ public List SaleInvoiceIdList { get; set; }
+
+ public void GetInvoice()
+ {
+ var InvInst = new Data.Database.Sale.InvoiceGet(SqlConnectionString);
+ var lkdjflsk = InvInst.GetBySaleInvoiceId(SaleInvoiceIdList);
+ }
+ }
+}
diff --git a/BealeEngineering/BealeEngineering.Core/packages.config b/BealeEngineering/BealeEngineering.Core/packages.config
new file mode 100644
index 0000000..e38523d
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.Core/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/BealeEngineering/BealeEngineering.sln b/BealeEngineering/BealeEngineering.sln
new file mode 100644
index 0000000..63414c6
--- /dev/null
+++ b/BealeEngineering/BealeEngineering.sln
@@ -0,0 +1,33 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29306.81
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{BC929CBA-5FE7-4A75-9AED-B4699E340B0D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BealeEngineering.Accounts", "BealeEngineering.Accounts\BealeEngineering.Accounts.csproj", "{F025483F-53D1-47FC-9691-EB771FD845EF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BealeEngineering.Core", "BealeEngineering.Core\BealeEngineering.Core.csproj", "{FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F025483F-53D1-47FC-9691-EB771FD845EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F025483F-53D1-47FC-9691-EB771FD845EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F025483F-53D1-47FC-9691-EB771FD845EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F025483F-53D1-47FC-9691-EB771FD845EF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FD88A52A-FDE5-4D0A-ABDF-EE87D19F21BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0583C62C-021B-4409-A7C8-85463F2CEA0C}
+ EndGlobalSection
+EndGlobal