text:
Imports System.Collections.Concurrent
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.Threading
Imports System.Windows.Forms
Imports WHLClasses.Exceptions
' ReSharper disable MemberCanBePrivate.Global
' ReSharper disable UnusedMember.Global
<System.Serializable()>
Public Class SkuCollection
    Inherits List(Of WhlSKU)

    #Region "FullCollectionGeneration thing"

    Public Precomp_Sku_Changelog As ConcurrentBag(Of SKUChangelog)
    Public Precomp_Sku_Locations As ConcurrentBag(Of SKULocation)
    Public Precomp_Sku_Images As ConcurrentBag(Of SkuImage)
    Public Precomp_Sku_SupplierData As ConcurrentBag(Of SKUSupplier)
    Public Precomp_Sku_Stock As ConcurrentBag(Of Dictionary(Of String, object))

    Public sub GeneratePrecomps()
        'Create the list

        T
        'Load the tables
        dim Sku_Changelog_Data as List(Of Dictionary(Of String, object)) = mysql.SelectDataDictionary("Select * from Sku_Changelog")
        Precomp_Sku_Changelog = new ConcurrentBag(Of SKUChangelog)
        'Create all of the SkuChangelogs
       'Download Changelog")
        Parallel.ForEach(Sku_Changelog_Data,sub(data as Dictionary(Of String, object))
                                                            dim NewChangelog as New Skuchangelog
                                                            NewChangelog.ChangelogID = data("logid")
                                                            NewChangelog.ShortSku = data("shortsku")
                                                            NewChangelog.DateModified = Date.Parse(data("datetimechanged"))
                                                            NewChangelog.Reason = data("reason")
                                                            NewChangelog.UserId = data("payrollId")
                                                            Precomp_Sku_Changelog.Add(newchangelog)
                                                        End Sub )
        sku_changelog_data.Clear()
        Sku_Changelog_Data = Nothing
        T("###### ====== Changelog")
        'Now we have a bunch of Changelogs which we can pass in to the Newbie function and not have to wait for responses on that table.

        'Now do the same thing with Locations
        dim Sku_Locations_Data as List(Of Dictionary(Of String, object)) = mysql.SelectDataDictionary("Select id, additionalinfo, locText, locID, locType, locWarehouse, Sku, RouteIndex from Sku_Locations JOIN whldata.locationreference ON sku_locations.LocationID = locationreference.locID JOIN whldata.location_routing ON location_routing.RouteID = locationreference.RouteID")
       'Download Locations")
        Precomp_Sku_Locations = New ConcurrentBag(Of SKULocation)
        Parallel.ForEach(Sku_Locations_Data,sub(data as Dictionary(Of String, object))
                                                            dim NewLocation as New SkuLocation
                                                            NewLocation.Additional = data("additionalInfo").ToString()
                                                            NewLocation.LocalLocationName = data("locText")
                                                            NewLocation.LocationID = data("locID")
                                                            NewLocation.LocationText = data("locText")
                                                            NewLocation.LocationType = data("locType")
                                                            newlocation.WarehouseID = data("locWarehouse")
                                                            Newlocation.Sku = data("Sku")
                                                            Precomp_Sku_Locations.Add(NewLocation)
                                                        End Sub)
       'Process Locations")
        Sku_Locations_Data.Clear()
        Sku_Locations_Data = Nothing
        T("###### ====== Locations")
        'Now we have precompiled locations too. This might be dodgy for things which still use old locations so we're gonna have to be able to fall back on the old ones, so we can't fuck up the function too mucj.

        'Now lets download the images because apparently that one does it too. Thank mr dave, or not because you didn't do the data I needed
        dim Sku_Images_Data as List(Of Dictionary(Of String, object)) = mysql.SelectDataDictionary("Select * from Sku_Images")
        Precomp_Sku_Images = New ConcurrentBag(Of SkuImage)
        Parallel.ForEach(Sku_Images_data, sub(data as Dictionary(Of String, object))
                                                            dim NewImage as New SkuImage
                                                            NewImage.FullImagePath = data("path")
                                                            NewImage.ImageFileName = data("filename")
                                                            NewImage.ImagePath = newimage.FullImagePath
                                                            NewImage.sku = data("sku")
                                                            Precomp_Sku_Images.Add(NewImage)
                                                        End Sub )
        Sku_Images_Data.Clear()
        Sku_Images_Data = Nothing
        T("###### ====== Images")
        'Images has a fallback function too.

        'Supplier info now.
         dim Sku_Supplierdata_Data as List(Of Dictionary(Of String, object)) = mysql.SelectDataDictionary("Select * from Sku_Supplierdata WHERE Invisible='False';")
        Precomp_Sku_Supplierdata = New ConcurrentBag(Of SKUSupplier)
        Parallel.ForEach(Sku_Supplierdata_Data, sub(Data as Dictionary(Of String, object))
                                                    Dim NewSupplier as New SKUSupplier
                                                    NewSupplier.Barcode = data("SupplierBarcode")
                                                    NewSupplier.BoxCode = ""
                                                    NewSupplier.CaseBarcode =  data("SupplierCaseBarcode")
                                                    NewSupplier.CaseBarcodeInner =  data("SupplierCaseInnerBarcode")
                                                    NewSupplier.Discontinued = data("isDiscontinued").Equals("True")
                                                    NewSupplier.LastModified = data("DateModified")
                                                    NewSupplier.LeadTime = data("LeadTimeWeeks")
                                                    NewSupplier.Name = data("SupplierName")
                                                    NewSupplier.OutOfStock = data("isOutOfStock").Equals("True")
                                                    NewSupplier.Price = data("SupplierPricePer")
                                                    NewSupplier.Primary = data("isPrimary").Equals("True")
                                                    NewSupplier.ReOrderCode = data("SupplierCode")
                                                    NewSupplier.ShortSku = data("SKU")
                                                    Precomp_Sku_Supplierdata.Add(NewSupplier)
                                                End Sub )
        Sku_Supplierdata_data.Clear()
        Sku_Supplierdata_data = Nothing
        T("###### ====== Suppliers")
        'Suppleirs is another one which can have fallbacks. Yipee.

        'Just gonna get the data for the Stock table because EZ. Saves querying that every time too, which sometimes comes back with no data, so we can make sure we have data now.
        Precomp_Sku_Stock = new ConcurrentBag(Of Dictionary(Of String,Object))
        While precomp_sku_stock.count < 50
            try
                Dim Temp As List(Of Dictionary(Of String,Object)) = mysql.SelectDataDictionary("SELECT * from whldata.inventory")
                Parallel.ForEach(temp,Sub(X As Dictionary(Of String,Object))
                                          Precomp_Sku_Stock.Add(X)
                                      End Sub)
            Catch ex As Exception

            End Try
        End While
        T("###### ====== Stock")
        'msgbox("done")

    End sub

    #End Region

    #Region "Old SkuColl stuff"

    '19/02/2016     Added the blank new for the serializer
    Public Sub New()

    End Sub

    '02/02/2016     Added an event to be raised when the application might want to process the new skucollection which will have happened on the other thread.
    <System.NonSerialized>
    Public Event ThreadedSkuUpdate As EventHandler

    ''' <summary>
    ''' Fast function which finds items which have the provided ID
    ''' </summary>
    ''' <param name="LocationID"></param>
    ''' <returns></returns>
    Public Function SearchLocationIDs(LocationID As Integer) As SkuCollection
        Dim coll As New SkuCollection(True)
        coll.AddRange(Where(Function(x As WhlSKU) (x.Locations.Where(Function(y As SKULocation) y.LocationID = LocationID)).Count > 0))
        Return coll
    End Function

    Public Function GetSku(Sku As String) As WhlSKU
        Try

            Return Where(Function(item As WhlSKU) item.SKU = Sku)(0)
        Catch ex As Exception
            Throw New NullReferenceException("No item with that sku currently exists.")
        End Try
    End Function

    Public Sub DownloadStatus(Status As String)
        Dim matches As ArrayList = MySQL.SelectData("SELECT * FROM whldata.whlnew WHERE new_status='" + Status + "';")
        'Loop throuigh
        For Each match As ArrayList In matches
            'Check if it's already tehre
            Dim exists As Boolean = False
            Try

                Dim test = GetSku(match(0).ToString)
                If test Is Nothing Then Throw New NullReferenceException
                exists = True
            Catch ex As Exception

            End Try
            If Not exists Then
                Me.Add(New WhlSKU(match))
            End If
        Next
    End Sub

    Public Sub New(Optional cancel As Boolean = False)
        If Not cancel Then
            DownloadSKUs()
        End If

        SetUpWorker()
    End Sub
    Public Sub New(list as IEnumerable(of WhlSKU))
        me.AddRange(list)
    End Sub
    Public Progress As Integer = 0
    Public Total As Integer = 0
    Private StatusText As String = ""
    Public ReadOnly Property RefreshStatus() As String
        Get
            Return StatusText
        End Get
    End Property

    Public Function ExcludeStatus(Status As String) As SkuCollection
        Dim Returnable As New SkuCollection(True)
        For Each child As WhlSKU In Me
            If Not child.NewItem.Status = Status Then
                Returnable.Add(child)
            End If
        Next
        Return Returnable
    End Function

    Public Sub MinimalUpdate(Changelog As Boolean)
        Try
            For Each item As WhlSKU In Me
                If Not Worker.CancellationPending Then
                    'StatusText = "UpdImg " + item.SKU.ToString
                    'item.UpdateImages()
                    'StatusText = "UpdStock " + item.SKU.ToString
                    'item.UpdateStock()
                    'StatusText = "UpdSales " + item.SKU.ToString
                    'item.SalesData.DownloadData()

                    StatusText = "UPDATE " + item.SKU.ToString
                    item.FullRefresh()
                    StatusText = "UpdShort " + item.SKU.ToString
                    item.UpdateMinimalMain()
                    If Changelog Then
                        item.DownloadChangelog()
                    End If

                End If
                'Run a minimal update on the items.

            Next
        Catch ex As InvalidOperationException
            AlertUserAboutError(ex)
        End Try

    End Sub

    '26/01/2016     GatherChildren - Gets all of the children of the shortsku, and brings them all together. Of course, it's in a SKUCollection. I'm on a list.
    Public Function GatherChildren(ShortSku As String) As SkuCollection
        Try
            Dim Newcoll As New SkuCollection(True)
            Newcoll.AddRange(Where(Function(item As WhlSKU) item.ShortSku.Equals(ShortSku)))
            Newcoll.Sort(function(sku, whlSku) sku.PackSize.CompareTo(whlSku.PackSize))
            Return Newcoll
        Catch ex As Exception
            Dim returnable As New SkuCollection(True)
            For Each child As WhlSKU In Me
                If child.ShortSku = ShortSku Then
                    returnable.Add(child)
                End If
            Next
            Return returnable
        End Try

    End Function

    Public Function GetItemsInBox(Box As Integer) As SkuCollection
        Dim ReturningList As New SkuCollection(True)
        For Each sku As WhlSKU In Me
            If sku.NewItem.Box = Box.ToString Then
                ReturningList.Add(sku)
            End If
        Next
        Return ReturningList
    End Function

    Public Function DownloadSKUs()
        '01/02/2016     Clear myself first though, otherwise we get duplicates. Baad.
        Me.Clear()

        'Download everything we have
        Dim Ink As Integer = 1
        Dim ProdList As ArrayList = MySql.SelectData("SELECT * FROM whldata.whlnew WHERE New_Status Not 'Dead'")
        Total = ProdList.Count()

        For Each Sku As ArrayList In ProdList
            CountDone = Ink.ToString + " of " + ProdList.Count.ToString
            Progress = Ink
            Add(New WhlSKU(Sku))
            System.Windows.Forms.Application.DoEvents()
            Ink += 1
        Next
        CountDone = ""
        Return Count

    End Function

    Public Sub StartThreadedWorker()
        Worker.RunWorkerAsync()
    End Sub

    Public Sub CAncelThreadedWorker()
        Try
            Worker.CancelAsync()
        Catch ex As Exception

        End Try

    End Sub

    Public ReadOnly Property WorkerBusy As Boolean
        Get
            Try
                Return Worker.IsBusy
            Catch ex As Exception
                Return False
            End Try
        End Get
    End Property

    Public Sub DisposeThreadedWorker()
        Worker.Dispose()
        Worker = Nothing
    End Sub

    Public Sub WorkerCrashHandler(sender As Object, e As RunWorkerCompletedEventArgs)
        Try
            Exceptions.handler.UEHandler(e.Error)
        Catch ex As Exception

        End Try
    End Sub

    Public Sub SetUpWorker(Optional downloadChangelogs As Boolean = False)
        Worker = New BackgroundWorker
        getChangelogs = downloadChangelogs
        AddHandler Worker.DoWork, AddressOf StartWorkerEH
        AddHandler Worker.RunWorkerCompleted, AddressOf WorkerCrashHandler
        Worker.WorkerSupportsCancellation = True
        PleaseCancelWorker = True
        SetUpNotificon()
    End Sub

    Dim PleaseCancelWorker As Boolean = False

    <System.NonSerialized>
    Private Worker As BackgroundWorker

    '02/02/2016     Sets the thread status text. Hopefully only gets set from the event handler for the processing of the threaded sku update.
    Public Sub SetStatusText(Text As String)
        StatusText = Text

    End Sub

    '03/04/2016     Added this to alert the users about errors happening in the backgeound.
    <NonSerialized>
    Dim Notificon As System.Windows.Forms.NotifyIcon
    '03/04/2016     See above.
    Public Sub SetUpNotificon()
        Notificon = New NotifyIcon
        Notificon.Text = "Background worker is active."
    End Sub
    '03/04/2016     AlertUserAboutError. Emails me but also throws up an notification from teh tray about it.
    Public Sub AlertUserAboutError(errora As Exception, Optional email As Boolean = False)
        If email Then
            Reporting.ReportException(errora, False)
        End If
        Notificon.ShowBalloonTip(4000, "Background Thread Error", "An error occured on the background thread: " + errora.Message, ToolTipIcon.Error)
    End Sub

    Dim getChangelogs As Boolean = False
    Private Sub StartWorkerEH()
        StartWorker(getChangelogs)
    End Sub

    ''' <summary>
    ''' Do not run this on the main thread. It is only public for testing purposes.
    ''' </summary>
    Public Sub StartWorker(Optional changelogs As Boolean = False)
        While Worker.CancellationPending = False
            If CountDone.Length > 2 Then
                If Not Worker.CancellationPending Then Threading.Thread.Sleep(15000)
            Else
                Try
                    StatusText = "Idle."
                    If Not Worker.CancellationPending Then CheckForNewSkus()
                    StatusText = "Idle."
                    'If Not Worker.CancellationPending Then MinimalUpdate(changelogs)
                    If Not Worker.CancellationPending Then
                        Dim SleptMins As Single = 0
                        While SleptMins < 5
                            If Worker.CancellationPending Then
                                Exit While
                            Else
                                SleptMins += (1/60)
                                Thread.Sleep(1000)
                            End If
                        End While
                    End If
                    StatusText = "Idle."
                    StatusText = "Waiting for AppProcess"
                    RaiseEvent ThreadedSkuUpdate(Me, New EventArgs)
                    StatusText = "Idle."
                    If Not Worker.CancellationPending Then Threading.Thread.Sleep(15000)
                Catch ex As Exception
                    AlertUserAboutError(ex)
                End Try

            End If
        End While
    End Sub

    Public Sub CheckForNewSkus()
        StatusText = "Downloading SKU Delta..."
        Dim theskus As ArrayList = MySQL.SelectData("SELECT sku FROM whldata.whlnew WHERE NOT New_Status='Dead';")
        StatusText = "Comparing SKU Delta..."
        For Each sku As ArrayList In theskus
            If Not Worker.CancellationPending Then

                StatusText = "Checking SKU Delta..."
                Application.DoEvents()
                Dim Exists As Boolean = False
                For Each item As WhlSKU In Me
                    If item.SKU = sku(0).ToString Then
                        Exists = True
                    End If
                Next
                If Not Exists Then
                    StatusText = "Downloading " + sku(0).ToString
                    Dim Full As ArrayList = MySQL.SelectData("SELECT * FROM whldata.whlnew WHERE sku='" + sku(0).ToString + "'")(0)
                    Dim NewItem As WhlSKU
                    Try
                        NewItem = New WhlSKU(Full)
                        Me.Add(NewItem)
                    Catch ex As Exception

                    End Try

                End If
            Else
                Exit For
            End If
        Next

    End Sub

    '25/01/2016     Advanced Search. I have literally no idea how this is going to turn out.
    ''' <summary>
    ''' This funciton does some clever searching. It also probably takes a while because it does a lot of searches and comparisons. RIP IN PIECES CPU.
    ''' </summary>
    ''' <param name="Terms">The standard search terms. Enter a nothing value to return everything. </param>
    ''' <param name="Category">Limit the search to a category. Defaults to nothiong. </param>
    ''' <param name="PrimarySupplierName">Limit the search to a supplier. Defaults to nothing. </param>
    ''' <param name="MatchAll">Boolean value to make the results match all search terms. Defaults to True. </param>
    ''' <param name="Divider">Set the search term divider. Default is space. </param>
    ''' <returns>A collection of SKUs (in a skucollection) which meets the specified criteria. </returns>
    Public Function AdvancedSearch(Terms As String, Optional Category As String = Nothing, Optional PrimarySupplierName As String = Nothing, Optional MatchAll As Boolean = True, Optional Divider As String = " ") As SkuCollection
        Dim SearchTerms As List(Of String) = Terms.Split(Divider).ToList
        Dim SearchResults As New List(Of SkuCollection)
        'Get each set of search results.
        For Each SearchTerm As String In SearchTerms
            SearchResults.Add(SearchSKUS(SearchTerm))
        Next
        'And now we can sort them.
        Dim FitsMatchall As New SkuCollection(True)

        'Checking for match alls.
        If MatchAll Then
            For Each item As WhlSKU In SearchResults(0)
                Dim Contained As Boolean = True
                For Each ResultsColl As SkuCollection In SearchResults
                    If Not ResultsColl.Contains(item) And Contained = True Then
                        Contained = False
                    End If

                Next
                If Contained Then
                    FitsMatchall.Add(item)
                End If
            Next

        Else
            For Each Coll As SkuCollection In SearchResults
                For Each Item As WhlSKU In Coll
                    If Not FitsMatchall.Contains(Item) Then
                        FitsMatchall.Add(Item)
                    End If
                Next
            Next
        End If
        'By now, anything which doesn't match all terms should be gone. If we asked for matchall. Otherwise everything should be in fitsmatchall.
        Dim FitsCategory As New SkuCollection(True)
        If Not IsNothing(Category) Then
            For Each Item As WhlSKU In FitsMatchall
                If Item.Category.ToLower = Category.ToLower Then
                    FitsCategory.Add(Item)
                End If
            Next
        Else
            For Each Item As WhlSKU In FitsMatchall
                FitsCategory.Add(Item)
            Next
        End If
        'This next bit weeds out anything which doens't match the supplier.
        Dim FitsSupplier As New SkuCollection(True)
        If Not IsNothing(PrimarySupplierName) Then
            For Each Item As WhlSKU In FitsCategory
                Try
                    If Item.GetPrimarySupplier().Name.ToLower = PrimarySupplierName.ToLower Then
                        FitsSupplier.Add(Item)
                    End If
                Catch ex As Exception

                End Try

            Next
        Else
            For Each Item As WhlSKU In FitsCategory
                FitsSupplier.Add(Item)
            Next
        End If
        'Weed out any nulls.
        Dim Returnable As New SkuCollection(True)
        For Each item As WhlSKU In FitsSupplier
            If Not IsNothing(item) Then
                Returnable.Add(item)
            End If
        Next

        'Finally, we're done. Dedupe for good measure.
        Return DupeFilter(Returnable)
    End Function

    '17/02/2016     Searches the newly added barcode list. This will be used to things which are cross-coded don't cause conflicts in picking/packing.
    Public Function SearchBarcodes(SearchTerm As String) As SkuCollection
        Dim returningList As New SkuCollection(True)
        SearchTerm = SearchTerm.ToLowerInvariant()
        For Each SKU As WhlSKU In Me
            '21/01/2016     Add this loop as a try, because the removal of a keyword in forking can break it.
            Try
                For Each searchy As String In SKU.Barcodes
                    If searchy.ToLower = SearchTerm Then
                        returningList.Add(SKU)
                    End If
                Next
            Catch ex As Exception

            End Try

        Next
        Return DupeFilter(returningList)
    End Function

    Public Function SearchSKUS(SearchTerm As String, Optional ExcludeBundles As Boolean = False) As SkuCollection

        If searchTerm.Trim() = "" Then
            Return me
        End If
        searchTerm = searchTerm.ToLowerInvariant()
        Dim searchEnumerable As IEnumerable(Of WhlSKU)
        Select Case excludeBundles
            Case True
                searchEnumerable = me.Where(Function(x) Not x.isBundle And x.SearchKeywords.ConvertAll(Function(z) z.ToLower()).Any(Function(y) y.Contains(searchTerm)))
                Exit Select
            Case Else
                searchEnumerable = me.Where(Function(x) x.SearchKeywords.ConvertAll(Function(z) z.ToLowerInvariant()).Any(Function(y) y.Contains(searchTerm)))
                Exit Select
        End Select
        Dim returningList = New SkuCollection(True)
        returningList.AddRange(searchEnumerable.Distinct())
        Return returningList

        #Region "Old Search Skus"
        'Dim searchEnumerable As IEnumerable(Of WhlSKU)

        'If excludeBundles Then
        '    searchEnumerable = From sku In Me Where sku.SearchKeywords.Any(Function(d) d.ToLower().Contains(searchterm.ToLower())) AndAlso sku.isBundle = False select sku
        'Else
        '    searchEnumerable = From sku In Me Where sku.SearchKeywords.Any(Function(d) d.ToLower().Contains(searchterm.ToLower())) select sku
        'End If
        'Dim returningList as New SkuCollection(True)
        'dim filteredResults = searchEnumerable.Distinct
        'returninglist.AddRange(filteredResults)
        'Return ReturningList

        'For Each SKU As WhlSKU In Me
        '    '21/01/2016     Add this loop as a try, because the removal of a keyword in forking can break it.
        '    Try
        '        For Each searchy As String In SKU.SearchKeywords
        '            If searchy.ToLower.Contains(SearchTerm.ToLower) Then
        '                If ExcludeBundles And SKU.isBundle Then
        '                Else
        '                    returningList.Add(SKU)
        '                End If
        '            End If
        '        Next
        '    Catch ex As Exception

        '    End Try

        'Next
        'Return DupeFilter(returningList)
        #End Region
    End Function
        Public Function SearchSkusExact(SearchTerm As String, Optional ExcludeBundles As Boolean = False) As SkuCollection

        If searchTerm.Trim() = "" Then
            Return me
        End If
        searchTerm = searchTerm.ToLowerInvariant()
        Dim searchEnumerable As IEnumerable(Of WhlSKU)
        Select Case excludeBundles
            Case True
                searchEnumerable = me.Where(Function(x) Not x.isBundle And x.SearchKeywords.ConvertAll(Function(z) z.ToLower()).Any(Function(y) y = searchTerm))
                Exit Select
            Case Else
                searchEnumerable = me.Where(Function(x) x.SearchKeywords.ConvertAll(Function(z) z.ToLowerInvariant()).Any(Function(y) y = searchTerm))
                Exit Select
        End Select
        Dim returningList = New SkuCollection(True)
        returningList.AddRange(searchEnumerable.Distinct())
        Return returningList

    End Function

    Public Function SearchSKUSReturningSingleSku(SearchTerm As String) As WhlSKU
        Dim returningSKU As New WhlSKU
        For Each SKU As WhlSKU In Me
            '21/01/2016     Add this loop as a try, because the removal of a keyword in forking can break it.
            Try
                'Dim continueSearch As Boolean = True
                For Each searchy As String In SKU.SearchKeywords
                    'If continueSearch = True Then
                    If searchy.ToLower.Contains(SearchTerm.ToLower) Then
                        returningSKU = SKU
                        Exit For
                        'continueSearch = False
                    End If
                    'End If
                Next
            Catch ex As Exception

            End Try

        Next
        Return returningSKU
    End Function
    Public Function SearchSkusReturningSingleLinq(SearchTerm As String) As WhlSKU

        Dim returningSKU As New WhlSKU
        For Each SKU As WhlSKU In Me
        if SKU.SearchKeywords.Any(Function(searchy) searchy.ToLower.Contains(SearchTerm.ToLower)) Then
                    returningSKU = SKU
                exit for
        End If

        Next
        Return returningSKU
    End Function

    Public Function DupeFilter(InitColl As SkuCollection) As SkuCollection
        Dim SKUList As New List(Of String)
        Dim ReturnColl As New SkuCollection(True)
        For Each sku As WhlSKU In InitColl
            If SKUList.Contains(sku.SKU) Then
                'SKIP
            Else
                SKUList.Add(sku.SKU)
                ReturnColl.Add(sku)
            End If
        Next
        Return ReturnColl
    End Function

    ''' <summary>
    ''' Sorts out the weird ordering provided by racing threads.
    ''' </summary>
    Public Sub FixOrder()
        Me.Sort(Function(x, y) x.SKU.CompareTo(y.SKU))
    End Sub

    Public CountDone As String = ""

    ''' <summary>
    ''' Mixes everything down so you only get one packsize per shortsku. Usually the smallest available, I.E. singles.
    ''' </summary>
    ''' <returns></returns>
    Public Function MakeMixdown() As SkuCollection
        Dim mixdown As New SkuCollection(True)
        'Sort the list before doing something
        FixOrder()
        'And then prepare to do the rewritten mixodwn.
        Dim ShortSkus As New List(Of String)
        'And now actually do the mixodwn.
        For Each child As WhlSKU In Me
            If Not ShortSkus.Contains(child.ShortSku) Then
                mixdown.Add(child)
                ShortSkus.Add(child.ShortSku)
            End If
        Next
        Return mixdown
    End Function

    '03/02/2016     Added a search by Status thing so we can find things by their status. Used in explorer for example for populating the status trees.
    Public Function SearchByStatus(Status As String) As SkuCollection
        Dim Returnable As New SkuCollection(True)
        For Each item As WhlSKU In Me
            If item.NewItem.Status = Status Then
                Returnable.Add(item)
            End If
        Next
        Return Returnable
    End Function

    Public Function SearchBySuppName(Supplier As String) As SkuCollection
        Dim Returnable As New SkuCollection(True)
        For Each item As WhlSKU In Me
            If Not IsNothing(item.SearchSupplierByCode(Supplier)) Then
                Returnable.Add(item)
            End If
        Next
        Return Returnable
    End Function

    #End Region
