If you haven't seen The 15 minute E-Commerce Site CLICK HERE
I'm currently looking for Contract jobs, Contact Me if you are interested. Dave.
E-commerce Tips 1.3 (Inventory) Jul 06
Tracking inventory can be much trickier than most people imagine. I've seen several different solutions. While many can work with very few purchases others are seriously flawed at scale. Lets go through a few solutions.
The first solution and for some reason the solution I see most goes like this.
@order.pay!
@order.items.each do |item|
item.product.remove_from_inventory!(item.quantity)
end
class Product
def remove_from_inventory!(qty)
self.quantity = quantity - qty
save
end
end
Unfortunately there is a lot going wrong above. The first issue has to do with the fact that the inventory is stored in the product class. Inventory and products need to be broken down into separate class. The fact is that they are not the same. The product class should not for more responsibility than it should know about.
Now we can refactor the code to look like this:
@order.pay!
@order.items.each do |item|
item.product.remove_from_inventory!(item.quantity)
end
class Product
def remove_from_inventory!(qty)
inventory.remove_items(qty)
end
end
class Inventory
def remove_items(qty)
self.quantity = quantity - qty
save
end
end
So now you have classes that have one responsibility but there is another glaring issue. The math is actually not accurate if you have 2 customers purchase at the same time.
For Example: Lets say order_one and order_two purchase the same item at the same time. This means the inventory object for each order will have the same quantity (let just say it's 5). Now when inventory is updated the first time the quantity will reduce to correctly. The second inventory object still thinks the quantity is 5 though. So when it updates inventory the quantity will still be 4 (if one item is purchased).
This brings us to yet another solution. Lets do the math in the database.
@order.pay!
@order.items.each do |item|
item.product.remove_from_inventory!(item.quantity)
end
class Product
def remove_from_inventory!(qty)
inventory.remove_items(qty)
end
end
class Inventory
def remove_items(qty)
sql = "UPDATE inventories
SET quantity = (quantity - #{qty})
WHERE id = #{self.id}"
ActiveRecord::Base.connection.execute(sql)
end
end
This starts to help manage the problem better but there are still some issues. The issue that needs to be dealt with next is what should happen when there is only a couple items left to purchase? With the above solution the database would now at least reflect the correct number of items. The problem is the number could be negative if two people purchase at the same time.
This is where business needs will probably determine the correct solution. Unfortunately, I'm not going to give you all teh solutions right now. One solution might be to have your database return an error if the quantity goes below 0. Then you would need an appropriate error handler. Another solution might be this:
class Inventory
has_many :inventory_items
end
This illustrates that each inventory item has it's own database record. Now if the item is in your cart you are the only one allowed to purchase the item. Again, you will need some business logic that will free the items just in case the customer doesn't make the purchase but with this solution there isn't a way to go below zero items in stock.
Yet another solution might be to have a safety stock level. This would mean when you have less than X number of items in stock people aren't allowed to purchase. That isn't a perfect solution if you do want to sell out but it does solve the issue with over selling.
Two more glaring issue before I move on. This all needs to be wrapped in a transaction. Thus if there is an item with collecting the money the inventory doesn't change and vice versa.
transaction do
@order.pay!
@order.items.each do |item|
item.product.remove_from_inventory!(item.quantity)
end
end
Almost done... Now lets move the @order.pay! option to the end of the transaction. Otherwise if there is an issue with inventory (or anything else) the order will not be charged. Likewise if there is an issue with paying the whole transaction will be reverted back.
transaction do
@order.items.each do |item|
item.product.remove_from_inventory!(item.quantity)
end
@order.pay!
end
Quantity isn't enough
Now at a high level you should have an understanding of inventory quantities being accurate and I hope you can improve your code at this level. However another big issue we have is quantity is not a good gauge of what is really happening. Quantity available to sell and Quantity in stock might reflect 2 separate numbers.
For example:
- You have 10 items in stock
- One item is purchased online.
- The amount you can sell has reduced
- BUT the amount in stock is still 10 until the item ships
So in reality the Inventory class should have the following columns:
- count_in_stock
- count_pending_to_customer
- count_pending_from_supplier (optional)
Now the quantity is just
class Inventory
def quantity_available_to_sell
count_in_stock - count_pending_to_customer
end
end
Well there is still much more to learn about inventory. This might justify creating a new post in the future. I hope you have taken away something useful. Thanks for reading.

7 comments so far
Tony Collen 07 Jul 12
DRH 07 Jul 12
CB 07 Jul 12
Tony Collen 07 Jul 12
SET quantity = (#{num} + quantity)Shouldn't "num" be "qty"? Or maybe even "-qty"?Alex 11 Jul 12
DRH 11 Jul 12
Gary 14 Jul 12
Post a comment