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