End Class

Public Class SkusDataController
    Public Sub SaveData(Collection As SkuCollection)
        ' Create file by FileStream class
        Dim fs As FileStream = New FileStream(My.Computer.FileSystem.SpecialDirectories.CurrentUserApplicationData + "\SkusCache.bin", FileMode.OpenOrCreate)

        ' Creat binary object
        Dim bf As New BinaryFormatter()
        Collection.SetStatusText("Saving data...")
        Application.DoEvents()
        ' Serialize object to file
        bf.Serialize(fs, Collection)

        fs.Close()
        Collection.SetStatusText("Data Saved. ")

    End Sub

    Public Function SkusFileInfo() As FileInfo
        Return New FileInfo(My.Computer.FileSystem.SpecialDirectories.CurrentUserApplicationData + "\SkusCache.bin")
    End Function

    Public Function LoadDataFile(Optional path As String = Nothing) As SkuCollection
        Dim bf As New BinaryFormatter()
        ' Open file and deserialize to object again
        If IsNothing(path) Then
            path = My.Computer.FileSystem.SpecialDirectories.CurrentUserApplicationData + "\SkusCache.bin"
        End If
        Dim fsRead As New FileStream(path, FileMode.Open)
        Dim Cool As SkuCollection = bf.Deserialize(fsRead)
        fsRead.Close()
        Return Cool
    End Function
End Class

