Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make AvailabilityValidator check inventory per StockLocation instead of per Shipment #1693

Merged
merged 8 commits into from
Feb 27, 2017

Conversation

jhawthorn
Copy link
Contributor

@jhawthorn jhawthorn commented Jan 26, 2017

This fixes a few issues with AvailabilityValidator

Stock was being checked per Shipment rather than per StockLocation. This meant that available inventory could be double counted towards the same order. Quantities is now checked per StockLocation.

If multiple Shipments (or now StockLocations) on one LineItem had insufficient inventory, the same error would be added multiple times to the line item. ex. ["Quantity selected of \"Product #20 - 5904 (Size: S)\" is not available.", "Quantity selected of \"Product #20 - 5904 (Size: S)\" is not available."]

One Shipment record was being queried and loaded for each inventory attached to the line item, which is quite slow and unnecessary. This has been replaced with a single query per LineItem.

Before:

> Spree::Stock::AvailabilityValidator.new.validate(line_item)
SELECT "spree_inventory_units".* FROM "spree_inventory_units" WHERE "spree_inventory_units"."line_item_id" = ?  [["line_item_id", 4]]
SELECT  "spree_shipments".* FROM "spree_shipments" WHERE "spree_shipments"."id" = ? LIMIT ?  [["id", 8], ["LIMIT", 1]]
SELECT  "spree_shipments".* FROM "spree_shipments" WHERE "spree_shipments"."id" = ? LIMIT ?  [["id", 8], ["LIMIT", 1]]
SELECT  "spree_shipments".* FROM "spree_shipments" WHERE "spree_shipments"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]
SELECT  "spree_shipments".* FROM "spree_shipments" WHERE "spree_shipments"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]
SELECT  "spree_stock_locations".* FROM "spree_stock_locations" WHERE "spree_stock_locations"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
SELECT  "spree_variants".* FROM "spree_variants" WHERE "spree_variants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
SELECT SUM("spree_stock_items"."count_on_hand") FROM "spree_stock_items" INNER JOIN "spree_stock_locations" ON "spree_stock_locations"."id" = "spree_stock_items"."stock_location_id" WHERE "spree_stock_items"."deleted_at" IS NULL AND "spree_stock_items"."variant_id" = 1 AND "spree_stock_items"."stock_location_id" = 1
SELECT  "spree_stock_locations".* FROM "spree_stock_locations" WHERE "spree_stock_locations"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
SELECT SUM("spree_stock_items"."count_on_hand") FROM "spree_stock_items" INNER JOIN "spree_stock_locations" ON "spree_stock_locations"."id" = "spree_stock_items"."stock_location_id" WHERE "spree_stock_items"."deleted_at" IS NULL AND "spree_stock_items"."variant_id" = 1 AND "spree_stock_items"."stock_location_id" = 1

After:

> Spree::Stock::AvailabilityValidator.new.validate(line_item)
SELECT  1 AS one FROM "spree_inventory_units" WHERE "spree_inventory_units"."line_item_id" = ? LIMIT ?  [["line_item_id", 4], ["LIMIT", 1]]
SELECT COUNT(*) AS count_all, "stock_location_id" AS stock_location_id FROM "spree_inventory_units" INNER JOIN "spree_shipments" ON "spree_shipments"."id" = "spree_inventory_units"."shipment_id" WHERE "spree_inventory_units"."line_item_id" = ? AND "spree_inventory_units"."pending" = ? GROUP BY "stock_location_id"  [["line_item_id", 4], ["pending", true]]
SELECT  "spree_variants".* FROM "spree_variants" WHERE "spree_variants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
SELECT SUM("spree_stock_items"."count_on_hand") FROM "spree_stock_items" WHERE "spree_stock_items"."deleted_at" IS NULL AND "spree_stock_items"."variant_id" = 1 AND "spree_stock_items"."stock_location_id" = 1
SELECT "spree_stock_items".* FROM "spree_stock_items" WHERE "spree_stock_items"."deleted_at" IS NULL AND "spree_stock_items"."variant_id" = 1 AND "spree_stock_items"."stock_location_id" = 1

@@ -54,39 +54,85 @@ module Stock
context "line_item is part of a shipment" do
let!(:order) { create(:order_with_line_items) }

context "has stock in all stock locations" do
context "has stock in one stock locations" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: "location"

@mamhoff
Copy link
Contributor

mamhoff commented Feb 24, 2017

This is really neat. However, I see some commits do not pass CI. :(

@jhawthorn jhawthorn force-pushed the availability_validator branch from 57210f6 to f0777d4 Compare February 27, 2017 19:56
@jhawthorn
Copy link
Contributor Author

This is really neat. However, I see some commits do not pass CI. :(

I believe that was just the second to last commit which I've rebased and amended the last commit fixing the spec failure. I'm running a git rebase -x rspec locally to double check

We can use ActiveRecord to build queries instead of a hash, which allows
us to reuse StockLocation.active and makes it a little more readable.
These specs exibit two issues when a line item is split across multiple
shipments.

If the line item is split across shipments, any availabilty errors will
be reported twice.

If a shipment is split across shipments with the same stock location, it
won't detect insufficient stock if both shipments are able to separately
fulfill the inventory.
Previously stock was checked per-shipment which could lead to incorrect
results as the same inventory from one stock location could be
double-counted across multiple shipments.

This also greatly reduces the number of queries, by replacing one
Shipment load per inventory_unit with a single query to get the quantity
of inventory units per stock location.
We can avoid selecting StockLocation records by just passing their id to
Stock::Quantifier
Instead of relying on the behaviour of stock_location_quantities and the
entire order state machine, we should just create shipments and
inventory units in the state we expect.

This adds tests for the line item needing some stock from each stock
location.

This adds one skipped test which fails because the error on the line
item is added twice.
We shouldn't report the same error twice on the same item.
@jhawthorn jhawthorn force-pushed the availability_validator branch from f0777d4 to a2e4ad6 Compare February 27, 2017 21:22
@jhawthorn jhawthorn changed the title Make InventoryValidator check inventory per StockLocation instead of per Shipment Make AvailabilityValidator check inventory per StockLocation instead of per Shipment Feb 27, 2017
@jhawthorn jhawthorn merged commit fb70a41 into solidusio:master Feb 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants