0

Recently I asked this question:

Find Common Values in several arrays, or lists VB.NET

I am trying to make an intelligent stock pick/dispatch location system at the point of invoice from a stock database. Multiple locations are available to dispatch from, and essentially I want to make efficient dispatch, so if all items purchased can be dispatched from one location, they are grouped and dispatched as such, but if not, it groups what it can and dispatches the rest from wherever the highest stock level is available.

Having thought about it a bit more over the holiday I don't think I can approach it that way.

Two reasons

  1. Because the number of locations has to be variable, this system is scalable.
  2. Because the number of items is also scalable

So, instead of listing suitable stock locations, I am now listing the quantity of stock available in each location for the respective items.

 Items       Locations
_________|__1___|___2__|__3__| - this is location IDs
Item 1   |  3   ,   4  ,  1    - this is the qty of stock available
Item 2   |  2   ,   4  ,  0    
Item 3   |  1   ,   3  ,  1    
Item 4   |  6   ,   1  ,  3    

I can turn this into a string which might be used to split and create arrays

ie

 stockDetails = "3,4,1|2,4,0|1,3,1|6,1,3"

Here, the comma separated values are quantity of available stock in each stock location and the pipes separate the individual items, so the table above is translated to the string above.

I am not keen on multi-dimensional arrays and don't know how I'd create one without knowing how many stock locations there are.

We can safely assume

  1. That the stock quantities are in the correct order to correlate back to the stock locations IDs.
  2. Each pipe separate series of comma separated values will have the same number of comma separated values.

I just can't work out how to determine the pick locations!

In the above example, all four items can actually be picked from stock location 1, providing only one of each item is purchased. Suppose though a customer bought 2 of item 3. Then the pick would have to be from location 2. All kinds of other scenarios can of course be presented depending on the number of items purchased, the quantity of items purchased and how many stock locations there are to pick from.

I started off simply picking from the location with the highest available stock, but that made no sense when stock was available to pick from more than one locaton because we ended up with multiple dispatch locations which was not necessary.

How can I analyse these variable length strings/arrays to determine the most intelligent way to dispatch.

I can work it out from the stock table with my human brain very well, but cannot see how to program VB.NET to do it!

Please help with VB examples, and also, although I appreciate the help on my original question and I did not specify, I cannot use Linq as I am on .NET 2.0 framework.

Community
  • 1
  • 1
Jamie Hartnoll
  • 7,231
  • 13
  • 58
  • 97
  • Can you use generic collections, or are you stuck using arrays for some reason? – John Saunders Dec 28 '11 at 18:52
  • Nope, not stuck, just inexpereinced. I'll Google generic collections... if you have an appropriate link to something that would help, please let me know! :-) – Jamie Hartnoll Dec 28 '11 at 18:54
  • http://msdn.microsoft.com/en-us/library/system.collections.generic.aspx – John Saunders Dec 28 '11 at 18:57
  • Right. Ok, this is possibly going to be another one of those "steep learning curve" examples! I'm not sure how to apply my problem to Generic Collections. I'm thinking you are suggesting using a Dictionary Collection? How would you recommend this being structured? Apologies for probably a very basic question, but I'm very new to .NET. I've also only just got to grips with basic JSON use, the Distionary structure here looks similar... – Jamie Hartnoll Dec 28 '11 at 19:13
  • I recommend you start with http://msdn.microsoft.com/en-us/vstudio/hh388568.aspx. Don't take forever reading everything, but at least get familiar with the basics of the language and of .NET. Generic collections are pretty basic. – John Saunders Dec 28 '11 at 19:15

1 Answers1

2

If this were my application, I would create a small class that, for each location, would hold a list of the available quantities for each requested product.

Upon retrieving the data, I would create a collection of these location classes for each location that had inventory for at least one of the products.

I would then have a method that provides a weight to indicate the percentage of products that can be fulfilled from that location. I would also probably have a method to indicate distance from the location to the delivery location, if known.

The location class would implement IComparable using the percentage of products and distance as the comparison metrics so that the items could be sorted within the collection.

To determine the locations the order would be fulfilled from, I would do the following:

1) Sort the list of locations and select the first one in the list. If the locations are known, this will also be the one closest to the delivery location.

2) If the location selected in step 1 did not satisfy 100% of the order, cycle through the list of locations and remove the products that were satisfied by the selected location. If a given location is at 0%, remove it from the collection of locations.

3) If all products have not been picked and there are still locations in the list, restart from step 1.

Hopefully this helps you get on the right track.

Update

Here is something to get you started.

This is a start on the location class

Public Class LocationQuantities
    Implements IComparable

    Public Sub New()
        m_cProductQuantities = New Generic.Dictionary(Of Integer, Decimal)
    End Sub

    Private m_wLocationId As Integer

    Public Property LocationId As Integer
        Get
            Return m_wLocationId
        End Get
        Set(value As Integer)
            m_wLocationId = value
        End Set
    End Property

    Private m_cProductQuantities As Generic.Dictionary(Of Integer, Decimal)
    ''' <summary>
    ''' A collection of quantities for each product. The key to the collection is the product id
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ProductQuantities As Generic.Dictionary(Of Integer, Decimal)
        Get
            Return m_cProductQuantities
        End Get
    End Property

    Private m_dWeight As Double
    ''' <summary>
    ''' This contains the weight of products for this location as set in CalculateWeight
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Weight As Double
        Get
            Return m_dWeight
        End Get
    End Property

    ''' <summary>
    ''' This method sets the weight for the specified list of product ids
    ''' </summary>
    ''' <param name="cProductIds"></param>
    ''' <remarks></remarks>
    Public Sub CalculateWeight(cProductIds As Generic.List(Of Integer))

        Dim wAvailableProducts As Integer

        ' Cycle through the list of available products
        For Each wProductId As Integer In cProductIds
            ' If our list of products contains the specified product and the product quantity is not 0
            If Me.ProductQuantities.ContainsKey(wProductId) AndAlso Me.ProductQuantities(wProductId) <> 0 Then
                ' Increase the count
                wAvailableProducts += 1
            End If
        Next

        ' Finally calculate the weight as the percentage of available products 
        If cProductIds.Count <> 0 Then
            m_dWeight = wAvailableProducts / cProductIds.Count
        Else
            m_dWeight = 0
        End If
    End Sub

    ''' <summary>
    ''' This method is used to compare one location to the next
    ''' </summary>
    ''' <param name="obj"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CompareTo(obj As Object) As Integer Implements System.IComparable.CompareTo

        With DirectCast(obj, LocationQuantities)
            Return .Weight.CompareTo(Me.Weight)
        End With
    End Function

    ''' <summary>
    ''' This method is used to add a quantity for the specified productid
    ''' </summary>
    ''' <param name="wProductId"></param>
    ''' <param name="dQuantity"></param>
    ''' <remarks></remarks>
    Public Sub AddQuantityForProductId(wProductId As Integer, dQuantity As Decimal)

        ' First, see if the product id exists in the list of our product quantities
        If Me.ProductQuantities.ContainsKey(wProductId) Then
            ' It does exist, so add the new quantity to the existing value
            Me.ProductQuantities(wProductId) += dQuantity
        Else
            ' The product id does not exist, so add a new entry
            Me.ProductQuantities.Add(wProductId, dQuantity)
        End If
    End Sub
End Class

Here is a collection class for the above item that performs a lot of the work

''' <summary>
''' This collection contains a list if LocationQuantities keyed by LocationId
''' </summary>
''' <remarks></remarks>
Public Class LocationQuantitiesCollection
    Inherits Generic.List(Of LocationQuantities)

    ' A local dictionary used for indexing
    Private m_cDictionaries As Generic.Dictionary(Of Integer, LocationQuantities)

    Public Sub New()
        m_cDictionaries = New Generic.Dictionary(Of Integer, LocationQuantities)
    End Sub

    ''' <summary>
    ''' This method adds the product and quantity to the specified location
    ''' </summary>
    ''' <param name="wLocationId"></param>
    ''' <param name="wProductId"></param>
    ''' <param name="dQuantity"></param>
    ''' <remarks></remarks>
    Public Sub AddLocationAndQuantityForProduct(wLocationId As Integer, wProductId As Integer, dQuantity As Decimal)

        Dim oLocationQuantities As LocationQuantities

        ' First, see if the location id exists in this collection
        If m_cDictionaries.ContainsKey(wLocationId) Then
            ' It does exist, so get a local reference
            oLocationQuantities = m_cDictionaries(wLocationId)
        Else
            ' It does not exist, so add a new entry

            oLocationQuantities = New LocationQuantities
            oLocationQuantities.LocationId = wLocationId

            ' The product id does not exist, so add a new entry
            m_cDictionaries.Add(wLocationId, oLocationQuantities)

            ' Finally, add it to the underlying list
            Me.Add(oLocationQuantities)
        End If

        ' Finally, add the product and quantity to the location quantity
        oLocationQuantities.AddQuantityForProductId(wProductId, dQuantity)
    End Sub

    ''' <summary>
    ''' This method calculates the inventory for the specified products and returns a dictionary keyed by productid 
    ''' whose value is the locationid for the product
    ''' </summary>
    ''' <param name="cProductIds"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CalculateInventory(cProductIds As List(Of Integer)) As Generic.Dictionary(Of Integer, Integer)

        ' This dictionary is keyed by productid and the value is the location id that the product will be delivered from
        Dim cProductLocations As New Generic.Dictionary(Of Integer, Integer)
        ' The list of productids left to find
        Dim cProductsToFind As New Generic.Dictionary(Of Integer, Integer)

        ' Copy all requested product ids to the list of product ids to find
        For Each wProductId As Integer In cProductIds
            cProductsToFind.Add(wProductId, wProductId)
        Next

        If Me.Count <> 0 Then
            Do While cProductsToFind.Count <> 0
                Dim oLocation As LocationQuantities

                ' Calculate the weight for each of the locations
                For Each oLocation In Me
                    oLocation.CalculateWeight(cProductIds)
                Next

                ' Sort the list of locations.
                Me.Sort()

                ' Get the first location in the list, update the product locations, then remove the products from each of the locations.
                oLocation = Me.Item(0)

                ' If there are no available products, bail out of the loop
                If oLocation.Weight = 0 Then
                    Exit Do
                End If

                ' For each of the products to be found (cycle backwards because we may be removing items from the list)
                For nI As Integer = cProductsToFind.Count - 1 To 0 Step -1
                    Dim wProductId As Integer

                    ' Get the productid
                    wProductId = cProductsToFind.Keys(nI)

                    ' If this location has a quantity, record this location as the location for the product and remove the product from the list
                    ' of products to find.
                    If oLocation.ProductQuantities.ContainsKey(wProductId) AndAlso oLocation.ProductQuantities(wProductId) <> 0 Then
                        ' This code assumes that found products have been removed
                        cProductLocations.Add(wProductId, oLocation.LocationId)
                        ' Remove the product to find from the list of products to find
                        cProductsToFind.Remove(wProductId)
                    End If
                Next

                If cProductsToFind.Count <> 0 Then
                    ' If there are more products to find, remove the found products from each of the locations and process again.
                    For Each oLocation In Me
                        ' Work backwards through the list of keys since we may be removing items
                        For nI As Integer = oLocation.ProductQuantities.Keys.Count - 1 To 0 Step -1
                            Dim wProductId As Integer

                            ' Get the product id
                            wProductId = oLocation.ProductQuantities.Keys(nI)

                            ' If we no longer need to find this product id, remove it from the list of product quantities at this location
                            If Not cProductsToFind.ContainsKey(wProductId) Then
                                cProductsToFind.Remove(wProductId)
                            End If
                        Next
                    Next
                End If
            Loop
        End If

        Return cProductLocations
    End Function
End Class

Finally, here is an extract from a form that can be used to load the inventory and calculate where a given item will be sourced from:

Public Class Form1

    Dim m_cLocationsAndQuantities As LocationQuantitiesCollection

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Call LoadLocationsAndQuantities()
        Call DoInventoryCalculation()
    End Sub

    ''' <summary>
    ''' Load the locations and quantities
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub LoadLocationsAndQuantities()

        m_cLocationsAndQuantities = New LocationQuantitiesCollection

        Dim wLocationId As Integer
        Dim wProductId As Integer
        Dim dQuantity As Decimal

        wLocationId = 1
        wProductId = 1
        dQuantity = 120

        m_cLocationsAndQuantities.AddLocationAndQuantityForProduct(wLocationId, wProductId, dQuantity)

        wProductId = 2
        dQuantity = 10

        m_cLocationsAndQuantities.AddLocationAndQuantityForProduct(wLocationId, wProductId, dQuantity)

        wLocationId = 2
        wProductId = 1
        dQuantity = 4

        m_cLocationsAndQuantities.AddLocationAndQuantityForProduct(wLocationId, wProductId, dQuantity)

    End Sub

    ''' <summary>
    ''' Perform the inventory calculations
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub DoInventoryCalculation()

        ' The list of productids to calculate inventory for
        Dim cProductIds As New List(Of Integer)
        ' The list of locations where each product will be obtained. The key is the productid and the value is the locationid
        Dim cLocationsAndProducts As Generic.Dictionary(Of Integer, Integer)

        Dim wProductId As Integer

        ' Calculate the inventory for productid 1
        wProductId = 1

        cProductIds.Add(wProductId)

        ' Finally, calculate the inventory
        cLocationsAndProducts = m_cLocationsAndQuantities.CalculateInventory(cProductIds)

        If cLocationsAndProducts Is Nothing OrElse cLocationsAndProducts.Count = 0 Then
            Console.WriteLine("None of the requested products could be found at any location")
        Else
            For Each wProductId In cLocationsAndProducts.Keys
                Console.WriteLine("Product ID " & wProductId & " will be delivered from Location ID " & cLocationsAndProducts(wProductId))
            Next
        End If
    End Sub

End Class

Update

Here is a .Net 2.0 version of the CalculateInventory method:

''' <summary>
''' This method calculates the inventory for the specified products and returns a dictionary keyed by productid 
''' whose value is the locationid for the product
''' </summary>
''' <param name="cProductIds"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function CalculateInventory(cProductIds As List(Of Integer)) As Generic.Dictionary(Of Integer, Integer)

    ' This dictionary is keyed by productid and the value is the location id that the product will be delivered from
    Dim cProductLocations As New Generic.Dictionary(Of Integer, Integer)
    ' The list of productids left to find
    Dim cProductsToFind As New Generic.Dictionary(Of Integer, Integer)

    ' Copy all requested product ids to the list of product ids to find
    For Each wProductId As Integer In cProductIds
        cProductsToFind.Add(wProductId, wProductId)
    Next

    If Me.Count <> 0 Then
        Do While cProductsToFind.Count <> 0
            Dim oLocation As LocationQuantities

            ' Calculate the weight for each of the locations
            For Each oLocation In Me
                oLocation.CalculateWeight(cProductIds)
            Next

            ' Sort the list of locations.
            Me.Sort()

            ' Get the first location in the list, update the product locations, then remove the products from each of the locations.
            oLocation = Me.Item(0)

            ' If there are no available products, bail out of the loop
            If oLocation.Weight = 0 Then
                Exit Do
            End If

            Dim cKeysToRemove As New List(Of Integer)

            ' For each of the products to be found
            For Each wProductId As Integer In cProductsToFind.Keys
                ' If this location has a quantity, record this location as the location for the product and remove the product from the list
                ' of products to find.
                If oLocation.ProductQuantities.ContainsKey(wProductId) AndAlso oLocation.ProductQuantities(wProductId) <> 0 Then
                    ' This code assumes that found products have been removed
                    cProductLocations.Add(wProductId, oLocation.LocationId)
                    ' Add the productid to the list of items to be removed
                    cKeysToRemove.Add(wProductId)
                End If
            Next

            ' Now remove the productids
            For Each wProductId As Integer In cKeysToRemove
                cProductsToFind.Remove(wProductId)
            Next

            If cProductsToFind.Count <> 0 Then
                ' If there are more products to find, remove the found products from each of the locations and process again.
                For Each oLocation In Me
                    For Each wProductId As Integer In oLocation.ProductQuantities.Keys
                        ' If we no longer need to find this product id, remove it from the list of product quantities at this location
                        If Not cProductsToFind.ContainsKey(wProductId) Then
                            cProductsToFind.Remove(wProductId)
                        End If
                    Next
                Next
            End If
        Loop
    End If

    Return cProductLocations