''' <summary>
''' GOOD LUCK WITH THIS ONE. Refertou your comprehensive guide to bad programming for more info on this one.
''' </summary>
<System.Serializable()>
Public Class WhlSKU

    Public Overrides Function ToString() As String
        Return $"{Sku} - {Title.Label}"
    End Function

    '26/04/16 - This was added to ensure issues in opening the same file twice in NewItem / The main inventory manager screen won't happen.
    'Defaults to false. If false, opening is allowed, changes to true on opening, changes to false on closing, will not open while true.
    'Other associated locations: NewItemAssist, the NewItemDialog_FormClosed function, and NewItem, the BoxExplorer_DoubleClick function.
    <NonSerialized>
    Public IsOpen As Boolean = False

    Public Sub SetDeliveryNote(NewNote As String, user As Employee)

        '18/05/16 - I guess closing the login box allowed the user to proceed anyway, leading to an emailed report. Fixing!
        If Not user Is Nothing Then
            If user.Permissions.DeliveryMods Then
                Dim Query As String = "UPDATE whldata.whlnew SET deliverynote='" + NewNote + "' WHERE Sku='" + SKU + "'"
                MySQL.insertUpdate(Query)

            Else
                MsgBox("You don't have permission to do that!")
            End If
            MsgBox("Please sign in to do this.")
        End If

    End Sub

    '29/02/2016     Gets the total stock value of the item, with flexibility for new-item status products which falls back on the init value.
    Public Function GetStockValue() As Single
        If isBundle Or ExtendedProperties.SplitBundle Then
            Return 0
        Else
            If NewItem.Status = "Entered" Or NewItem.Status = "Priced" Or NewItem.Status = "Photographed" Or NewItem.Status = "Listed" Or NewItem.Status = "Pending" Or NewItem.Status = "Exported" Then
                'New item, fall back on the init
                Try

                    If Not Stock.Total = 0 Then

                        Return (Stock.Total * (GetPrimarySupplier.Price))
                    Else
                        If PackSize = 0 Then
                            Return (NewItem.InitStock * (GetPrimarySupplier.Price))
                        Else
                            Return (NewItem.InitStock * (GetPrimarySupplier.Price * PackSize))
                        End If
                    End If

                Catch ex As Exception
                    Return (NewItem.InitStock * Price.Net)
                End Try

            Else
                'Use the actual
                Return (Stock.Total * Price.Net)
            End If
        End If

    End Function

    Public ReadOnly Property StockValue As Single
        Get
            Return GetStockValue()
        End Get
    End Property

    '03/02/2016     Fully refreshes the item from the database. Probably will be used liberally during newitem,
    Public Sub FullRefresh()
        Dim InitQuery As String = "SELECT * FROM whldata.whlnew WHERE SKU='" + Me.SKU + "';"

        Dim Response As ArrayList = MySql.SelectData(InitQuery)
        If Response.Count = 1 Then
            Newbie(Response(0))
        End If
    End Sub

    Public Function SetGroupStatus(NewStatus As String, User As Employee, Optional reason As String = "") As Integer
        '23/05/16 - Added handling for cancelling authentication
        If Not User Is Nothing Then
            If User.Permissions.SkuStatus Then
                Me.RecordChangelog(User, "Group status update: " + NewStatus + " | " + reason)
                Return SetGroupStatusLocally(NewStatus)
            ElseIf User.Permissions.SaveChangesToSKU Then
                Me.RecordChangelog(User, "Group status update: " + NewStatus + " | " + reason)
                Return SetGroupStatusLocally(NewStatus)
            Else
                Throw New Exceptions.InsufficientPermissionsException("You don't have permission to set statuses.")
            End If
        Else
            Return Nothing
        End If

    End Function

    Private Function SetGroupStatusLocally(NewStatus As String)
        Dim Query As String = "UPDATE whldata.whlnew SET New_Status='" + NewStatus + "' WHERE ShortSku='" + ShortSku + "';"
        Return MySql.insertupdate(Query)
    End Function

    Private Function SetActiveFromLinnworks() As Integer
        Dim Query As String = "UPDATE whldata.whlnew SET New_Status='Active', sLevel=0, min=0, total=0, New_TransferBox='0', HasBeenListed='True' WHERE Sku LIKE '" + ShortSku + "%';"
        Return MySql.insertupdate(Query)
    End Function

    '29/02/2016     Runs through teh duppliers and checks if they're al discon. If they are, returns true.
    <DebuggerStepThrough>
    Public Function IsDiscontinuedOnAllSuppliers() As Boolean
        Dim AllDiscon As Boolean = True
        For Each supp As SKUSupplier In Suppliers
            If Not supp.Discontinued Then
                AllDiscon = False
            End If
        Next
        Return AllDiscon
    End Function

    '03/02/2016     Added to refresh some little data about an item. Things which are saved on the main data though, like status and box.
    Public Sub UpdateMinimalMain()
        Dim InitQuery As String = "SELECT New_Status, New_TransferBox FROM whldata.whlnew WHERE SKU='" + Me.SKU + "';"

        Dim Response As ArrayList = MySql.SelectData(InitQuery)
        If Response.Count = 1 Then

            If Response(0)(0).ToString = "Pending" Then
                'Check Linnworks for stock existing.
                Dim Linnsponse As ArrayList = MySql.SelectData("SELECT * FROM whldata.inventory WHERE SKU='" + Me.SKU + "'")
                If Linnsponse.Count > 0 Then
                    'We have entries, so change the box and status.
                    SetActiveFromLinnworks()
                    Me.NewItem.Box = "0"
                    Me.NewItem.Status = "Active"
                    RecordChangelog((New EmployeeCollection).FindEmployeeByID(0), "Automatically marked ACTIVE at " + My.Computer.Name)
                Else
                    Me.NewItem.Box = Response(0)(1).ToString
                    Me.NewItem.Status = Response(0)(0).ToString
                End If
            ElseIf Response(0)(0).ToString = "Inactive" Then
                'Check Linnworks for stock existing.
                'Dim Linnsponse As ArrayList = MySql.SelectData("SELECT * FROM whldata.inventory WHERE SKU='" + Me.SKU + "'")
                'If Linnsponse.Count = 0 Then
                '    'Check if the table is empty then.
                '    If MySql.SelectData("SELECT Count(*) FROM whldata.inventory;")(0)(0).ToString = "0" Then
                '        SetGroupStatusLocally("Dead")
                '        SwapBoxLocally("0")
                '        RecordChangelog((New EmployeeCollection).FindEmployeeByID(0), "Automatically marked DEAD at " + My.Computer.Name)
                '        Me.NewItem.Status = "Dead"
                '    End If
                'Else
                '    Me.NewItem.Box = Response(0)(1).ToString
                '    Me.NewItem.Status = Response(0)(0).ToString
                'End If
            ElseIf Response(0)(0).ToString = "Active" Then
                If IsDiscontinuedOnAllSuppliers() Then
                    SetGroupStatusLocally("Inactive")
                    SwapBoxLocally("0")
                    RecordChangelog((New EmployeeCollection).FindEmployeeByID(0), "Automatically marked INACTIVE at " + My.Computer.Name)
                    Me.NewItem.Status = "Inactive"
                Else
                    Me.NewItem.Box = Response(0)(1).ToString
                    Me.NewItem.Status = Response(0)(0).ToString
                End If
                'ElseIf Response(0)(0).ToString = "Dead" Then
                '    Dim Linnsponse As ArrayList = MySql.SelectData("SELECT * FROM whldata.inventory WHERE SKU='" + Me.SKU + "'")
                '    If Linnsponse.Count > 0 Then
                '        SetGroupStatusLocally("Inactive")
                '        SwapBoxLocally("0")
                '        RecordChangelog((New EmployeeCollection).FindEmployeeByID(0), "Automatically marked INACTIVE at " + My.Computer.Name)
                '        Me.NewItem.Status = "Inactive"
                '    End If

            End If

        End If
    End Sub

    '03/02/2016     Create the box transfer function. This simply allows us to switch the box of the item.
    Public Sub SwapBox(NewBox As String, User As Employee)
        '23/05/16 - Added handling for cancelling authentication
        If Not User Is Nothing Then
            If User.Permissions.SaveChangesToSKU Then
                SwapBoxLocally(NewBox)
            Else
                Throw New Exceptions.InsufficientPermissionsException("The authenticated user does not have permission to change the box. ")
            End If
        End If
    End Sub

    '14/03/2016     Added the swapbox with no user for private use.
    Private Sub SwapBoxLocally(NewBox As String)
        Me.NewItem.Box = NewBox
        MySql.insertupdate("UPDATE whldata.whlnew SET New_TransferBox='" + NewBox + "' WHERE ShortSku='" + Me.ShortSku + "'")
    End Sub

    '18/04/2016     Added to find a changelog with a matching reason
    Public Function GetLatestChangelog(Optional search As String = "") As SKUChangelog
        Dim returnable As SKUChangelog = Nothing
        For Each entry As SKUChangelog In Changelog
            If search.Length = 0 Then
                returnable = entry
            Else
                If entry.Reason.ToLower.Contains(search.ToLower) Then
                    returnable = entry

                End If
            End If
        Next
        Return returnable
    End Function

    '04/02/2016     RefreshSearchKeywrods - Refreshes the search keywords
    Public Sub RefreshSearchKeywords()
        'Search Terms
        SearchKeywords.Clear()
        SearchKeywords.Add(ExtendedProperties.GS1Barcode)
        SearchKeywords.Add(SKU)
        SearchKeywords.Add(ShortSku)
        SearchKeywords.Add(ShortSku.Substring(2))
        For Each Supplier As SKUSupplier In Suppliers
            SearchKeywords.Add(Supplier.Barcode)
            SearchKeywords.Add(Supplier.CaseBarcode)
            SearchKeywords.Add(Supplier.BoxCode)            '08/12/2015 - Events, dates and some supplier info added.
            SearchKeywords.Add(Supplier.CaseBarcodeInner)   '08/12/2015 - Events, dates and some supplier info added.
            SearchKeywords.Add(Supplier.ReOrderCode)
        Next
        For Each Shelf As SKULocation In Locations
            SearchKeywords.Add(Shelf.LocalLocationName)
        Next
        If Not IsNothing(Composition) Then
            For Each comp As WhlSKU In Composition
                SearchKeywords.AddRange(comp.SearchKeywords)
            Next
        End If
        SearchKeywords.Add(Title.Label)
        SearchKeywords.Add(Title.Invoice)
    End Sub

    '03/02/2016     Updates the status of the item in the database and in the local cache.
    Public Sub SetStatus(Status As String, User As Employee)
        If User.Permissions.SkuStatus Then
            Me.NewItem.Status = Status
            SetGroupStatusLocally(Status)
        ElseIf User.Permissions.SaveChangesToSKU Then
            Me.NewItem.Status = Status
            SetGroupStatusLocally(Status)
        ElseIf User.Permissions.ModLocation Then 'Location Modifier in Inventory Control
            Me.NewItem.Status = Status
            SetGroupStatusLocally(Status)
        Else
            Throw New Exceptions.InsufficientPermissionsException("The authenticated user does not have permission to update the status of the item. ")
        End If
    End Sub

    '09/02/2016     Updates the notes. Uses the SkUStatus permissions because "Temporary"
    Public Sub SetNote(User As Employee)
        '23/05/16 - Added handling for cancelling authentication
        If Not User Is Nothing Then
            '4/5/16 - Added .replace code for pounds and euros.
            Dim Query As String = "UPDATE whldata.whlnew SET New_Note='" + NewItem.Note.Replace("\", "\\").Replace("'", "\'").Replace("£", "\\u00A3").Replace("€", "\\u20AC") + "' WHERE ShortSku='" + Me.ShortSku + "'"
            If User.Permissions.SkuStatus Then
                MySql.insertupdate(Query)
            ElseIf User.Permissions.SaveChangesToSKU Then
                MySql.insertupdate(Query)
            Else
                Throw New Exceptions.InsufficientPermissionsException("The authenticated user does not have permission to update the status of the item. ")
            End If
        End If

    End Sub

    '24/10/2016 - Gets the first location which is a picking location.
    Public Function GetLocation(type As SKULocation.SKULocationType) As SKULocation
        Dim target As SKULocation = Nothing
        For Each location As SKULocation In Locations
            If Not IsNothing(location) Then
                If location.LocationType = type Then
                    target = location
                    Exit For
                End If
            End If
        Next
        If Not IsNothing(target) Then
            Return target
        Else
            If Locations.Count > 0 Then
                If Locations(0).LocationID = 0 Then
                    Reporting.ReportException(New NoWarehouseException(SKU + " Missing warehouse. Shelf: " + Locations(0).LocalLocationName), False)
                    Return Locations(0)
                End If
            End If
        End If
        Return Nothing
    End Function
    Public Function GetLocationsByType(Types() As SKULocation.SKULocationType) As List(Of SKULocation)
        locations.Sort(Function(Xaml as SkuLocation, Yaml As SkuLocation) xaml.LocationText.Compareto(yaml.LocationText))
        Return Locations.Where(Function(x As SKULocation) Types.Contains(x.LocationType)).ToList
    End Function
    Public Function GetLocationsByType(Type As SKULocation.SKULocationType) As List(Of SKULocation)
        Return Locations.Where(Function(x As SKULocation) Type = x.LocationType).ToList
    End Function

    Friend Class NoWarehouseException
        Inherits Exception

        Friend Sub New(msg As String)
            MyBase.New(msg)
        End Sub

    End Class

    '02/02/2016     Added to enable cloning form NewItem so we Celyn can easily just copy stuff.
    Public Function CloneWithNewSKU(NewShortSku As String) As WhlSKU
        'This works like fork for pack sizde or whatever it's called, but it's not the same.
        If NewShortSku.Length = 7 And NewShortSku.StartsWith("10") Then
            Dim Returnable As WhlSKU = Me.ShittyClone()
            Returnable.SKU = NewShortSku + "xxxx"
            Returnable.ShortSku = NewShortSku

            Return Returnable
        Else
            Throw New Exceptions.InvalidShortSkuFormat("The supplied SKU is not in a valid format. The allowed format is 7 characters, starting with ""10""")
        End If
    End Function

    '27/01/2016     Created to enable misc changelog saves.
    Public Sub UpdateLog(User As Employee, Text As String)
        Dim ChangesQuery As String = ""
        ChangesQuery = "INSERT INTO whldata.sku_changelog (shortsku, payrollId, reason, datetimechanged) VALUES ('" + ShortSku + "','" + User.PayrollId.ToString + "','" + Text + "','" + Now.ToString("dd/MM/yyyy HH:mm:ss") + "');"
        Dim response3 As Object = MySQL.insertUpdate(ChangesQuery)
    End Sub

    Public Sub IncludeImages()
        For Each imageitem As SKUImage In Images
            Try
                imageitem.ImageData = System.Drawing.Image.FromFile(imageitem.ImagePath)
            Catch ex As Exception

            End Try

        Next
    End Sub

    Public Sub SaveLocations(emp As Employee) 'Looking for location stuff, and I find this. And there aren't any references. Bleh.
        If emp.Permissions.SaveChangesToSKU Then
            SaveLocationsLocally(Nothing)
            RecordChangelog(emp, "Updated Location.")
        Else
            Throw New Exceptions.InsufficientPermissionsException("You don't have permission to save location data.") 'Spellcheck was here, dpon't is a loser!
        End If

    End Sub

    '10/08/2016 Moved location saving to a new function.
    Private Sub SaveLocationsLocally(Optional ByRef savestatuses As List(Of String) = Nothing, Optional refreshthelocations As Boolean = False)
        'Ok, items couldn't leave the constructor because of this. Sidestepping. Gonna need to set this to true if it's ever required
        If refreshthelocations Then
            RefreshLocations()
        Else
            MySQL.insertUpdate("DELETE FROM whldata.Shortsku_locations WHERE ShortSku='" + ShortSku + "';")
        End If
        For Each location As SKULocation In Locations
            '21/01/2016     Updated to save per SKU instead of per ShortSku.
            Dim locationsquery As String
            If location.Additional = " " Or location.Additional = "" Then
                location.Additional = 0
            End If

            Dim NewTableID As String = Shortsku + "_" + location.LocationID.ToString
            'Already exists and just needs updating
            locationsquery = "Replace INTO whldata.Shortsku_locations (id, shelfName, LocationId, Shortsku, Stocklevel) VALUES ('" + NewTableID.ToString + "','" + location.LocationText.ToString + "','" + location.LocationID.ToString + "','" + Shortsku.ToString + "','" + location.Additional + "')"
            Dim response1 As Object = MySQL.insertUpdate(locationsquery) '...it sends the query.
            If Not IsNothing(savestatuses) Then 'Ok, so when the item is saved through the current method...

                If response1.ToString.Length < 10 Then
                    savestatuses.Add("Location Data for '" + location.LocationText + "' saved successfully.")
                Else
                    savestatuses.Add("Location Data for '" + location.LocationText + "' Error: " + response1.ToString)
                End If 'And responds by adding the result to the list
            End If
        Next

    End Sub

    '20/01/2016     How the hell don't we have a fucntion for this until now???? Well now we do. Does what it says on the tin - Gets the primary supplier.
    Public Function GetPrimarySupplier() As SKUSupplier

        For Each supp As SKUSupplier In Suppliers
            If supp.Primary Then
                Return supp
            End If
        Next

        If Suppliers.Count > 0 Then
            Suppliers(0).Primary = True
            Return Suppliers(0)
        End If

        Return Nothing
    End Function

    '20/01/2016     Recalculates all of the current prices. Uses the price of the primary supplier * packsize as the net.
    Public Sub RecalculatePrices(Retail As Single)
        Price.Retail = Retail
        Try
            Price.Net = GetPrimarySupplier().Price * PackSize
        Catch ex As Exception
            'Can't get the price for some reason. Ah well, just use the one we have.
        End Try
        RecalculateCosts()
        Price.Profit = Price.Retail - Costs.Total - Price.Net
        '20/01/2016     Added conditional margin calc so it supports negative margins.
        If Price.Profit < 0 Then 'Negative margin
            Price.Margin = 0
        Else
            Price.Margin = ((Price.Retail - Costs.Total) / Price.Net) - 1 'Normal method
        End If

        Price.Gross = Price.Net + Costs.VAT
    End Sub

    '21/01/2016     ShittyClone: Serialises the object, saves it, loads it and then unserailises it, then returns it. LOL it's shit.
    Private Function ShittyClone() As WhlSKU
        Dim DataController As New GenericDataController
        Dim folder As String = My.Computer.FileSystem.SpecialDirectories.CurrentUserApplicationData
        Dim file As String = "temp.bin"

        Dim Filename As String = folder + "\" + file
        DataController.SaveDataToFile(file, Me, folder)
        Return DataController.LoadSKUFile(Filename)
    End Function

    '21/01/2016     Added to facilitiate forking items for new packsizes by getting the current, making applicable changes and then returning the new one for addition to the
    '               original collection, and also for later saving and modification.
    Public Function ForkSkuPackSize(NewPackSize As Integer) As WhlSKU
        Dim ReturnMe As WhlSKU = ShittyClone()
        ReturnMe.SKU = ShortSku + NewPackSize.ToString.PadLeft(4, "0")
        ReturnMe.PackSize = NewPackSize
        If PackSize = 0 Then
            ReturnMe.Profile.Weight = Profile.Weight * NewPackSize
        Else
            ReturnMe.Profile.Weight = Profile.Weight * (NewPackSize / PackSize)
        End If
        For Each location As SKULocation In Locations

            location.LocationTableID = ReturnMe.SKU + "_" + ((New Random).Next(Int32.MaxValue)).ToString

        Next
        ReturnMe.Price.Retail = 0.01
        ReturnMe.ExtendedProperties.LabourCode = "1"
        ReturnMe.SearchKeywords.Remove(SKU)
        ReturnMe.SearchKeywords.Remove(ExtendedProperties.GS1Barcode)
        ReturnMe.SearchKeywords.Add(ReturnMe.SKU)
        ReturnMe.ExtendedProperties.GS1Barcode = ""
        ReturnMe.ExtendedProperties.Envelope = "Please Select"
        ReturnMe.NewItem.IsListed = True
        '21/01/2016     Crashes if too heavy, so check if it needs couriering, and if it does then we can activate courier. We're just going to say above 2.5kg because screw variables.
        If ReturnMe.Profile.Weight > 2500 Then
            'Activate courier
            ReturnMe.ExtendedProperties.NeedsCourier = True
        End If

        ReturnMe.RecalculatePrices(0.01)
        Return ReturnMe
    End Function

    '18/01/2016     Added cost recalculation to whlsku inline, but added it as a Public sub so recalculation can be done later. This might be an awful idea. We'll try it.
    Public Function RecalculateCosts() As Single
        Dim Envelopae As String = ExtendedProperties.Envelope
        Dim Retailprice As Single = Price.Retail
        Dim Envman As New EnvelopeCollection
        Dim Env As Envelope = Envman.GetEnvelope(Envelopae, True)
        EnvelopeObject = Env
        Dim Feeman As New Fees.FeeManager
        Dim Ispacket As Boolean = Envelopae.StartsWith("P")
        If Not IsNothing(Env) Then
            Costs.Envelope = Feeman.GetEnvelopePrice(Env)
            Costs.Postage = Feeman.GetPostagePrice(Profile.Weight + Env.Weight, Ispacket, ExtendedProperties.NeedsCourier, ExtendedProperties.Oversized)
        Else
            Costs.Envelope = 0
            Costs.Postage = 0
        End If

        Costs.Packing = Costs.Postage + Costs.Envelope
        Costs.Fees = Feeman.GetListingFees(Retailprice)
        Costs.Labour = Feeman.GetLabourPrice(ExtendedProperties.LabourCode)
        Costs.VAT = Feeman.GetVATCost(Retailprice)
        Costs.Total = Costs.Packing + Costs.Fees + Costs.Labour + Costs.VAT

        Return Costs.Total

    End Function

    '04/02/2016     Adds to stock levels into the database which are set in the pricing screen. They get overridden later on anyway.
    Public Sub SetNewStock(Level As Integer, Optional Minimum As Integer = 0)
        If Minimum < 0 Then
            Minimum = 0
        End If
        Dim Total As Integer = Level + Minimum
        If Minimum = 0 Then
            Minimum = -1
        End If
        Dim query As String = "UPDATE whldata.whlnew SET slevel=" + Level.ToString + ", min=" + Minimum.ToString + ", total=" + Total.ToString + " WHERE sku='" + SKU + "';"
        Dim response As Object = MySQL.insertUpdate(query)
        Stock.Level = Level
        Stock.Minimum = Minimum
        Stock.Total = Total

    End Sub

    ''' <summary>
    ''' This function will save all properties to the database.
    ''' </summary>
    ''' <returns>Returns a list of strings which display the update status of each element of the product in a user fiendly way.</returns>
    Public Function SaveChanges(LogUser As Employee, Optional LogReason As String = "None provided") As List(Of String)

        '23/05/16
        If IsNothing(LogUser) Then
            MsgBox("Changes not saved")
            Return Nothing
        Else
            Dim SaveStatuses As New List(Of String)

            '============================================================================ Aww shit here we go...
            '14/01/2016     Added distinguish (ext35) to teh save, and load functions.
            '15/01/2016     Added Inner (New_inner, 117), InitStock(initialquantity, 38), InitLevel(initiallevel, 39), InitMinimum(120), ListPriority(121), and
            '               IsListed(122) to save and load.
            Dim WhlNewQuery As String
            WhlNewQuery = "REPLACE INTO whldata.whlnew (sku, itemtitle, Net, gross, retail, profit, margin, Envelope, weight, PackSize, labour, courier, DeliveryNote, packingcost, " _
                + " postagecost, envcost, feescost, labourcost, vatcost, totalcost, parts, screws, ext33, ShortSku, gs1, labelshort, linnshort, " _
                + " New_Brand, New_Description, New_Finish, New_Size, New_Note, New_TransferBox, New_Status, IsPair, ext35, New_Inner, initialquantity, initiallevel, InitMinimum, ListPriority, IsListed, Pieces, slevel, min, total, PickNote, PackNote, SplitBundle, KnowledgeNote, isBundle, HasBeenListed) VALUES" _
                + " ('" + SKU.ToString + "','" + Title.Invoice.ToString.Replace("\", "\\").Replace("'", "\'").Replace(",", "") + "','" + Price.Net.ToString + "','" + Price.Gross.ToString + "','" + Price.Retail.ToString + "','" + Price.Profit.ToString + "','" _
                + Price.Margin.ToString + "','" + ExtendedProperties.Envelope.ToString + "','" + Profile.Weight.ToString + "','" + PackSize.ToString + "','" + ExtendedProperties.LabourCode.ToString _
                + "','" + ExtendedProperties.NeedsCourier.ToString + "','" + DeliveryNote.ToString + "','" + Costs.Packing.ToString + "','" + Costs.Postage.ToString + "','" + Costs.Envelope.ToString _
                + "','" + Costs.Fees.ToString + "','" + Costs.Labour.ToString + "','" + Costs.VAT.ToString + "','" + Costs.Total.ToString + "','" + ExtendedProperties.Parts.ToString + "','" _
                + ExtendedProperties.Screws.ToString + "','" + Category.ToString + "','" + ShortSku.ToString + "','" + ExtendedProperties.GS1Barcode.ToString + "','" + Title.Label.ToString.Replace("\", "\\").Replace("'", "\'") + "','" _
                + Title.Linnworks.ToString.Replace("\", "\\").Replace("'", "\'") + "','" + NewItem.Brand.ToString + "','" + NewItem.Description.ToString + "','" + NewItem.Finish.ToString + "','" + NewItem.Size.ToString + "','" _
                + NewItem.Note.ToString.Replace("\", "\\").Replace("'", "\'") + "','" + NewItem.Box.ToString + "','" + NewItem.Status.ToString + "','" + ExtendedProperties.IsPair.ToString + "','" + Title.Distinguish.ToString + "','" _
                + ExtendedProperties.Inner.ToString + "','" + NewItem.InitStock.ToString + "','" + NewItem.InitLevel.ToString + "','" + NewItem.InitMinimum.ToString + "','" + NewItem.ListPriority.ToString + "','" _
                + NewItem.IsListed.ToString + "','" + Pieces.ToString + "','" + Stock.Level.ToString + "','" + Stock.Minimum.ToString + "','" + Stock.Total.ToString + "','" + PickNote.ToString + "','" + PackNote.ToString + "','" + ExtendedProperties.SplitBundle.ToString + "','" + KnowledgeNote.ToString + "','" + isBundle.ToString + "','" + NewItem.HasBeenListed.ToString + "');"
            Dim Response As Object = MySQL.insertUpdate(WhlNewQuery)
            If Response.ToString.Length < 10 Then
                SaveStatuses.Add("Main Data saved successfully.")
            Else
                SaveStatuses.Add("Main Data Error: " + Response.ToString)
            End If

            'Moved location saving to its; own function.
            '   SaveLocationsLocally(SaveStatuses) We shouldn't be saving locations here - We'll end up overwriting chnages probably.

            'Gotta make sure ALL the items get the oversized state setting, not just the active item
            SaveSizeState()

            'Remove old suppliers
            MySQL.insertUpdate("DELETE FROM whldata.sku_supplierdata WHERE SKU='" + ShortSku + "';")
            'Now we save the suppliers to the suppliersdata table.
            For Each supplier As SKUSupplier In Suppliers
                Dim suppquery As String
                If supplier.LastModified.Contains("Old Type") Then
                    supplier.LastModified = Now.ToString("dd/MM/yyyy HH:mm:ss")
                End If
                '28/01/2016     Forgot to set the TableDataID (v2)
                supplier.TableDataId = ShortSku + "_" + supplier.Name.ToUpper
                '25/01/2016     Dues to able restructuring to eliminate suppdata duplication, this has been changed to use the new key "SkuSuppKey", a concat of SKU_SuppName.
                suppquery = "REPLACE INTO whldata.sku_supplierdata (SkuSuppKey,SKU,SupplierName, SupplierCode, SupplierBarcode, SupplierCaseBarcode, SupplierPricePer, " _
                    + "isPrimary, isDiscontinued, isOutOfStock, DateModified, LeadTimeWeeks, SupplierCaseInnerBarcode, SupplierBoxCode) VALUES ('" + supplier.TableDataId.ToString.ToUpper + "','" _
                    + ShortSku.ToString + "','" + supplier.Name.ToString.ToUpper + "','" + supplier.ReOrderCode.ToString + "','" + supplier.Barcode.ToString + "','" + supplier.CaseBarcode.ToString _
                    + "','" + supplier.Price.ToString + "','" + supplier.Primary.ToString + "','" + supplier.Discontinued.ToString + "','" + supplier.OutOfStock.ToString + "','" +
                    supplier.LastModified.ToString + "','" + supplier.LeadTime.ToString + "','" + supplier.CaseBarcodeInner.ToString + "','" + supplier.BoxCode.ToString + "')"

                Dim response1 As Object = MySQL.insertUpdate(suppquery)
                If response1.ToString.Length < 10 Then
                    SaveStatuses.Add("Supplier Data for '" + supplier.Name + "' saved successfully.")
                Else
                    SaveStatuses.Add("Supplier Data for '" + supplier.Name + "' Error: " + response1.ToString)
                End If
            Next

            '08/08/2016     Added saving composition.
            'First, clear the composition for this item.
            MySQL.insertUpdate("DELETE FROM whldata.sku_composition WHERE bundleSku='" + ShortSku + "';")
            'Then readd the new ones.
            If isBundle Then
                For Each comp As WhlSKU In Composition
                    MySQL.insertUpdate("INSERT INTO whldata.sku_composition (bundleSku, childSku) VALUES ('" + ShortSku + "','" + comp.SKU + "');")
                Next
            End If

            SaveStatuses.Add(RecordChangelog(LogUser, LogReason))
            Return SaveStatuses
        End If

    End Function

    Public Function SavePhotosExclusively() As String
        ''Aaaand finally, save the pictures.
        'Dim goods As Integer = 0
        Dim ResultString As String = SKU + " changes:" + vbNewLine

        For Each img As SKUImage In Images
            Dim updated As Boolean = False
            Dim savednew As Boolean = False

            Dim imgquery As String = ""
            'So we wanna know if our sku has an image with this name
            Dim imageCount As ArrayList = MySQL.SelectData("SELECT COUNT(*) FROM whldata.sku_images WHERE path='" + img.ImagePath.Replace("\", "\\").Replace("'", "\'") + "' AND sku='" + SKU + "' AND shortsku='" + ShortSku + "';")
            If Convert.ToInt32(imageCount(0)(0)) > 0 Then
                'IT DOES. Update it. We don't wanna duplicate it.
                imgquery = "UPDATE whldata.sku_images SET filename='" + img.ImageFileName.ToString.Replace("\", "\\").Replace("'", "\'") + "', path='" + img.ImagePath.Replace("\", "\\").Replace("'", "\'") + "', sku='" + SKU + "', shortsku='" + ShortSku + "', isPrimary='"+img.isPrimary.ToString()+"' WHERE path='" + img.ImagePath.Replace("\", "\\").Replace("'", "\'") + "' AND sku='" + SKU + "' AND shortsku='" + ShortSku + "';"
                updated = True
            Else
                'IT DOESN'T. insert it.
                imgquery = "INSERT INTO whldata.sku_images (filename,path, sku, shortsku, isPrimary) VALUES ('" + img.ImageFileName.ToString.Replace("\", "\\").Replace("'", "\'") + "','" + img.ImagePath.Replace("\", "\\").Replace("'", "\'") + "', '" + SKU + "','" + ShortSku + "','"+img.isPrimary.ToString()+"');"
                savednew = True
            End If
            Dim response1 As Object = MySQL.insertUpdate(imgquery)
            If response1.ToString.Length < 10 Then
                If updated Then
                    ResultString += "- Database update for " + img.ImagePath.Replace("T:\Photos\", "") + " succeeded." + vbNewLine
                ElseIf savednew Then
                    ResultString += "- Database addition for " + img.ImagePath.Replace("T:\Photos\", "") + " succeeded." + vbNewLine
                End If
            Else
                ResultString += "- Database change for " + img.ImagePath.Replace("T:\Photos\", "") + " failed." + vbNewLine
            End If
        Next

        Return ResultString

    End Function

    Private Function RemoveImageByPath(SearchPath As String) As Boolean
        Dim foundImage As  Boolean = False
        For Each image As SKUImage In Me.Images
            If image.ImagePath = SearchPath Then
                Me.Images.Remove(image)
                foundImage = True
                Exit For
            End If
        Next

        Return foundImage
    End Function

    Public Function RemovePhotos(PhotoToGo As SKUImage) As String
        ''Aaaand finally, save the pictures.
        Dim ResultString As String = ""

        'So we wanna know if our sku has an image with this name
        Dim imageCount As ArrayList = MySQL.SelectData("SELECT COUNT(*) FROM whldata.sku_images WHERE path='" + PhotoToGo.ImagePath.Replace("\", "\\").Replace("'", "\'") + "' AND sku='" + SKU + "' AND shortsku='" + ShortSku + "';")
        If Convert.ToInt32(imageCount(0)(0)) > 0 Then
            'IT DOES. Remove it.
            Dim removedFromSku As Boolean = RemoveImageByPath(PhotoToGo.ImagePath) 'do dat!
            If removedFromSku Then
                ResultString = "Image was removed from item data," 'HOORAY!
            Else
                ResultString = "Image was not removed from item data," 'FACK.
            End If

            Dim imgquery As String = "DELETE FROM whldata.sku_images WHERE path='" + PhotoToGo.ImagePath.Replace("\", "\\").Replace("'", "\'") + "' AND sku='" + SKU + "' AND shortsku='" + ShortSku + "';"
            Dim response1 As Object = MySQL.insertUpdate(imgquery)
            If response1.ToString.Length < 10 Then
                ResultString += " and database removal succeeded."
                'was it a total success?
                If Not removedFromSku Then 'Oh no!
                    ResultString = ResultString.Replace(" and ", " but ") 'English!
                    ResultString += " Please tell IT. It may not have existed in the item data at all, but we'd still like to know if it's happening." 'So the database had a record, but the sku did not...
                End If
            Else 'Ah, bugger all
                ResultString += " and database removal failed. Please tell IT." 'Wait, if it existed, how are we in this part of the loop? Clearly it's broken. Tell us!!
                If removedFromSku Then 'Ok, something worked.
                    ResultString = ResultString.Replace(" and ", " but ")
                End If
            End If
        Else 'Oh, it's not there?
            ResultString = "Image data is not on the database,"
            'So let's remove it from the sku data
            Dim removedFromSku As Boolean = RemoveImageByPath(PhotoToGo.ImagePath) 'do dat!
            If removedFromSku Then
                ResultString += " removed from item data." 'Hmm. Somehow it was added to the sku data but not the database.
            Else
                ResultString += " and doesn't seem to be in the item data." '...Did it exist at all?
            End If
        End If

        Return ResultString 'Get message.

    End Function

    '06/04/2016     Moved out of the save so I can use it more.
    Public Function RecordChangelog(User As Employee, reason As String)
        '23/05/16 - Added handling for cancelling authentication
        If Not User Is Nothing Then
            Dim ChangesQuery As String = ""
            ChangesQuery = "INSERT INTO whldata.sku_changelog (shortsku, payrollId, reason, datetimechanged) VALUES ('" + ShortSku + "','" + User.PayrollId.ToString + "','" + reason + "','" + Now.ToString("dd/MM/yyyy HH:mm:ss") + "');"
            Dim response3 As Object = MySQL.insertUpdate(ChangesQuery)
            If response3.ToString.Length < 10 Then
                Return "Changelog for '" + ShortSku + "' saved successfully."
            Else
                Return "Changelog for '" + ShortSku + "' Error: " + response3.ToString
            End If
        Else
            Return Nothing
        End If
    End Function

    Public Sub RecordLocationAudit(User as Employee, Location as Integer, AuditEvent as AuditEvents, Additional As Integer, FriendlyString As String, EventSource As String )
        Dim LocText as string = LocationIDConversion(Location)
        Dim CurrentTotal = Convert.ToInt32(MySQL.SelectDataDictionary($"SELECT Sum(StockLevel) as total from whldata.Shortsku_locations where Shortsku = '{ShortSku}'")(0)("total"))
        Dim query ="INSERT INTO whldata.locationaudit (Shortsku,locationid,DateOfEvent,Additional,AuditUserID,AuditEvent,LocationText,FriendlyString,EventSource,TotalAtTime) VALUES ('" + Sku + "','" + Location.ToString + "','" + Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + Additional.ToString + "','" + User.PayrollId.ToString() + "','" + Convert.ToInt32(AuditEvent).ToString()+ "','" + LocText +"','"+ FriendlyString + "','"+EventSource+$"','{CurrentTotal}');"
        MySQL.insertUpdate(query)

    End Sub
    Public Enum AuditEvents
        ShitsFuckedYo = 0
        NewLocation = 1
        AdjustedStock = 2
        RemovedLocation = 3
        AutoAdjustedStock = 4
        AutoRemovedStock = 5
        AutoAddedLocation = 6

        Adjust_Add = 11
        Adjust_Remove = 12
        Adjust_Set = 13

        Move_From = 14
        Move_To = 15

    End Enum
    Public Sub SaveSizeState()
        Dim ChangesQuery As String = ""
        ChangesQuery = "UPDATE whldata.whlnew SET OversizedItem='" + ExtendedProperties.Oversized.ToString + "' WHERE shortsku='" + ShortSku + "';"
        MySQL.insertUpdate(ChangesQuery)
    End Sub

    Public Sub New()

    End Sub

    Public Sub New(param As Object)
        If param.GetType = (New ArrayList).GetType Then
            'Its an arraylist, shock horror
            Newbie(param)
        ElseIf param.ToString.Length = 7 Then
            'Probably a sku
            ShortSku = param
            SKU = param + "xxxx"
            Dates.FirstRecorded = Now
        End If
    End Sub

    ''' <summary>
    ''' Returns the cheapest supplier object attached to this product.
    ''' </summary>
    ''' <returns></returns>
    Public Function CheapestSupplier() As SKUSupplier
        'Cool
        Dim cheapest As SKUSupplier = Suppliers(0)
        For Each supp As SKUSupplier In Suppliers
            If supp.Price < cheapest.Price Then
                cheapest = supp
            End If
        Next
        Return cheapest
    End Function

    Public Sub UpdateImages()
        Dim NewImageTable As List(Of Dictionary(Of String, Object)) = MySQL.SelectDataDictionary("SELECT * FROM whldata.sku_images WHERE SKU='" + SKU + "';")
        If NewImageTable.count > 0 Then
            Me.Images.Clear()
            For Each Img As Dictionary(Of String, Object) In NewImageTable
                Dim NewImg As New SKUImage
                NewImg.ImagePath = Img("path")
                NewImg.ImageFileName = Img("filename")
                NewImg.FullImagePath = Img("path")
                NewImg.isPrimary = Img("IsPrimary")
                Images.Add(NewImg)
            Next
        End If
    End Sub

    '03/02/2016     Generates the Linnshort title which we dont like using but do it anyway.
    Public Function GenerateLinnTitle() As String
        If ExtendedProperties.IsPair Then
            Return Me.PackSize.ToString + "P   " + Me.ShortSku.Substring(2) + "     " + Me.Title.Label + " " + Me.GetPrimarySupplier().ReOrderCode.ToUpper
        Else
            Return Me.PackSize.ToString + "    " + Me.ShortSku.Substring(2) + "     " + Me.Title.Label + " " + Me.GetPrimarySupplier().ReOrderCode.ToUpper
        End If

    End Function

    '01/02/2016     Created for use with the thingy.
    Public Sub UpdateStock()
        Try
            Dim LinnStock As ArrayList = MySQL.SelectData("SELECT SKU, Stock, StockMinimum FROM whldata.inventory WHERE SKU='" + SKU + "';")(0)
            If LinnStock.Count > 0 Then
                Try
                    If IsDBNull(LinnStock(1)) Then Stock.Level = 0 Else Stock.Level = LinnStock(1)
                Catch ex As Exception
                    Stock.Level = 0
                End Try
                Try
                    If IsDBNull(LinnStock(2)) Then Stock.Minimum = 0 Else Stock.Minimum = LinnStock(2)
                Catch ex As Exception
                    Stock.Minimum = 0
                End Try
                Stock.Dormant = 0
                If Stock.Minimum = -1 Then
                    Stock.Total = Stock.Dormant + Stock.Level
                Else
                    Stock.Total = Stock.Dormant + Stock.Level + Stock.Minimum
                End If
                If NewItem.Status = "Pending" Then
                    NewItem.Status = "Active"
                End If
            Else

            End If

        Catch exe As Exception
            ''01/02/2016     Do nothing...
        End Try
    End Sub

    ''' <summary>
    ''' Gets the stock level from the database or provided precomp and applied it to the sku.
    ''' </summary>
    ''' <param name="Precomp_Stock">A precomp of the Stock table.</param>
    Public Sub GetStock(Optional Precomp_Stock As ConcurrentBag(Of Dictionary(Of String,Object)) = nothing, Optional GiveUpAfterThis As Boolean = false)
        If Not isnothing(precomp_stock)
            dim Stockdata as Dictionary(Of String, object) = Precomp_Stock.First(function(x as Dictionary(Of String, object)) x("SKU") = sku)
            if Stockdata is nothing then
                throw new Exception()
            Else
                Stock.Level = Stockdata("stock")
                Stock.Minimum = Stockdata("stockminimum")
                Stock.Dormant = 0
                Stock.Total = stock.Level + Stock.Minimum
            End If
        else
            Dim LinnStock As ArrayList = MySQL.SelectData("SELECT SKU, Stock, StockMinimum FROM whldata.inventory WHERE SKU='" + SKU + "';")
            If LinnStock.Count > 0 Then
                Try
                    If IsDBNull(LinnStock(0)(1)) Then Stock.Level = 0 Else Stock.Level = LinnStock(0)(1)
                Catch ex As Exception
                    Stock.Level = 0
                End Try
                Try
                    If IsDBNull(LinnStock(0)(2)) Then Stock.Minimum = 0 Else Stock.Minimum = LinnStock(0)(2)
                Catch ex As Exception
                    Stock.Minimum = 0
                End Try
                Stock.Dormant = 0
                Stock.Total = Stock.Dormant + Stock.Level + Stock.Minimum 'Why the fuck does this include dormant? Come on resharper, get your shit together
            Else
                Dim rows As Integer = MySQL.SelectData("Select COUNT(*) FROM whldata.inventory")(0)(0)
                If rows = 0 Then
                    If GiveUpAfterThis Then
                        Thread.Sleep(2000) 'Sleep for 2s, should give the table time to refill up.
                        Throw New Exceptions.EmptyStockTableException
                    Else
                        GetStock(Precomp_Stock,true)
                    End If
                Else
                    If GiveUpAfterThis Then
                        Dim Line As New Exception("Item has no line in Inventory")
                        Line.Data.Add("sku",sku)
                        Line.Data.Add("tablerows",rows.ToString())
                        Throw Line
                    Else
                        Thread.Sleep(200) 'Sleep for 2s, should give the table time to refill up.
                        GetStock(Precomp_Stock,true)
                    End If
                End If
            End If
        End If

    End Sub

    '               Creates the record of data.
    ''' <summary>
    ''' Create a WHLSku from a simple arraylist from the whlnew table.
    ''' </summary>
    ''' <param name="Data">The arraylist of values from a row.</param>
    ''' <param name="ThrowRefreshExeptions">Whether or not to throw special exceptions, like if there is nothing in the stock table etc. Default is false.</param>
    Public Sub Newbie(Data As ArrayList, Optional ThrowRefreshExeptions As Boolean = False, Optional Precomp_Stock As ConcurrentBag(Of Dictionary(Of String,Object)) = nothing, Optional Precomp_Images As ConcurrentBag(Of SkuImage) = nothing, Optional Precomp_Suppliers As ConcurrentBag(Of SkUSupplier) = nothing, Optional Precomp_Locations As ConcurrentBag(Of SkuLocation) = nothing)
         T
        'SO MUCH LEEGACY CODE IT FUCKING HURTS

        'Initialate

        'Basic
        SKU = Data(0)
        ShortSku = Data(80)
        PackSize = Data(15)
        If IsDBNull(Data(123)) Then
            Pieces = 1
        Else
            Pieces = Convert.ToInt32(Data(123).ToString)
        End If

        'Misc
        DeliveryNote = Data(31)

        'SkuStock
       T
        'Try to get live stock first, then if that doesn't work, fall back as usual.
        Try

            GetStock(Precomp_stock)
        Catch e As Exceptions.EmptyStockTableException
            If ThrowRefreshExeptions Then
                Throw New Exception
            Else
                Try
                    GetStock()
                Catch e1 As Exceptions.EmptyStockTableException
                    Try
                        Threading.Thread.Sleep(50)
                        GetStock()
                    Catch e2 As Exceptions.EmptyStockTableException
                        Try
                            Threading.Thread.Sleep(50)
                            GetStock()
                        Catch e3 As Exceptions.EmptyStockTableException
                            Try
                                Threading.Thread.Sleep(50)
                                GetStock()
                            Catch e4 As Exceptions.EmptyStockTableException
                                'Ah yes. just leave the stock level empty. That's fucking helpfuil.
                            End Try
                        End Try
                    End Try
                End Try

            End If
        Catch exe As Exception
            'Fall back because either we hit an exception or we just don't have any.
            If IsDBNull(Data(1)) Then Stock.Level = 0 Else Stock.Level = Data(1)

            If IsDBNull(Data(2)) Then Stock.Minimum = 0 Else Stock.Minimum = Data(2)

            Stock.Dormant = 0

            If IsDBNull(Data(4)) Then Stock.Total = 0 Else Stock.Total = Data(4)

            '08/12/2015 - Rewote the stock pickup here because it was awful considering newer thigns will pull stock from Linnworks anyway.
        End Try

       T("GetStock")

        'SKUPrices
        Price.Gross = Data(7)

        Price.Net = Data(6)
        Price.Retail = Data(8)
        If Data(9) = "NSL" Then
            Price.Profit = 0
        Else
            Try
                Price.Profit = Data(9)
            Catch ex As Exception
                Price.Profit = 0
            End Try
        End If
        If Data(10) = "NSL" Then
            Price.Margin = 0
        Else
            Try
                Price.Margin = Data(10)
            Catch ex As Exception
                Price.Margin = 0
            End Try
        End If

        'SKUTitles
        Title.Invoice = Data(5)
        Title.Label = Data(98)
        Title.Linnworks = Data(99)
        If IsDBNull(Data(101)) Then
            Title.NewItem = ""
        Else
            Try
                Title.NewItem = Data(101)
            Catch ex As Exception
                Title.NewItem = ""
            End Try
        End If
        '14/01/2016     Added distinguish to teh save, and load functions.
        If IsDBNull(Data(79)) Then
            Title.Distinguish = ""
        Else
            Try
                Title.Distinguish = Data(79)
            Catch ex As Exception
                Title.Distinguish = ""
            End Try
        End If

       T

        'Images
        Images.Clear()
        If Not IsNothing(Precomp_Images) then
            dim results as List(Of SKUImage) = Precomp_Images.Where(function(Img As skuimage) Img.sku = SKU).ToList()
            if results.Count>0 then
                Images.AddRange(results)
            else
                ImageFallback(Data)
            End If
        Else
            Dim NewImageTable As List(Of Dictionary(Of String, Object)) = MySQL.SelectDataDictionary("SELECT * FROM whldata.sku_images WHERE SKU='" + SKU + "';")
            If NewImageTable.count > 0 Then
                Me.Images.Clear()
                For Each Img As Dictionary(Of String, Object) In NewImageTable
                    Dim NewImg As New SKUImage
                    NewImg.ImagePath = Img("path")
                    NewImg.ImageFileName = Img("filename")
                    NewImg.FullImagePath = Img("path")
                    NewImg.isPrimary = Img("IsPrimary")
                    Images.Add(NewImg)
                Next
            Else
                ImageFallback(Data)
            End If
        End If

        T("Images")

        'Suppliers
        Suppliers.Clear()

        If Not isnothing(Precomp_Suppliers) Then
            dim results as List(Of SkuSupplier) = Precomp_Suppliers.Where(function(Supp As SkuSupplier) Supp.ShortSku = Shortsku).ToList()
            if results.Count>0 then
                Suppliers.AddRange(results)
            else
                SupplierFallback(Data)
            End If
        Else
            Dim NewSupplierTable As Object = MySQL.SelectData("SELECT * FROM whldata.sku_supplierdata WHERE SKU='" + ShortSku + "' AND Invisible='False';")
            Dim NewArrSupp As ArrayList = NewSupplierTable
            If NewArrSupp.Count > 0 Then
                'We have records!
                For Each Supp As ArrayList In NewArrSupp
                    Dim NewSupp As New SKUSupplier

                    If Supp(8).ToString = "True" Then
                        NewSupp.Primary = True
                    Else
                        NewSupp.Primary = False
                    End If
                    If Supp(9).ToString = "True" Then
                        NewSupp.Discontinued = True
                    Else
                        NewSupp.Discontinued = False 'Why does the name disappear here?
                    End If
                    If Supp(10).ToString = "True" Then
                        NewSupp.OutOfStock = True
                    Else
                        NewSupp.OutOfStock = False
                    End If
                    'Moved because the name keeps getting dropped
                    NewSupp.Name = Supp(2).ToString.ToUpper
                    NewSupp.ReOrderCode = Supp(3).ToString
                    NewSupp.Barcode = Supp(4).ToString
                    NewSupp.CaseBarcode = Supp(5).ToString
                    NewSupp.LeadTime = Supp(12)
                    NewSupp.Price = Supp(7)
                    NewSupp.LastModified = Supp(11)
                    '25/01/2016     Changed the TableDataID code for the new Pkey.
                    NewSupp.TableDataId = ShortSku + "_" + NewSupp.Name.ToUpper
                    NewSupp.CaseBarcodeInner = Supp(13)
                    'NewSupp.BoxCode = Supp(14)     Looks obselete to me.
                    Suppliers.Add(NewSupp)
                Next
            Else
                SupplierFallback(Data)
            End If
        End If
        T("Suppliers")
        'Locations

        Locations.clear()
        If Not isnothing(Precomp_Locations) Then
            dim results as List(Of SkuLocation) = Precomp_Locations.Where(function(Loc As SkuLocation) Loc.Sku = Sku).ToList()
            if results.Count>0 then
                Locations.AddRange(results)
            else
                LocationFallback(Data)
            End If
        Else
            Try
                RefreshLocations()
            Catch ex As Exception
                LocationFallback(Data)
            End Try
        End If

       T("Locations")
        'Extended Properties
        If Not IsDBNull(Data(85)) Then
            ExtendedProperties.GS1Barcode = Data(85)
        End If

        If Data(67).ToString = "0" Then
            ExtendedProperties.Screws = 0
            ExtendedProperties.HasScrews = False
        ElseIf Data(67).ToString.Length > 0 Then
            ExtendedProperties.Screws = Data(67)
            ExtendedProperties.HasScrews = True
        Else
            ExtendedProperties.Screws = 0
            ExtendedProperties.HasScrews = False
        End If
        Try
            ExtendedProperties.Parts = Data(66)
        Catch ex As Exception
            ExtendedProperties.Parts = 0
        End Try
        '31/03/2016     Added the SplitBundle flag.
        If IsDBNull(Data(126)) Then
            ExtendedProperties.SplitBundle = False
        Else
            ExtendedProperties.SplitBundle = Data(126)
        End If

       T
        SalesData.SetSku(SKU)
        SalesData.DownloadData()
       T("Sales Data")
        'Moved refresh search keyeords to after composition is made.

        '29/01/2016     Add anything in the budlepacks thing.
        If Data(119).ToString.Length > 1 Then
            Dim bundles As String() = Data(119).ToString.Split(" ")
            For Each seacrch As String In bundles
                SearchKeywords.Add(seacrch)
            Next
        End If

        'Costs, which for some reason weren't there before...
        Try
            If (IsDBNull(Data(43)) Or Data(43) = "NLS") Then Costs.Packing = 0 Else Costs.Packing = Data(43)
        Catch ex As Exception
            Costs.Packing = 0
        End Try
        Try
            If (IsDBNull(Data(44)) Or Data(44) = "NLS") Then Costs.Postage = 0 Else Costs.Postage = Data(44)
        Catch ex As Exception
            Costs.Postage = 0
        End Try
        Try
            If (IsDBNull(Data(45)) Or Data(45) = "NLS") Then Costs.Envelope = 0 Else Costs.Envelope = Data(45)
        Catch ex As Exception
            Costs.Envelope = 0
        End Try
        Try
            If (IsDBNull(Data(46)) Or Data(46) = "NLS") Then Costs.Fees = 0 Else Costs.Fees = Data(46)
        Catch ex As Exception
            Costs.Fees = 0
        End Try
        Try
            If (IsDBNull(Data(47)) Or Data(47) = "NLS") Then Costs.Labour = 0 Else Costs.Labour = Data(47)
        Catch ex As Exception
            Costs.Labour = 0
        End Try
        Try
            If (IsDBNull(Data(48)) Or Data(48) = "NLS") Then Costs.VAT = 0 Else Costs.VAT = Data(48)
        Catch ex As Exception
            Costs.VAT = 0
        End Try
        Try
            If (IsDBNull(Data(49)) Or Data(49) = "NLS") Then Costs.Total = 0 Else Costs.Total = Data(49)
        Catch ex As Exception
            Costs.Total = 0
        End Try

        'Post-Save additions

        Try
            If IsDBNull(Data(13)) Then Profile.Weight = 0 Else Profile.Weight = Data(13)
        Catch ex As Exception
            Profile.Weight = 0
        End Try
        Try
            If IsDBNull(Data(12)) Then ExtendedProperties.Envelope = "" Else ExtendedProperties.Envelope = Data(12)
        Catch ex As Exception
            ExtendedProperties.Envelope = ""
        End Try
        Try
            If IsDBNull(Data(17)) Then ExtendedProperties.LabourCode = "" Else ExtendedProperties.LabourCode = Data(17)
        Catch ex As Exception
            ExtendedProperties.LabourCode = ""
        End Try
        Try
            If IsDBNull(Data(77)) Then Category = "" Else Category = Data(77)
        Catch ex As Exception
            Category = ""
        End Try
        'NEWITEM STUFF
        Try
            If IsDBNull(Data(100)) Then NewItem.Brand = "" Else NewItem.Brand = Data(100)
        Catch ex As Exception
            NewItem.Brand = ""
        End Try
        Try
            If IsDBNull(Data(101)) Then NewItem.Description = "" Else NewItem.Description = Data(101)
        Catch ex As Exception
            NewItem.Description = ""
        End Try
        Try
            If IsDBNull(Data(104)) Then NewItem.Finish = "" Else NewItem.Finish = Data(104)
        Catch ex As Exception
            NewItem.Finish = ""
        End Try
        Try
            If IsDBNull(Data(105)) Then NewItem.Size = "" Else NewItem.Size = Data(105)
        Catch ex As Exception
            NewItem.Size = ""
        End Try
        Try
            If IsDBNull(Data(106)) Then NewItem.Note = "" Else NewItem.Note = Data(106)
        Catch ex As Exception
            NewItem.Note = ""
        End Try
        Try
            If IsDBNull(Data(109)) Then NewItem.Box = "0" Else NewItem.Box = Data(109)
        Catch ex As Exception
            NewItem.Box = "0"
        End Try
        Try
            If IsDBNull(Data(110)) Then NewItem.Status = "" Else NewItem.Status = Data(110)
        Catch ex As Exception
            NewItem.Status = ""
        End Try
        Try
            If Data(118) = "True" Or Data(118) = "P" Then
                ExtendedProperties.IsPair = True
            Else
                ExtendedProperties.IsPair = False
            End If
        Catch ex As Exception
            ExtendedProperties.IsPair = False
        End Try
        '18/01/2016     Modified the courier detection because it was wrong. Checked for "True", but it's actually Y or y in older formats.
        Try
            If (Data(18) = "True") Or (Data(18).ToString.ToLower = "y") Then ExtendedProperties.NeedsCourier = True Else ExtendedProperties.NeedsCourier = False
        Catch ex As Exception
            ExtendedProperties.NeedsCourier = False
        End Try
        '19/11/2016     Now gets Oversized settings
        Try
            If (Data(130) = "True") Or (Data(130).ToString.ToLower = "y") Then ExtendedProperties.Oversized = True Else ExtendedProperties.Oversized = False
        Catch ex As Exception
            ExtendedProperties.Oversized = False
        End Try
        '21/12/2015     "Added" the "new" initial stock field. Lives in the col38 of the main table, and in NewItem in classes.
        Try
            If IsDBNull(Data(38)) Then NewItem.InitStock = 0 Else Int32.TryParse(Data(38), NewItem.InitStock)
        Catch ex As Exception
            NewItem.InitStock = 0
        End Try

        #region "Dates"
        'The next 2 block are made 08/12/2015 - Events and Dates.
        'Dates

        '8/11/2016 - Removed dates because we never really used them and this shit is slooooow.
        Dates.FirstRecorded = Date.MinValue
        Dates.FirstPriced = Date.MinValue
        Dates.FirstPhoto = Date.MinValue
        Dates.AddedToLinnworks = Date.MinValue
        Dates.FirstListed = Date.MinValue
        'Dim DatesResponse As ArrayList = MySQL.SelectData("SELECT * FROM whldata.sku_dates WHERE ShortSku='" + ShortSku + "' LIMIT 1;")
        'If DatesResponse.Count = 1 Then
        '    Try
        '        Dates.FirstRecorded = Date.Parse(DatesResponse(0)(1).ToString)
        '        Dates.FirstPriced = Date.Parse(DatesResponse(0)(2).ToString)
        '        Dates.FirstPhoto = Date.Parse(DatesResponse(0)(3).ToString)
        '        Dates.AddedToLinnworks = Date.Parse(DatesResponse(0)(4).ToString)
        '        Dates.FirstListed = Date.Parse(DatesResponse(0)(5).ToString)
        '    Catch ex As Exception
        '        'We dont really use these anyway so it's not a problem if it doesnt work.
        '        Dates.FirstRecorded = Date.MinValue
        '        Dates.FirstPriced = Date.MinValue
        '        Dates.FirstPhoto = Date.MinValue
        '        Dates.AddedToLinnworks = Date.MinValue
        '        Dates.FirstListed = Date.MinValue
        '    End Try

        'Else
        '    'Set empty date values.
        '    Try
        '        If Data(36) = "" Then
        '            Dates.FirstRecorded = Date.MinValue
        '        Else
        '            Try
        '                Dates.FirstRecorded = Date.Parse(Data(36))
        '            Catch ex As Exception
        '                Dates.FirstRecorded = Date.MinValue
        '            End Try

        '        End If
        '    Catch ex As Exception

        '    End Try

        '    Dates.FirstPriced = Date.MinValue
        '    Dates.FirstPhoto = Date.MinValue
        '    Dates.AddedToLinnworks = Date.MinValue
        '    Dates.FirstListed = Date.MinValue
        'End If
        #End Region

        '11/07/2016     Moved the changelog into it's own little function. Then we can avoid downloading changelog data where we don't need it.

        '15/01/2016     Adding NewItem Fields (InitStock, InitLevel, InitMinimum, ListPriority, Islisted) and Inner field in ExtendedProperties.
        '            ...Apparently InitStock has already been added on 21/12/2015. Adding the others insread.
        Try
            If (IsDBNull(Data(39)) Or Data(39) = "") Then NewItem.InitLevel = 0 Else NewItem.InitLevel = Convert.ToInt32(Data(39))
        Catch ex As Exception
            NewItem.InitLevel = 0
        End Try
        Try
            If IsDBNull(Data(120)) Then NewItem.InitMinimum = 0 Else NewItem.InitMinimum = Convert.ToInt32(Data(120))
        Catch ex As Exception
            NewItem.InitMinimum = 0
        End Try

        Try
            If IsDBNull(Data(121)) Then NewItem.ListPriority = 0 Else NewItem.ListPriority = Convert.ToInt32(Data(121))
        Catch ex As Exception
            NewItem.ListPriority = 0
        End Try
        '26/01/2016     Minor change to make listing enabled by default for when the database doesn't do that itself.
        Try
            If IsDBNull(Data(122)) Then
                NewItem.IsListed = True
            ElseIf Data(60).ToString = "NSL" Then
                NewItem.IsListed = False
            Else
                NewItem.IsListed = Convert.ToBoolean(Data(122))
            End If

        Catch ex As Exception
            NewItem.IsListed = True
        End Try

        If Data(129).ToString = "True" Then
            NewItem.HasBeenListed = True
        Else
            NewItem.HasBeenListed = False
        End If

       T
        '18/01/2016     (Possibly temporary) Added fee recalc inline so it's fresh. See how badly it impacts loading speed. Yay timers.
        '19/01/2016     Results on times with this added:
        '               Time With Recalc on Dev PC:     4min 55.26sec   (295 sec)
        '               Time Without recalc on Dev PC:  2min 24.44sec   (144 sec)
        '               Recalc time difference: 105% slower.
        '               Ouch.
        '20/01/2016     Changed RecalculateCosts to RecalculatePrices so we get updated profit/margin/whatever figures.
        Try
            RecalculatePrices(Price.Retail)
        Catch ex As Exception
            Console.WriteLine(SKU + ": " + ex.Message)
        End Try
       T("Recalcs")
        '08/02/2016     Added loading Inners, because apparently we dont do that.
        Try
            If IsDBNull(Data(117)) Then
                ExtendedProperties.Inner = ""
            Else
                ExtendedProperties.Inner = Data(117).ToString
            End If
        Catch ex As Exception

        End Try
        '17/02/2016     Add entires tp the "Barcodes" collections. Works like a search but for picking and packing.
        If IsNothing(Barcodes) Then
            MsgBox("You need to reset your cache.")
        Else
            Barcodes.Clear()
            Barcodes.Add(SKU)
            Barcodes.Add(ExtendedProperties.GS1Barcode)
            If PackSize = 1 Then
                For Each supplier As SKUSupplier In Suppliers
                    Barcodes.Add(supplier.Barcode)
                Next
            End If
        End If

       T
        '18/02/2016     Add prepack info the SKUs which has been around for SO LONG and it just hasnt been added .
        Dim PrepackResponse As ArrayList = MySQL.SelectData("SELECT * FROM whldata.prepacklist WHERE Sku='" + SKU + "';")
        If PrepackResponse.Count > 0 Then
            'Woo
            PrepackInfo.Bag = PrepackResponse(0)(1)
            PrepackInfo.Notes = PrepackResponse(0)(2)
        End If
       T("Prepack")
        '21/03/2016     Added the Pack and Pick notes.
        PackNote = Data(125).ToString
        PickNote = Data(124).ToString
        KnowledgeNote = Data(127).ToString
        'SaleTitle = Data(128).ToString
        'SaleDesc = Data(129).ToString

        UpdateMinimalMain()
       'Minimal")
        '16/05/2016     Adds the downlaoding of listings on the end.
        GetListings()
        T("Minimnal and Listings")
        '08/08/2016     Added native bundle support and the Bundle Wizard in WhlNewProducts. Here's the parts which deal with it in Newbie.
        isBundle = Boolean.Parse(Data(128).ToString)
        'Check if it is a sbundle, then downloads the compositions if it is.

        Composition = New List(Of WhlSKU)
        If isBundle Then
            'Get the list of Composition items.
            Dim NewLocs As New List(Of SKULocation)
            Dim Comps As ArrayList = MySQL.SelectData("SELECT * FROM whldata.sku_composition WHERE bundleSku='" + ShortSku + "';")
            For Each item As ArrayList In Comps
                Dim newsku As New WhlSKU(MySQL.SelectData("SELECT * FROM whldata.whlnew WHERE sku='" + item(2).ToString + "';")(0))
                NewLocs.AddRange(newsku.Locations)
                Composition.Add(newsku)
            Next
            If ExtendedProperties.SplitBundle
                If NewLocs.Count > 0 Then
                    Locations.Clear
                    Locations.AddRange(NewLocs)
                End If
            End If

        End If
        RefreshSearchKeywords()

        'Update the title with the new location if we can.
        Try
            If Title.Invoice.Length > 6 Then
                UpdateTitleWithCorrectLocation()
            End If 'We don't want to do this if we don't ahve a title,
        Catch ex As Exception
            'Ah well.
        End Try

        T("======================================================================================================================================================================================= DONE")
    End Sub

    Public sub UpdateTitleWithCorrectLocation
        dim ActualTitle as string = ""
        if Title.Invoice.Contains("  -  ") then
            ActualTitle = Title.Invoice.Substring(Title.Invoice.IndexOf("  -  ")+5).Trim(" ")
        else
            ActualTitle = Title.Invoice.Trim(" ")
        End If
        'Now we've trimmed it down we can append the correct locaitn.
        Dim newtitle = GetLocation(SKULocation.SKULocationType.Pickable).LocationText + "    -    " + ActualTitle
        Title.Invoice = newtitle
    End sub

#Region "Fallback Fucntions"
    Private Sub ImageFallback(Data As ArrayList)
        Console.WriteLine(SKU + ": Image Fallback")
                If Data(40).ToString.Length > 0 Then
                    Dim NewImg As New SKUImage
                    Dim IFN As String() = Data(40).ToString.Split("\")
                    NewImg.ImageFileName = IFN(IFN.Count - 1).Split(".")(0)
                    NewImg.ImagePath = Data(40)
                    Images.Add(NewImg)
                End If

                If Data(41).ToString.Length > 0 Then
                    Dim NewImg As New SKUImage
                    Dim IFN As String() = Data(41).ToString.Split("\")
                    NewImg.ImageFileName = IFN(IFN.Count - 1).Split(".")(0)
                    NewImg.ImagePath = Data(41)
                    Images.Add(NewImg)
                End If

                If Data(42).ToString.Length > 0 Then
                    Dim NewImg As New SKUImage
                    Dim IFN As String() = Data(42).ToString.Split("\")
                    NewImg.ImageFileName = IFN(IFN.Count - 1).Split(".")(0)
                    NewImg.ImagePath = Data(42)
                    Images.Add(NewImg)
                End If
    End Sub
    Private Sub SupplierFallback(Data As arraylist)
        Console.WriteLine(SKU + ": Supplier Fallback")
            'Fallback for the olden method.
            If Data(19).ToString.Length > 0 Then
                Dim NewSupp As New SKUSupplier
                NewSupp.Name = Data(19)
                NewSupp.ReOrderCode = Data(20)
                NewSupp.Barcode = Data(21)
                NewSupp.CaseBarcode = Data(22)
                NewSupp.LeadTime = 4
                NewSupp.Primary = True
                NewSupp.Price = Price.Net / PackSize
                NewSupp.LastModified = "Old Type- Not Found"
                NewSupp.CaseBarcodeInner = ""
                NewSupp.BoxCode = ""
                '25/01/2016     Added the TableDataID code for the new Pkey.
                NewSupp.TableDataId = ShortSku + "_" + NewSupp.Name
                Suppliers.Add(NewSupp)
            End If
            If Data(23).ToString.Length > 0 Then
                Dim NewSupp As New SKUSupplier
                NewSupp.Name = Data(23)
                NewSupp.ReOrderCode = Data(24)
                NewSupp.Barcode = Data(25)
                NewSupp.CaseBarcode = Data(26)
                NewSupp.LeadTime = Data(58)
                NewSupp.Price = Price.Net / PackSize
                NewSupp.LastModified = "Old Type- Not Found"
                NewSupp.CaseBarcodeInner = ""
                NewSupp.BoxCode = ""
                '25/01/2016     Added the TableDataID code for the new Pkey.
                NewSupp.TableDataId = ShortSku + "_" + NewSupp.Name
                Suppliers.Add(NewSupp)
            End If
            If Data(27).ToString.Length > 0 Then
                Dim NewSupp As New SKUSupplier
                NewSupp.Name = Data(27)
                NewSupp.ReOrderCode = Data(28)
                If Not IsDBNull(Data(29)) Then
                    NewSupp.Barcode = Data(29)
                Else
                    NewSupp.Barcode = ""
                End If
                If Not IsDBNull(Data(30)) Then
                    NewSupp.CaseBarcode = Data(30)
                Else
                    NewSupp.CaseBarcode = ""
                End If
                NewSupp.LeadTime = Data(58)
                NewSupp.Price = Price.Net / PackSize
                NewSupp.LastModified = "Old Type- Not Found"
                NewSupp.CaseBarcodeInner = ""
                NewSupp.BoxCode = ""
                '25/01/2016     Added the TableDataID code for the new Pkey.
                NewSupp.TableDataId = ShortSku + "_" + NewSupp.Name
                Suppliers.Add(NewSupp)
            End If
    End Sub
    Private Sub LocationFallback(Data As Arraylist)
        Console.WriteLine(SKU + ": Location Fallback")
            'Fallback for the olden method.

            '24/10/16 - Still need to lookup the location to find it's type.
            If Data(51).ToString.Length > 0 Then
                'Basic Location.
                Dim NewLoc As New SKULocation
                NewLoc.LocationTableID = SKU + "_" + ((New Random).Next(Int32.MaxValue)).ToString
                NewLoc.LocalLocationName = Data(51)
                Dim Lookup As List(Of Dictionary(Of String,Object)) = MySQL.SelectDataDictionary("SELECT locID, locText, locWarehouse, locType, RouteIndex FROM whldata.locationreference JOIN whldata.location_routing ON location_routing.RouteID = locationreference.RouteID WHERE locText LIKE '" + Data(51).ToString + "'")
                If Lookup.Count > 0 Then
                    NewLoc.LocationID = Lookup(0)("locID")
                    NewLoc.LocationText = Lookup(0)("locText")
                    NewLoc.LocalLocationName = Lookup(0)("locText")
                    NewLoc.WarehouseID = Lookup(0)("locWarehouse")
                    NewLoc.LocationType = Convert.ToInt32(Lookup(0)("locType"))
                    NewLoc.RouteIndex = Lookup(0)("RouteIndex")
                Else
                    NewLoc.LocationType = SKULocation.SKULocationType.Pickable
                    NewLoc.LocationText = NewLoc.LocalLocationName
                End If
                If Locations.Count > 0 Then
                    Locations.Clear()
                End If
                Locations.Add(NewLoc)
            End If
    End Sub
#End Region
    Public Sub RefreshLocations()
        'Locations

        Dim NewLocs As List(Of Dictionary(Of String,Object)) = MySQL.SelectDataDictionary("Select id, StockLevel, locText, locID, locType, locWarehouse, Shortsku, RouteIndex from whldata.Shortsku_locations JOIN whldata.locationreference ON Shortsku_locations.LocationId = locationreference.locID JOIN whldata.location_routing ON location_routing.RouteID = locationreference.RouteID WHERE ShortSku='" + ShortSku + "';")

        Dim Temploc As New List(Of SKULocation)
        If NewLocs.Count > 0 Then
            'We have records!

            For Each line As Dictionary(Of String,Object) In NewLocs
                Dim NewLoc As New SKULocation
                NewLoc.LocationTableID = line("id")
                NewLoc.LocalLocationName = line("locText")
                NewLoc.Additional = line("StockLevel").ToString
                NewLoc.LocationID = line("locID")
                NewLoc.LocationText = line("locText")
                NewLoc.WarehouseID = line("locWarehouse")
                NewLoc.LocationType = Convert.ToInt32(line("locType"))
                NewLoc.RouteIndex = line("RouteIndex")
                Temploc.Add(NewLoc)
            Next
            Dim TotalStock = TempLoc.Where(function(loc) not(loc.LocationType = SKULocation.SKULocationType.Pickable)).Sum(Function(x) Convert.ToInt32(x.Additional))
            for each res in Temploc
                if (res.LocationType = SKULocation.SKULocationType.Pickable)
                    res.Additional = Stock.Total - TotalStock
                End If
            Next
        Else
            Console.WriteLine("Failed to load locations. Must have a fallback location. SKU: " + SKU)
            Throw New Exception()
        End If
        Locations.Clear()
        Locations.AddRange(Temploc)

    End Sub
    Public Async Function RefreshLocationsAsync() As Task
        'Locations

        Dim NewLocs As List(Of Dictionary(Of String,Object)) = Await MySQL.SelectDataDictionaryAsync("Select id, StockLevel, locText, locID, locType, locWarehouse, Shortsku, RouteIndex from Shortsku_locations JOIN whldata.locationreference ON Shortsku_locations.LocationId = locationreference.locID JOIN whldata.location_routing ON location_routing.RouteID = locationreference.RouteID WHERE ShortSku='" + ShortSku + "';")

        Dim Temploc As New List(Of SKULocation)
        If NewLocs.Count > 0 Then
            'We have records!

            For Each line As Dictionary(Of String,Object) In NewLocs
                Dim NewLoc As New SKULocation
                NewLoc.LocationTableID = line("id")
                NewLoc.LocalLocationName = line("locText")
                NewLoc.Additional = line("additionalinfo").ToString
                NewLoc.LocationID = line("locID")
                NewLoc.LocationText = line("locText")
                NewLoc.WarehouseID = line("locWarehouse")
                NewLoc.LocationType = Convert.ToInt32(line("locType"))
                NewLoc.RouteIndex = line("RouteIndex")
                Temploc.Add(NewLoc)
            Next

        Else
            Console.WriteLine("Failed to load locations. Must have a fallback location. SKU: " + SKU)
            Throw New Exception()
        End If
        Locations.Clear()
        Locations.AddRange(Temploc)

    End Function
    <DebuggerStepThrough()>
    Friend Function LocationIDConversion(Location As Integer)
        Try
            Dim LocationString As String = ""
            Dim LocObject As ArrayList

            LocObject = MySQL.SelectData("SELECT locText FROM whldata.locationreference WHERE locID=" + Location.ToString + " LIMIT 1;")
            For Each LocList As ArrayList In LocObject
                LocationString = LocList(0).ToString
            Next
            Return LocationString
        Catch Ex As Exception
            Return "Could not find shelf name"
        End Try

    End Function

    Public Sub AddNewLocation(LocationID As Integer, User As Employee, Optional AdditionalInfo As String = "", Optional RefreshTheLocation As Boolean = False)
        If User.Permissions.SaveChangesToSKU Then
            Dim ShelfName As String = LocationIDConversion(LocationID)
            AddNewLocationFriendly(LocationID, AdditionalInfo, RefreshTheLocation)
            RecordChangelog(User, "Added a new location - " + LocationID.ToString + " " + ShelfName)
        ElseIf User.Permissions.ModLocation Then 'Location Modifier in Inventory Control
            Dim ShelfName As String = LocationIDConversion(LocationID)
            AddNewLocationFriendly(LocationID, AdditionalInfo, RefreshTheLocation)
            RecordChangelog(User, "Added a new location - " + LocationID.ToString + " " + ShelfName)
        Else
            Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to add a location to an item.")
        End If
    End Sub

    '24/10/2016
    Friend Sub AddNewLocationFriendly(LocationID As Integer, Optional AdditionalInfo As String = "", Optional RefreshTheLocation As Boolean = False)
        Dim mysqllookup As ArrayList = MySQL.SelectData("SELECT * FROM whldata.locationreference WHERE locID=" + LocationID.ToString + " LIMIT 1;")
        If mysqllookup.Count = 0 Then
            Throw New Exceptions.LocationNullReferenceException("No location with the ID '" + LocationID.ToString + "' could be found in the database.")
        Else
            Dim NewLoc As New SKULocation
            NewLoc.LocationTableID = SKU + "_" + ((New Random).Next(Int32.MaxValue)).ToString
            NewLoc.LocalLocationName = mysqllookup(0)(1)
            NewLoc.LocationID = mysqllookup(0)(0)
            NewLoc.LocationText = mysqllookup(0)(1)
            NewLoc.WarehouseID = mysqllookup(0)(2)
            NewLoc.LocationType = Convert.ToInt32(mysqllookup(0)(3))
            NewLoc.Additional = AdditionalInfo
            Locations.Add(NewLoc)

            SaveLocationsLocally(Nothing, RefreshTheLocation)
        End If
    End Sub

    Public Sub RemoveLocation(LocationID As Integer, User As Employee)
        Dim mysqllookup As ArrayList = MySQL.SelectData("SELECT * FROM whldata.locationreference WHERE locID=" + LocationID.ToString + " LIMIT 1;")
        If mysqllookup.Count = 0 Then Throw New Exceptions.LocationNullReferenceException("No location with the ID '" + LocationID.ToString + "' could be found in the database.")

        If User.Permissions.SaveChangesToSKU Then
            Dim ShelfName As String = LocationIDConversion(LocationID)
            RemoveLocationFriendly(LocationID)
            RecordChangelog(User, "Removed an old location - " + LocationID.ToString + " " + ShelfName)
        ElseIf User.Permissions.ModLocation Then 'Location Modifier in Inventory Control
            Dim ShelfName As String = LocationIDConversion(LocationID)
            RemoveLocationFriendly(LocationID)
            RecordChangelog(User, "Removed an old location - " + LocationID.ToString + " " + ShelfName)
        Else
            Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
        End If
    End Sub

    Public Sub RemoveAllLocations(user As Employee)
        If user.Permissions.SaveChangesToSKU Then
            RemoveAllLocations()
            RecordChangelog(user, "Removed all existing locations")
        Else
            Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
        End If
    End Sub

    Friend Sub RemoveAllLocations()
        MySQL.insertUpdate("DELETE FROM whldata.Shortsku_locations WHERE Shortsku='" + ShortSku + "';")
        Locations.Clear()
    End Sub

    Friend Sub RemoveLocationFriendly(LocationID As Integer)

        Dim dellocation As SKULocation = Nothing
        For Each location As SKULocation In Locations
            If location.LocationID = LocationID Then
                dellocation = location
                Exit For
            End If
        Next
        If Not IsNothing(dellocation) Then
            'Remove from the database
            MySQL.insertUpdate("DELETE FROM whldata.Shortsku_locations WHERE Shortsku_locations.id='" + dellocation.LocationTableID + "';")
            Locations.Remove(dellocation)
        Else
            Throw New Exceptions.LocationNullReferenceException("The location with reference ID '" + LocationID.ToString + "' is not attached to the item to be removed.")
        End If

    End Sub
    #Region "Location with Audit"
    ''' <summary>
    ''' Adds an item to a new location
    ''' </summary>
    ''' <param name="LocationID"></param>
    ''' <param name="User"></param>
    ''' <param name="Amount"></param>
    Public Sub AddLocationWithAudit(LocationID as Integer, User as Employee, Amount As Integer, EventSource As String,Optional FromAltMethod As Boolean = False,Optional FromDelivery as Boolean = false)
        if MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations WHERE LocationId ='"+LocationID.ToString()+"' AND ShortSku='"+ShortSku+"';").Count = 0
            If Amount < 0  Then Throw New NegativeStockException("Stock cannot be negative")

            If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU
                ' Dim Results = SelectDataDictionary("SELECT * from whldata.locationreference WHERE locID='" + LocationID.ToString() + "' LIMIT 1;")(0) 'This was never even used.
                Dim ShelfName As String = LocationIDConversion(LocationID)
                Dim id as String = Shortsku.ToString +"_" +LocationID.ToString
                Dim query as string = "INSERT INTO whldata.Shortsku_locations (id,shelfName,Shortsku,StockLevel,LocationId) VALUES ('" + id + "','"  + ShelfName + "','" + Shortsku +"','" + Amount.ToString() +"','" + LocationID.ToString + "');"
                Dim InsertResult = MySQL.insertUpdate(query)
                if InsertResult=1 Then
                    RefreshLocations()
                    if(FromAltMethod)
                        RecordLocationAudit(User,LocationID,AuditEvents.Adjust_Add, Amount,"Added new Location - " + ShelfName + " With Stock of: " + Amount.ToString(), EventSource)
                    else
                        RecordLocationAudit(User,LocationID,AuditEvents.NewLocation, Amount,"Added new Location - " + ShelfName + " With Stock of: " + Amount.ToString(), EventSource)
                    End If
                Else
                    Dim Excep = New Exception("AddLocationWithAudit returned """+ InsertResult.Tostring+ """ instead of 1 from the insert call.")
                    Excep.Data.Add("LocationID",LocationID)
                    Excep.Data.Add("User",User.PayrollId.ToString)
                    Excep.Data.Add("Amount",Amount.ToString)
                    Excep.Data.Add("FromAltMethod",FromAltMethod.Tostring)
                    Excep.Data.Add("FromDelivery",FromDelivery.ToString())
                    Reporting.ReportException(Excep)
                End If

            else
                Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
            End If
        Else
            if(FromDelivery)
                throw new Exception("This location is already attached to this item")
                else
                    if Not(amount = 0)
                        AdjustStockWithAudit(LocationID,User,Amount, EventSource)
                    End if
            End If

        End If
    End Sub
    ''' <summary>
    ''' Removes Stock from a location
    ''' </summary>
    ''' <param name="LocationID"></param>
    ''' <param name="User"></param>
    ''' <param name="Amount"></param>
    Public Sub RemoveLocationWithAudit(LocationID as Integer, User as Employee, EventSource As String, Optional Amount As Integer = 0, Optional FromWHApp as Boolean = false)
        if MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations WHERE LocationId ='"+LocationID.ToString()+"' AND ShortSku='"+ShortSku+"';").Count > 0
            If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU

                MySQL.insertUpdate("DELETE FROM whldata.Shortsku_locations WHERE Shortsku='" + ShortSku +"' AND LocationID = '" + LocationID.ToString +"';")
                If (Amount = 0) then
                    If(FromWHApp)
                        RecordLocationAudit(User,LocationID,AuditEvents.AutoRemovedStock,Amount,"Auto Removed Location - " + LocationIDConversion(LocationID) + " From Sku "+ SKU + " - All Stock", EventSource)
                    Else
                        RecordLocationAudit(User,LocationID,AuditEvents.RemovedLocation,Amount,"Removed Location - " + LocationIDConversion(LocationID) + " From Sku "+ SKU + " - All Stock", EventSource)
                    End If

                Else
                    If FromWHApp
                        RecordLocationAudit(User,LocationID,AuditEvents.AutoRemovedStock,Amount,"Auto Removed Location - " + LocationIDConversion(LocationID) + " From Sku With Stock of: " + Amount, EventSource)
                    Else
                        RecordLocationAudit(User,LocationID,AuditEvents.RemovedLocation,Amount,"Removed Location - " + LocationIDConversion(LocationID) + " From Sku With Stock of: " + Amount, EventSource)

                    End If

                End If
                RefreshLocations()
            else
                Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
            End If

            Else

            Throw New Exceptions.LocationNullReferenceException("The location with reference ID '" + LocationID.ToString + "' is not attached to the item to be removed.")
        end if

    End Sub
    ''' <summary>
    ''' Adjusts the stock in a location by a specified amount,Used for Picking
    ''' </summary>
    ''' <param name="LocationID">The location</param>
    ''' <param name="User">User who made the change</param>
    ''' <param name="Amount">Amount the stock has changed by</param>
    ''' <param name="FromWHApp">Optional Param for Warehouse application use</param>
    Public Sub AdjustStockWithAudit(LocationID As Integer, User As Employee, Amount As Integer, EventSource As String, Optional FromWHApp As Boolean = False)

        Dim AdjustType As AuditEvents

        If Amount < 0 Then
            If(FromWHApp)
                AdjustType = AuditEvents.AutoAdjustedStock
            Else
                AdjustType = AuditEvents.Adjust_Remove
            End If
        Else
        If (FromWHApp)
            AdjustType = AuditEvents.AutoAdjustedStock
        Else
            AdjustType = AuditEvents.Adjust_Add
        End If

        End If
        Dim result = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations a left join whldata.locationreference b on a.LocationId = b.locID WHERE LocationId ='" + LocationID.ToString() + "' AND Shortsku='" + ShortSku + "' LIMIT 1;")
        If result.Count > 0 Then

            If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU Then
                Dim NewStock = Convert.ToInt32(result(0)("StockLevel")) + Amount
                If NewStock > 0 Then
                    MySQL.insertUpdate("UPDATE whldata.Shortsku_locations SET StockLevel='" + NewStock.ToString() + "'WHERE LocationId ='" + LocationID.ToString() + "' AND Shortsku='" + ShortSku + "';")
                    If FromWHApp Then
                        RecordLocationAudit(User, LocationID, AdjustType, Amount, "Auto-adjusted stock of: " + SKU + " at: " + LocationIDConversion(LocationID) + " To: " + NewStock.ToString(), EventSource)
                    Else
                        RecordLocationAudit(User, LocationID, AdjustType, Amount, "Adjusted stock of: " + SKU + " at: " + LocationIDConversion(LocationID) + " To: " + NewStock.ToString(), EventSource)
                    End If
                ElseIf NewStock = 0 Then

                    If FromWHApp Then
                        MySQL.insertUpdate("UPDATE whldata.Shortsku_locations SET StockLevel='" + NewStock.ToString() + "'WHERE LocationId ='" + LocationID.ToString() + "' AND Shortsku='" + ShortSku + "';")
                        RecordLocationAudit(User, LocationID, AdjustType, Amount, "Auto Adjusted Location from: " + SKU + " at: " + LocationIDConversion(LocationID) + " No Stock Remaining", EventSource)
                    ElseIf Not (result(0)("locType") = 1) Then
                        MySQL.insertUpdate("DELETE FROM whldata.Shortsku_locations where id ='" + result(0)("id") + "';")
                        RecordLocationAudit(User, LocationID, AdjustType, Amount, "Removed Location from: " + SKU + " at: " + LocationIDConversion(LocationID), EventSource)
                    Else
                        MySQL.insertUpdate("UPDATE whldata.Shortsku_locations SET StockLevel='" + NewStock.ToString() + "'WHERE LocationId ='" + LocationID.ToString() + "' AND Shortsku='" + ShortSku + "';")
                        RecordLocationAudit(User, LocationID, AdjustType, Amount, "Auto Adjusted Location from: " + SKU + " at: " + LocationIDConversion(LocationID) + " No Stock Remaining", EventSource)

                    End If
                ElseIf NewStock < 0 And Not (FromWHApp) Then
                    Throw New NegativeStockException("The stock cannot be negative")
                Else
                    'Called when from a Warehouse Application
                    MySQL.insertUpdate("UPDATE whldata.Shortsku_locations SET StockLevel='" + NewStock.ToString() + "'WHERE LocationId ='" + LocationID.ToString() + "' AND Shortsku='" + ShortSku + "';")
                    RecordLocationAudit(User, LocationID, AdjustType, Amount, "Auto Adjusted Location from: " + SKU + " at: " + LocationIDConversion(LocationID) + " No Stock Remaining", EventSource)

                End If
                RefreshLocations()
            Else
                Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
            End If
        Else
            AddLocationWithAudit(LocationID, User, Amount, EventSource,True)
            'Throw New Exceptions.LocationNullReferenceException("The location with reference ID '" + LocationID.ToString + "' is not attached to the item to be removed.")
        End If
    End Sub
    ''' <summary>
    ''' Used to move Items between Locations
    ''' </summary>
    ''' <param name="OldLocationID"></param>
    ''' <param name="User"></param>
    ''' <param name="Amount"></param>
    ''' <param name="NewLocationID"></param>
    ''' <param name="EventSource"></param>
    Public Sub MoveLocationWithAudit(OldLocationID As Integer, User As Employee,Amount As Integer, NewLocationID As Integer, EventSource As String)
        Dim newLocResult = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations a left join whldata.locationreference b on a.LocationID = b.locID WHERE LocationID ='"+NewLocationID.ToString()+"' AND ShortSku='"+ShortSKU+"' LIMIT 1;")
        Dim oldLocResult = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations a left join whldata.locationreference b on a.LocationID = b.locID WHERE LocationID ='"+OldLocationID.ToString()+"' AND ShortSku='"+ShortSKU+"' LIMIT 1;")
        if (newLocResult.Count = 0)
            AddLocationWithAudit(NewLocationID,User,0,EventSource,True)
            newLocResult = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations a left join whldata.locationreference b on a.LocationID = b.locID WHERE LocationID ='"+NewLocationID.ToString()+"' AND ShortSku='"+ShortSKU+"' LIMIT 1;")
        End If
        Dim newLocationStock = Convert.ToInt32(newLocResult(0)("StockLevel")) + Amount
        Dim oldLocationStock = Convert.ToInt32(oldLocResult(0)("StockLevel")) - Amount
        if(newLocationStock < 0)
            newLocationStock = amount
        End If
        if(oldLocationStock < 0 And Not(oldLocResult(0)("locType").ToString() = "1"))
            Throw new NegativeStockException("The stock cannot be negative")
        End If
        If(oldLocationStock = 0 And Not(oldLocResult(0)("locType").ToString() = "1")) 'OldLocation has no stock and is not a picking location.
            MySQL.insertUpdate($"UPDATE whldata.Shortsku_locations SET StockLevel = '{newLocationStock}' where id = '"+newLocResult(0)("id")+"';DELETE FROM whldata.Shortsku_locations where id ='" + oldLocResult(0)("id")+"';")
            RecordLocationAudit(User,NewLocationID,AuditEvents.Move_To, Amount,$"Moved {Amount} of {SKU} To {newLocResult(0)("locText")}",EventSource)
            RecordLocationAudit(User,OldLocationID,AuditEvents.Move_From, -Amount,$"Moved {Amount} of {SKU} From {oldLocResult(0)("locText")}",EventSource)
            RecordLocationAudit(User,OldLocationID,AuditEvents.RemovedLocation, 0,$"Removed {SKU} from {oldLocResult(0)("locText")}",EventSource)
        Else if ((oldLocationStock = 0 And oldLocResult(0)("locType").ToString() = "1"))
            MySQL.insertUpdate($"UPDATE whldata.Shortsku_locations SET StockLevel = '{newLocationStock}' where id = '"+newLocResult(0)("id")+"';UPDATE whldata.Shortsku_locations SET StockLevel = '0' where id ='" + oldLocResult(0)("id")+"';")
            RecordLocationAudit(User,NewLocationID,AuditEvents.Move_To, Amount,$"Moved {Amount} of {SKU} To {newLocResult(0)("locText")}",EventSource)
            RecordLocationAudit(User,OldLocationID,AuditEvents.Move_From, -Amount,$"Moved {Amount} of {SKU} From {oldLocResult(0)("locText")}",EventSource)
        else
            MySQL.insertUpdate($"UPDATE whldata.Shortsku_locations SET StockLevel = '{newLocationStock}' where id = '{newLocResult(0)("id")}';UPDATE whldata.Shortsku_locations SET StockLevel = '{oldLocationStock}' where id ='" + oldLocResult(0)("id")+"';")
            RecordLocationAudit(User,NewLocationID,AuditEvents.Move_To, Amount,$"Moved {Amount} of {SKU} To {newLocResult(0)("locText")}",EventSource)
            RecordLocationAudit(User,OldLocationID,AuditEvents.Move_From, -Amount,$"Moved {Amount} of {SKU} To {oldLocResult(0)("locText")}",EventSource)
        End If

    End Sub
    Public Sub SetLocationStockWithAudit(LocationID As integer, User As employee,Amount as Integer, EventSource As String)
        Dim result = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations WHERE LocationID ='"+LocationID.ToString()+"' AND ShortSku='"+ShortSku+"' LIMIT 1;")
        If result.Count > 0

            If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU
                Dim NewStock = Amount

                Try
                    Dim query as string = "UPDATE whldata.Shortsku_locations SET StockLevel = '" + Amount.ToString( )+"' WHERE Shortsku = '"+Shortsku+"' AND LocationId ='"+LocationID.ToString()+"';"
                    MySQL.insertUpdate(query)
                Catch ex As Exception
                    Console.WriteLine(ex.Message)
                    Throw
                End Try

                RecordLocationAudit(User, LocationID, AuditEvents.Adjust_Set, Amount, "Adjusted stock of: " + SKU + " at: " + LocationIDConversion(LocationID) + " To: " + NewStock.ToString(), EventSource)
                RefreshLocations()
            else
                Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
            End If
        Else
            AddLocationWithAudit(LocationID,User,Amount, EventSource,True)
        End if
    End Sub
    Public Sub MoveAllBetweenLocations(OldLocationID As Integer, User As Employee, NewLocationID As Integer , EventSource As String ,Optional Amount As Integer = 0)
        Dim newLocResult = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations join locationreference on locid = locationid WHERE LocationID ='"+NewLocationID.ToString()+"' AND Shortsku='"+ShortSku+"' LIMIT 1;")
        Dim oldLocResult = MySQL.SelectDataDictionary($"SELECT * from whldata.Shortsku_locations join locationreference on locid = locationid WHERE LocationID ='{OldLocationID}' AND Shortsku='{ShortSku}' LIMIT 1;")
        If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU Then
            Dim NewStock = 0
            Dim OldShelfName = oldLocResult(0)("Shelfname")
            Dim NewShelfName = newLocResult(0)("Shelfname")
            If newLocResult.Count = 1 Then

                Try
                    If oldLocResult(0)("locType").ToString() = "1" Then
                        newstock = Convert.ToInt32(GetLocation(SKULocation.SKULocationType.Pickable).Additional)
                    Else
                        newstock = Convert.ToInt32(oldLocResult(0)("StockLevel"))
                    End If

                    newstock += Int32.Parse(newLocResult(0)("StockLevel").ToString())
                Catch ex As Exception
                    newstock = 0
                End Try
            End If
            Dim id As String = ShortSku.ToString + "_" + NewLocationID.ToString
            Try
                if oldLocResult(0)("locType").ToString() = "1" and (not newLocResult(0)("locType").ToString() = "1")
                    Dim query = $"UPDATE whldata.Shortsku_locations SET StockLevel = 0 where id='{oldLocResult(0)("id")}';REPLACE INTO whldata.Shortsku_locations (id,shelfname,Shortsku,StockLevel,locationID) VALUES ('" + id + "','" + NewShelfName + "','" + ShortSku + "','" + NewStock.ToString() + "','" + NewLocationID.ToString + "');"
                    MySQL.insertUpdate(query)
                else
                    Dim query = "DELETE FROM whldata.Shortsku_locations WHERE id='" + oldLocResult(0)("id") + "';REPLACE INTO whldata.Shortsku_locations (id,shelfname,Shortsku,StockLevel,locationID) VALUES ('" + id + "','" + NewShelfName + "','" + ShortSku + "','" + NewStock.ToString() + "','" + NewLocationID.ToString + "');"
                    MySQL.insertUpdate(query)
                End If

            Catch ex As Exception

                throw
                'Quality.
            End Try
            Dim friendly As String = "Moved all of " + SKU + " from: " + OldShelfName + " To: " + NewShelfName
            RecordLocationAudit(User, OldLocationID, AuditEvents.Move_From, newstock, friendly, EventSource)
            RecordLocationAudit(User, NewLocationID, AuditEvents.Move_To, newstock, friendly, EventSource)
            RefreshLocations()
        Else
            Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
        End If

    End Sub
    Public Function AdjustStockReturningNewStock(LocationID As Integer, User As Employee, Amount As Integer, EventSource As String) As Integer
        Dim result = MySQL.SelectDataDictionary("SELECT * from whldata.Shortsku_locations WHERE LocationID ='"+LocationID.ToString()+"' AND ShortSku='"+ShortSku+"' LIMIT 1;")
        If result.Count > 0

            If User.Permissions.ModLocation Or User.Permissions.DeliveryMods Or User.Permissions.PrepackAdmin Or User.Permissions.SaveChangesToSKU
                Dim NewStock = Amount

                Try
                    Dim query as string = "UPDATE whldata.Shortsku_locations SET StockLevel = '" + Amount.ToString( )+"' WHERE ShortSku = '"+ShortSku+"' AND LocationID ='"+LocationID.ToString()+"';"
                    MySQL.insertUpdate(query)
                Catch ex As Exception
                    Console.WriteLine(ex.Message)
                    Throw
                End Try

                RecordLocationAudit(User, LocationID, AuditEvents.Adjust_Set, Amount, "Adjusted stock of: " + SKU + " at: " + LocationIDConversion(LocationID) + " To: " + NewStock.ToString(), EventSource)
                RefreshLocations()
            else
                Throw New Exceptions.InsufficientPermissionsException("You don't have the required permission (D) to remove a location from an item.")
            End If
        Else
            AddLocationWithAudit(LocationID,User,Amount, EventSource,True)
        End if
        return Amount
    End Function
   #end region
    Public Sub DownloadChangelog(Optional Precomp_Changelog As ConcurrentBag(Of SKUChangelog) = nothing)
        '17/12/2015     Added the class to store and then the list of "SKUChangelog" which will be used for
        '               audit trailing. Hopefully it won't get too big but who knows kek.
        '
        '               Also removed events after realising that the changelog was basically the same.
        T
        If Not IsNothing(Precomp_Changelog)
            Changelog.AddRange(Precomp_Changelog.Where(Function(Cl As SKUChangelog) cl.ShortSku = shortsku).ToList)
        Else
            Changelog.Clear()
            Dim ChangesResponse As ArrayList = MySQL.SelectData("SELECT * FROM whldata.sku_changelog WHERE shortsku='" + ShortSku + "';")
            For Each ChangesData As ArrayList In ChangesResponse
                Dim NewChange As New SKUChangelog
                NewChange.DateModified = Date.Parse(ChangesData(4))
                NewChange.Reason = ChangesData(3).ToString
                NewChange.UserId = Convert.ToInt32(ChangesData(2).ToString)
                Changelog.Add(NewChange)
            Next
        End If
        If Changelog.Count = 0 Then
            Dim NewChange As New SKUChangelog
            NewChange.DateModified = Now
            NewChange.Reason = "No entries available"
            NewChange.UserId = 0
            Changelog.Add(NewChange)
        End If
        T("Changelog")
    End Sub

    Public Function SearchSupplierByCode(Code As String) As SKUSupplier
        For Each supp As SKUSupplier In Suppliers
            If supp.Name.ToLower = Code.ToLower Then
                Return supp
            End If
        Next
        Return Nothing
    End Function

    '16/05/2016     Downloads the listings from the listing table.
    Public Sub GetListings()
        Try
            Listings.Clear()
            'Dim ListingsSQL As ArrayList = MySql.SelectData("SELECT * FROM whldata.sku_websaleinfo WHERE Sku='" + SKU + "';")
            'For Each listing As ArrayList In ListingsSQL
            '    Dim NewListing As New SkuListing
            '    NewListing.ListingID = listing(0)
            '    NewListing.AttachedSKU = listing(1).ToString
            '    NewListing.Title = listing(2).ToString
            '    NewListing.HTML = listing(3).ToString
            '    NewListing.UserModified = listing(4)
            '    NewListing.ItemNumber = listing(5).ToString
            '    NewListing.Website = listing(6).ToString
            '    Listings.Add(NewListing)
            'Next
        Catch ex As Exception
            Listings = New List(Of SkuListing)
        End Try
    End Sub

    'This is where we keep all of the main data.

    Public SKU As String
    Public ShortSku As String
    Public Stock As New SKUStock
    Public Price As New SKUPrices
    Public Title As New SKUTitles
    Public Images As New List(Of SKUImage)
    Public Suppliers As New List(Of SKUSupplier)
    Public PrepackInfo As New SKUPrepack
    Public Pieces As Integer = 1
    Public DeliveryNote As String = ""
    Public Costs As New SKUCosts
    Public Locations As New List(Of SKULocation)
    Public SalesData As New SKUSalesData
    Public PackSize As Integer = 0
    Public ExtendedProperties As New SKUExtended
    Public SearchKeywords As New List(Of String)
    Public Profile As New SKUProfile
    Public CustomData As New List(Of Object)
    Public Category As String = ""
    Public NewItem As New SKUNewItem
    Public Dates As New SKUDates
    'Public Events As New List(Of SKUEvents)        Redundant before it was even used. RIP.
    Public Changelog As New List(Of SKUChangelog)
    '21/01/2016     Added the envelope object because it's an absolute ballache having to parse it all the time.
    Public EnvelopeObject As Envelope
    '17/02/2016     Added a list of barcodes. Works like the search keywords, but better a lot tighter on the keywords so people can only scan what they mean to.
    Public Barcodes As New List(Of String)
    '21/03/2016     Added the picking and packing notes so picking and packing applications can use them.
    Public PickNote As String = ""
    Public PackNote As String = ""
    Public KnowledgeNote As String = ""
    'Public SaleTitle As String = ""
    'Public SaleDesc As String = ""
    '16/05/2016     Added the listings functionality.
    Public Listings As New List(Of SkuListing)

    '07/08/2016     Added Bundles, which need compositions. Also added the bundle flag.
    Public isBundle As Boolean = False
    Public Composition As New List(Of WhlSKU)

End Class

<System.Serializable()>
Public Class SKUDates
    Public Sub New()
        FirstRecorded = Date.MinValue
        FirstPriced = Date.MinValue
        FirstPhoto = Date.MinValue
        FirstListed = Date.MinValue
        AddedToLinnworks = Date.MinValue
    End Sub

    Public FirstRecorded As Date
    Public FirstPriced As Date
    Public FirstPhoto As Date
    Public AddedToLinnworks As Date
    Public FirstListed As Date
End Class
'17/12/2015     SKUEvents has been removed due to redundancy.
<System.Serializable()>
Public Class SKUSalesData
    'Remember, we don't download this automatically
    Public Sub New()
        Sku = ""
        Weighted = 0
        Raw8 = 0
        Raw4 = 0
        Raw1 = 0
        Avg8 = 0
        Avg4 = 0
        Avg1 = 0
    End Sub

    Dim Sku As String
    Dim Weighted As Integer
    Dim Raw8 As Integer
    Dim Raw4 As Integer
    Dim Raw1 As Integer
    Dim Avg8 As Integer
    Dim Avg4 As Integer
    Dim Avg1 As Integer

    '13/04/2016     Added to consolidate teh shitty and widely variable attempts to calculate total sales for items across packsizes.
    Public CombinedWeekly As Integer = 0

    Public Property WeightedAverage
        Get
            Return Weighted
        End Get
        Set(value)
            Weighted = value
        End Set
    End Property
    Public Property EightWeekTotal
        Get
            Return Raw8
        End Get
        Set(value)
            Raw8 = value
        End Set
    End Property
    Public Property FourWeekTotal
        Get
            Return Raw4
        End Get
        Set(value)
            Raw4 = value
        End Set

    End Property
    Public Property OneWeekTotal
        Get
            Return Raw1
        End Get
        Set(value)
            Raw1 = value
        End Set
    End Property
    Public Property EightWeekAverage
        Get
            Return Avg8
        End Get
        Set(value)
            Avg8 = value
        End Set
    End Property
    Public Property FourWeekAverage
        Get
            Return Avg4
        End Get
        Set(value)
            Avg4 = value
        End Set
    End Property
    Public Property OneWeekAverage
        Get
            Return Avg1
        End Get
        Set(value)
            Avg1 = value
        End Set
    End Property

    Public Sub SetSku(NewSKU As String)
        Sku = NewSKU
    End Sub
    Public Sub DownloadData()
        Dim Resp As ArrayList = MySQL.SelectData("SELECT * FROM whldata.salesdata WHERE Sku='" + Sku + "';")
        If Resp.Count < 1 Then
            Raw8 = 0
            Raw4 = 0
            Raw1 = 0
            Weighted = 0
            Avg8 = 0
            Avg4 = 0
            Avg1 = 0
        Else

            Raw8 = Resp(0)(1)
            Raw4 = Resp(0)(2)
            Raw1 = Resp(0)(3)
            Weighted = Resp(0)(4)
            Avg8 = Resp(0)(5)
            Avg4 = Resp(0)(6)
            Avg1 = Resp(0)(7)
        End If

        '13/04/2016     Added to consolidate teh shitty and widely variable attempts to calculate total sales for items across packsizes.
        CombinedWeekly = 0
        Dim RespCombo As ArrayList = MySQL.SelectData("SELECT salesdata.sku, salesdata.Weighted8Week, whlnew.packsize FROM whldata.salesdata INNER JOIN whldata.whlnew ON whlnew.sku = salesdata.sku WHERE salesdata.sku LIKE '" + Sku.Substring(0, 7) + "%'")
        If RespCombo.Count > 0 Then
            For Each item As ArrayList In RespCombo
                CombinedWeekly += (Convert.ToInt32(item(1)) * Convert.ToInt32(item(2)))
            Next
        End If

    End Sub
End Class
<System.Serializable()>
Public Class SKUExtended
    Public Sub New()
        Parts = 1
        Screws = 0
        HasScrews = False
        GS1Barcode = ""
        Envelope = "x"
        LabourCode = ""
        NeedsCourier = False
        Oversized = False
        IsPair = False
        Inner = ""
        SplitBundle = False
    End Sub

    Public Parts As Integer
    Public Screws As Integer
    Public HasScrews As Boolean
    Public GS1Barcode As String
    Public Envelope As String
    Public LabourCode As String
    Public NeedsCourier As Boolean
    Public Oversized As Boolean
    Public IsPair As Boolean
    Public Inner As String
    Public SplitBundle As Boolean
End Class
<System.Serializable()>
Public Class SKUProfile
    Public Sub New()
        Weight = 0
    End Sub
    Public Weight As Single
End Class
<System.Serializable()>
Public Class SKUNewItem
    Public Sub New()
        Brand = ""
        Finish = ""
        Description = ""
        Size = ""
        Note = ""
        Box = ""
        Status = ""
        InitShelf = ""
        HasBeenListed = False
    End Sub

    Public Brand As String
    Public Finish As String
    Public Description As String
    Public Size As String
    Public Note As String
    Public Box As String
    Public Status As String
    Public InitShelf As String
    Public InitStock As Integer = 0
    Public InitLevel As Integer = 0
    Public InitMinimum As Integer = 0
    Public ListPriority As Integer = 1
    Public IsListed As Boolean = True
    Public HasBeenListed As Boolean = False

End Class
<System.Serializable()>
Public Class SKULocation
    Public LocationTableID As String
    Public LocalLocationName As String
    Public WarehouseID As Integer
    Public LocationID As Integer
    Public LocationText As String
    Public LocationType As SKULocationType
    Public Additional As String = " "
    Public Sku As String
    Public RouteIndex As Integer = 0

    Public Enum SKULocationType
        Unused = -1
        Storage = 0
        Pickable = 1
        Prepack = 2
        Delivery = 3
        PrepackInstant = 4
        PrepackComplete = 5
        Damaged = 6
        Lost = 7
        Returns = 8
    End Enum
End Class
<System.Serializable()>
Public Class SKUCosts
    Public Sub New()
        Packing = 0
        Postage = 0
        Envelope = 0
        Fees = 0
        Labour = 0
        VAT = 0
        Total = 0
    End Sub

    Public Packing As Single
    Public Postage As Single
    Public Envelope As Single
    Public Fees As Single
    Public Labour As Single
    Public VAT As Single
    Public Total As Single
End Class
<System.Serializable()>
Public Class SKUSupplier
    Dim SuppName As String = ""
    Dim SuppCode As String = ""
    Dim SuppPrice As Single = 0.00
    Dim SuppCaseCode As String = ""
    Dim SuppBarCode As String = ""
    Dim SuppDisCon As Boolean = False
    Dim SuppPrimary As Boolean = False
    Dim SuppLead As Integer = 4
    Dim SuppNoStock As Boolean = False
    Dim Modified As String = "New Item"
    ''' <summary>
    ''' This is not used.
    ''' </summary>
    Dim SuppBoxCode As String = ""
    Dim SuppCaseInnerBarcode As String = ""
    Public TableDataId As String
    Public ShortSku as string

    ''' <summary>
    ''' barcode which can be found on inners. For some reason this is occasionally different.
    ''' </summary>
    ''' <returns></returns>
    Public Property CaseBarcodeInner As String
        Get
            Return SuppCaseInnerBarcode
        End Get
        Set(value As String)
            SuppCaseInnerBarcode = value
        End Set
    End Property

    ''' <summary>
    ''' The code which appears on the box. May not be the same as the reorder code for some suppliers. ### NOT USEED ###
    ''' </summary>
    ''' <returns></returns>
    Public Property BoxCode As String
        Get
            Return SuppBoxCode
        End Get
        Set(value As String)
            SuppBoxCode = value
        End Set
    End Property

    ''' <summary>
    ''' Weeks which are the "lead time"
    ''' </summary>
    ''' <returns></returns>
    Public Property LastModified As String
        Get
            Return Modified
        End Get
        Set(value As String)
            Modified = value
        End Set
    End Property

    ''' <summary>
    ''' Weeks which are the "lead time"
    ''' </summary>
    ''' <returns></returns>
    Public Property LeadTime As Integer
        Get
            Return SuppLead
        End Get
        Set(value As Integer)
            SuppLead = value
        End Set
    End Property

    ''' <summary>
    ''' The 3 character supplier code.
    ''' </summary>
    ''' <returns></returns>
    Public Property OutOfStock As Boolean
        Get
            Return SuppNoStock
        End Get
        Set(value As Boolean)
            SuppNoStock = value
        End Set
    End Property

    ''' <summary>
    ''' The 3 character supplier code.
    ''' </summary>
    ''' <returns></returns>
    Public Property Name As String
        Get
            Return SuppName
        End Get
        Set(value As String)
            SuppName = value
        End Set
    End Property

    ''' <summary>
    ''' The re-order code for the supplier which identifies the product on the supplier's site.
    ''' </summary>
    ''' <returns></returns>
    Public Property ReOrderCode As String
        Get
            Return SuppCode
        End Get
        Set(value As String)
            SuppCode = value
        End Set
    End Property

    ''' <summary>
    ''' The last recorded price that the item was ordered for.
    ''' </summary>
    ''' <returns></returns>
    Public Property Price As Single
        Get
            Return SuppPrice
        End Get
        Set(value As Single)
            SuppPrice = value
        End Set
    End Property

    ''' <summary>
    ''' The barcode on the outer box. this is sometimes the same as the normal barcode.
    ''' </summary>
    ''' <returns></returns>
    Public Property CaseBarcode As String
        Get
            Return SuppCaseCode
        End Get
        Set(value As String)
            SuppCaseCode = value
        End Set
    End Property

    ''' <summary>
    ''' The barcode on the product itself.
    ''' </summary>
    ''' <returns></returns>
    Public Property Barcode As String
        Get
            Return SuppBarCode
        End Get
        Set(value As String)
            SuppBarCode = value
        End Set
    End Property

    ''' <summary>
    ''' This value is set to true of the product has been discontinued.
    ''' </summary>
    ''' <returns></returns>
    Public Property Discontinued As Boolean
        Get
            Return SuppDisCon
        End Get
        Set(value As Boolean)
            SuppDisCon = value
        End Set
    End Property
    ''' <summary>
    ''' This values states whether this supplier is the primary one or not.
    ''' </summary>
    ''' <returns></returns>
    Public Property Primary As Boolean
        Get
            Return SuppPrimary
        End Get
        Set(value As Boolean)
            SuppPrimary = value
        End Set
    End Property
End Class
<System.Serializable()>
Public Class SKUStock
    Public Sub New()
        slevel = 0
        smin = 0
        sdormant = 0
        stotal = 0
    End Sub

    Dim slevel As Integer
    Dim smin As Integer
    Dim sdormant As Integer
    Dim stotal As Integer

    Public Property Level As Integer
        Get
            Return slevel
        End Get
        Set(value As Integer)
            slevel = value
        End Set
    End Property
    Public Property Minimum As Integer
        Get
            Return smin
        End Get
        Set(value As Integer)
            smin = value
        End Set
    End Property
    Public Property Dormant As Integer
        Get
            Return sdormant
        End Get
        Set(value As Integer)
            sdormant = value
        End Set
    End Property
    Public Property Total As Integer
        Get
            Return stotal
        End Get
        Set(value As Integer)
            stotal = value
        End Set
    End Property
End Class
<System.Serializable()>
Public Class SKUPrices
    Public Sub New()
        Net = 0
        Gross = 0
        Retail = 0
        Profit = 0
        Margin = 0
    End Sub

    Public Net As Single
    Public Gross As Single
    Public Retail As Single
    Public Profit As Single
    Public Margin As Single
End Class
<System.Serializable()>
Public Class SKUTitles
    Public Sub New()
        Invoice = ""
        Label = ""
        Linnworks = ""
        NewItem = ""
        Distinguish = ""

    End Sub
    Public Invoice As String
    Public Label As String
    Public Linnworks As String
    Public NewItem As String
    Public Distinguish As String
End Class
<System.Serializable()>
Public Class SKUImage
    Public ImagePath As String
    Public FullImagePath As String
    Public ImageData As System.Drawing.Image = Nothing
    ''' <summary>
    ''' This is actually the database index
    ''' </summary>
    Public ImageFileName As String
    Public sku as string
    Public isPrimary as Boolean = false
End Class
'Pskupost has been removed because it was redundant.

<System.Serializable()>
Public Class SKUPrepack
    Public Sub New()
        Bag = "NEVER BAGGED"
        Notes = ""
        PrepackLog = Nothing
    End Sub

    Public Bag As String
    Public Notes As String
    Public PrepackLog As List(Of PrepackLog)
End Class
<System.Serializable()>
Public Class SKUChangelog
    Public UserId As Integer
    Public DateModified As Date
    Public Reason As String
    Friend ChangelogID as integer
    Public ShortSku As string
End Class

'16/05/2016     skuListing added containing data about listings.

<Serializable>
Public Class SkuListing
    Public ListingID As Integer = 0
    Public AttachedSKU As String
    Public Title As String
    Public HTML As String
    Public UserModified As Integer
    Public ItemNumber As String
    Public Website As String

    Public Sub Save()
        If ListingID = 0 Then
            'New Listing
            MySql.insertupdate("INSERT INTO whldata.sku_websaleinfo (Sku,ListingTitle,ListingDescription,CreatedUser,ItemID,website) VALUES ('" + AttachedSKU + "','" + Title + "','" + HTML.Replace("\", "\\").Replace("'", "\'") + "'," + UserModified.ToString + ",'" + ItemNumber + "','" + Website + "');")
            ListingID = MySql.LastAddedID
        Else
            MySql.insertupdate("REPLACE INTO whldata.sku_websaleinfo (ID,Sku,ListingTitle,ListingDescription,CreatedUser,ItemID,website) VALUES (" + ListingID.ToString + ",'" + AttachedSKU + "','" + Title + "','" + HTML.Replace("\", "\\").Replace("'", "\'") + "'," + UserModified.ToString + ",'" + ItemNumber + "','" + Website + "');")
        End If
    End Sub
End Class