End Function
competent_tech
  • 44,465
  • 11
  • 90
  • 113
  • That's exactly what I want to do... irritatingly, I think it might be beyond my programming expertise though! Hmmm... thanks, points me in the right direction I guess, I'll continue Googling! – Jamie Hartnoll Dec 28 '11 at 19:33
  • @JamieHartnoll: I have updated the answer with some starter code that implements the main concepts that you need to address, with the exception of the distance portion. – competent_tech Dec 28 '11 at 21:09
  • that's awesome! Thanks so much, gone way beyond the call, of duty! I'm not worried about the distance aspect, so that's cool. I'm unable to get to this properly now until Monday, but will post back! Jamie – Jamie Hartnoll Dec 29 '11 at 11:38
  • Thanks so much for the help. I realised I ahve used Generic Distionarys before, to create JSON output from a database, but I'm really vague on exactly what's going on. Your code looks awesome and is way more than I expected to get from here, so thanks so much. I am lost on how exactly to input the data to the functions though. Given a very simple set of data as in my question, how should I present this to your functions? There are various dictionaryes declared within your code, but I am struggling to see where to input the basic data I begin with?! – Jamie Hartnoll Dec 30 '11 at 15:33
  • Apologies for bad typing in the comment above! :( I really like the weighting idea, and I can understand how that secton of code works, just not sure where to input the data. I am assuming `m_cProductQuantities` is a list of quantity of stock available by location, but not sure how to format that, also, it needs to be quantity by item by location as well, I dont know how to present that as a key/value pair as there's 3 variables... – Jamie Hartnoll Dec 30 '11 at 15:48
  • Ah, `m_cProductQuantities` is the initial collection, so `m_cProductQuantities.Add("itemID", 'quantity')` would make sense with your notes. Not quite seeing how it handles multiple locations. To get the weighting code to work I need to simply pass a list as the `cProductIds` variable? – Jamie Hartnoll Dec 30 '11 at 17:25
  • @JamieHartnoll: Ok, I have revised the answer to include substantially more detail. Hopefully that will get you going. – competent_tech Dec 30 '11 at 17:50
  • Excellent, thanks yet again! As you posted that I *think* I just had a breakthrough and started getting some useable output. With your updated answer I'm sure I'll get the whole thing sorted! :-) – Jamie Hartnoll Dec 30 '11 at 18:44
  • I've had a bit of time now to digest the code and can basically see what's going on, although I can't pretend I completely understand. I have a couple of issues within the `CalculateInventory` function. `wProductId = cProductsToFind.Keys(nI)` and `wProductId = oLocation.ProductQuantities.Keys(nI)` don't work, giving the error "cannot be indexed as it has no default property" Googling it and trying a few variations I can see that as you're cycling in reverse and editing the collection as it goes we cannot use For Each, but without that, the lines above don't work. ... – Jamie Hartnoll Jan 02 '12 at 18:05
  • http://www.pcreview.co.uk/forums/generic-collection-question-t2909057.html this article seemed to suggest a solution, using `System.Collections.ObjectModel.KeyedCollection(Of TKey, TItem)`. There was also a suggestion using `Dim KeyArray(YourDictionary.Keys.Count) as string YourDictionary.Keys.CopyTo(KeyArray) ValueByIndex = YourDictionary(KeyArray(Index))` but I am not sure about this as I'm not sure it will retain the order of items correctly? (Edit, I understand the `KeyArray` option, so if this will retain order, that would be the prefered solution!) – Jamie Hartnoll Jan 02 '12 at 18:07
  • Is the error about cannot be indexed at compile time or run time? – competent_tech Jan 02 '12 at 18:28
  • Visual Web Developer flags it up as an error on the page when editting (blue wibbly underline) and also when run on server it returns the same error without even processing any of the code. (maybe a .NET 2.0 thing?) – Jamie Hartnoll Jan 02 '12 at 18:30
  • Also... although, this might be my fault... having used the `keyarray` method to get round this as per the article mentioned above, I now get an error `index out of range` at the line `oLocationQuantities = Me.Item(wLocationId)` this could well be because converting the collection to an index array hasn't worked? – Jamie Hartnoll Jan 02 '12 at 18:34
  • @JamieHartnoll: I actually just fixed the index out of range error in AddLocationAndQuantityForProduct and another one in AddQuantityForProductId. I have updated the answer with fixes for both. And, using .Net 2.0 is definitely the cause of the keys error. Can you change to .Net 3.5 or 4? – competent_tech Jan 02 '12 at 18:39
  • Ok, I have added a .Net 2.0 version of the calculateinventory method. Basically, we just collect the keys to be deleted, then add a second loop to actually delete them. – competent_tech Jan 02 '12 at 18:43
  • OK, thanks. I am currently bound to .NET 2.0. I'm afraid I am not entirely sure why, but my hosting company tells me that the current set of server machines running the hosting packages I have are .NET 2.0 only. I intend to move to a dedicated server eventually, which will definitely be upgraded, but for the time being, no, I am afraid I can't change to 3.5 or 4.0 :-( – Jamie Hartnoll Jan 02 '12 at 18:47
  • That's ok, the rewritten CalculateInventory will work fine on .Net 2.0. – competent_tech Jan 02 '12 at 18:50
  • That's awesome. Thanks. It's now all working! So just to clarify. The functions fired on button click; `LoadLocationsAndQuantities()` the values declared in that function are to be the stock levels AVAILABLE from my database? `DoInventoryCalculation()` calculates the availability of stock item REQUESTED by an order form? (in this sample case, just productID 1) so in order to use this in real life I need to populate those values from my database and run `DoInventoryCalculation()` for every item requested by the sale? – Jamie Hartnoll Jan 02 '12 at 19:07
  • Excellent! I have a minor issue though! (sorry!) If I make `dQuantity = 0` for any location or item in `LoadLocationsAndQuantities()` I get "None of the requested products could be found at any location." Having isolated the logic, it's `cLocationsAndProducts.Count = 0` which is `True` and thus generating the output. EDIT: (I am dealing with just productID 1 at the moment, having set up 3 locations, with differing stock qtys, if any of them = 0 I can't pick from any location) – Jamie Hartnoll Jan 02 '12 at 19:19
  • Ah, the `CompareTo` logic in `LocationQuantities` is backwards: change it to `Return .Weight.CompareTo(Me.Weight)`. I have updated the answer. – competent_tech Jan 02 '12 at 19:27
  • Next problem that I'll encounter is; what if more than one of any item is requested? I can see we're checking stock is not 0, but if 2 of any item are requested & only 1 is available in one location, but 2 are available from another location we might return an erroneous stock pick. In `DoInventoryCalculation()` I need to change `cProductIds` to a dictionary not a list so it can pass the item ID & qty required to `CalculateInventory` then to make this work I'll also need to alter the `weight` function where it cycles, I *think* I can handle this myself thanks to you, but am I missing anything? – Jamie Hartnoll Jan 02 '12 at 19:48
  • Oh, and Yep, I'm happy to hit "Accept" now! Your help has been invaluable on this one! :) – Jamie Hartnoll Jan 02 '12 at 19:48
  • You are definitely on the right track. The one wrinkle is what happens if 2 are requested, but only 1 is available at each location. You will probably need a second loop that goes through the remaining productstofind (i.e. those that weren't satisfied 100% from a single location) and see if there are a combination of locations that can fulfill the order. The other alternative (that would be somewhat messier) is to make 1 request for each product quantity (i.e. make 2 requests if 2 items were ordered). This would definitely be easier to implement, but feels wrong somehow. – competent_tech Jan 02 '12 at 19:55
  • Hmm. The second option would be easiest to implement, although untidy, but I am not sure if it'd create some disorganised pick selections. I'll have a play around. Given fairly light useage it might not be "correct" but if I don't get funny pick selections that method could work for this application. – Jamie Hartnoll Jan 02 '12 at 20:11
  • Just to update you, I've got it all working as required now! Adding the required quantity to the `DoInventoryCalculation()` function worked with various other modifications. As you sugested, I had to run some extra logic to create a compilation of pick locations where required. I now have it outputing a list of items, stock locations and the quantities to dispatch from each location. Final stage is to deal with a situation where more stock is required than available, but I can handle that pretty simply! Excellent, thanks so much for the outstanding help! Jamie – Jamie Hartnoll Jan 03 '12 at 20:46