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

[Inventory] Holding inventory units during checkout #6033

Merged
merged 4 commits into from
Sep 16, 2016

Conversation

Arminek
Copy link
Contributor

@Arminek Arminek commented Sep 8, 2016

Q A
Bug fix? no
New feature? yes
BC breaks? no
Related tickets
License MIT

@Arminek Arminek changed the title [Inventory] Holding inventory units during checkout [WIP][Inventory] Holding inventory units during checkout Sep 8, 2016
@gabiudrescu
Copy link
Contributor

are you sure you want to do this as a standard behavior of Sylius? because reserving the stock of a product for the user only for adding it to cart is a pretty big commitment, from business point of view.

also, it's not clear for me from these scenarios how the units get released? only by acquisition? how can I, as an admin, cancel these reservations?

{
foreach ($order->getItems() as $orderItem) {
/** @var OrderItemInterface $orderItem */
/** @var ProductVariantInterface $variant */
Copy link
Member

Choose a reason for hiding this comment

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

Is redundant, as getVariant is typehinted.

@Arminek Arminek force-pushed the inventory-hold branch 3 times, most recently from d6b9f09 to 4a4231c Compare September 13, 2016 17:58
@Arminek Arminek changed the title [WIP][Inventory] Holding inventory units during checkout [Inventory] Holding inventory units during checkout Sep 13, 2016
@michalmarcinkowski
Copy link
Contributor

@gabiudrescu we decided to have the following default behavior:

  • Reserve inventory when order is placed (checkout completed)
  • When the order is cancelled the reserved units are released (to be implemented in coming weeks)
  • After the order is paid the inventory is decreased

Later we will think about mechanisms like releasing reserved units when order is not paid after X minutes, but right now it complicates a lot. E.g. you can have offline payment method (e.g. cash on delivery) and you don't want to release the reserved units after X minutes. What is more we need to figure out what will happen when the reserved units are released and after that someone will pay for the order that have products which are out of stock... Let's discuss that later :)

Copy link
Contributor

@michalmarcinkowski michalmarcinkowski left a comment

Choose a reason for hiding this comment

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

We should figure out better naming for these services.

Scenario: Holding inventory units
Given this user has added 3 products "Iron Maiden T-Shirt" to the cart
And this user bought those products
When I view all variants of the product "Iron Maiden T-Shirt"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that When I view variants of product "Iron Maiden T-Shirt" will be more appropriate step.

Given this user has added 3 products "Iron Maiden T-Shirt" to the cart
And this user bought those products
When I view all variants of the product "Iron Maiden T-Shirt"
Then I should know that 3 units of this product is hold
Copy link
Contributor

@michalmarcinkowski michalmarcinkowski Sep 14, 2016

Choose a reason for hiding this comment

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

Then 3 units of this product should be on hold
And 5 units of this product should be on hand

Scenario: Release hold units after order has been paid
Given this user has added 3 products "Iron Maiden T-Shirt" to the cart
And this user bought this product
When I view the summary of this order made by "sylius@example.com"
Copy link
Contributor

Choose a reason for hiding this comment

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

When I view the summary of this order

When I view the summary of this order made by "sylius@example.com"
And I mark the order of "sylius@example.com" as a paid
And I view all variants of the product "Iron Maiden T-Shirt"
Then I should not know about on hold quantity of this product
Copy link
Contributor

Choose a reason for hiding this comment

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

Then 2 units of this product should be on hand
And there should be no units of this product on hold

Copy link
Contributor

Choose a reason for hiding this comment

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

@michalmarcinkowski And there should be no units of this product on hold?

Given this user has added 3 products "Iron Maiden T-Shirt" to the cart
And this user bought this product
When I view the summary of this order made by "sylius@example.com"
And I mark the order of "sylius@example.com" as a paid
Copy link
Contributor

Choose a reason for hiding this comment

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

... as a paid


return $quantity === $onHandQuantity;
} catch (ElementNotFoundException $exception) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we return false when the element is not on page?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO in those kind of methods yes.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should return false only if that element is not rendered if on hand quantity is equal to 0.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at src/Sylius/Bundle/AdminBundle/Resources/views/ProductVariant/Field/inventory.html.twig we can see, that those elements are always rendered, so we should not catch that exception.

/**
* @author Arkadiusz Krakowiak <arkadiusz.krakowiak@lakion.com>
*/
final class PaidOrderInventoryHandler implements PaidOrderInventoryHandlerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the name of the class is coupled with order state (PaidOrder)?


public function __construct()
{
$this->decreasingQuantityUpdaters = new PriorityQueue();
Copy link
Contributor

Choose a reason for hiding this comment

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

Another composite? Looks like overhead for this use case...

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed.

When I view the summary of this order made by "sylius@example.com"
And I mark the order of "sylius@example.com" as a paid
And I view all variants of the product "Iron Maiden T-Shirt"
Then I should not know about on hold quantity of this product
Copy link
Contributor

Choose a reason for hiding this comment

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

@michalmarcinkowski And there should be no units of this product on hold?

@@ -170,7 +170,7 @@ public function iAmAtTheCheckoutAddressingStep()
}

/**
* @Given /^(this user) bought this product$/
* @Given /^(this user) bought (?:this|those) product(?:|s)$/
Copy link
Contributor

Choose a reason for hiding this comment

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

(?:this product|those products)?

@@ -276,6 +276,16 @@ public function thisUserHasProductInTheCart(ShopUserInterface $user, ProductInte
}

/**
* @Given /^(this user) has(?:| added) (\d+) (products "[^"]+") (?:to|in) the cart$/
Copy link
Contributor

@pamil pamil Sep 15, 2016

Choose a reason for hiding this comment

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

What about expanding this regexp?

/^(this user) has (\d+) (products "[^"]+") in the cart$/
/^(this user) added (\d+) (products "[^"]+") to the cart$/


return $quantity === $onHandQuantity;
} catch (ElementNotFoundException $exception) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should return false only if that element is not rendered if on hand quantity is equal to 0.


return $quantity === $onHoldQuantity;
} catch (ElementNotFoundException $exception) {
return false;
Copy link
Contributor

@pamil pamil Sep 15, 2016

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Should throw exception when element not found.


public function __construct()
{
$this->decreasingQuantityUpdaters = new PriorityQueue();
Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed.

foreach ($this->decreasingQuantityUpdaters as $decreasingQuantityUpdater) {
try {
$decreasingQuantityUpdater->decrease($order);
} catch (\InvalidArgumentException $exception) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It shouldn't catch general exceptions.

public function handle(OrderInterface $order)
{
if ($this->decreasingQuantityUpdaters->isEmpty()) {
throw new HandleException(self::class, 'There is no decreasing updaters to handle this request.');
Copy link
Contributor

Choose a reason for hiding this comment

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

HandleException says nothing, as we can get a stacktrace from any exception, but it does not say what came wrong.

/**
* @author Arkadiusz Krakowiak <arkadiusz.krakowiak@lakion.com>
*/
interface PaidOrderInventoryHandlerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

PaidOrder -> Order

/**
* @author Arkadiusz Krakowiak <arkadiusz.krakowiak@lakion.com>
*/
interface PaidOrderInventoryHandlerInterface
Copy link
Contributor

@pamil pamil Sep 15, 2016

Choose a reason for hiding this comment

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

Anyway, Handler seems to be a too general name. What should it do?

@@ -16,7 +16,7 @@
/**
* @author Anna Walasek <anna.walasek@lakion.com>
*/
interface OnHandQuantityUpdaterInterface
interface DecreasingQuantityUpdaterInterface
Copy link
Contributor

@pamil pamil Sep 15, 2016

Choose a reason for hiding this comment

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

Why is it Decreasing? What should it do?

/**
* @author Arkadiusz Krakowiak <arkadiusz.krakowiak@lakion.com>
*/
interface IncreasingQuantityUpdaterInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems redundant, has one method with one argument, same as DecreasingQuantity..., why won't you merge them?

$orderItem->getQuantity()->willReturn(10);
$orderItems = new ArrayCollection();
$orderItems->add($orderItem->getWrappedObject());
$order->getItems()->willReturn($orderItems);
Copy link
Contributor

Choose a reason for hiding this comment

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

$order->getItems()->willReturn([$orderItem]);

instead of these three lines.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same for every ArrayCollection in specs.

Copy link
Contributor

@pamil pamil left a comment

Choose a reason for hiding this comment

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

GitHub does not allow to add request changes review without any text here...

@Arminek Arminek force-pushed the inventory-hold branch 2 times, most recently from f613632 to 049455b Compare September 15, 2016 13:40
public function thereShouldBeNoUnitsOfThisProductOnHold(ProductInterface $product)
{
Assert::false(
$this->indexPage->hasOnHoldQuantity($product->getFirstVariant(), 1),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why checking for 1? If 0 is not displayed we ca simply check if there is onHold label or not.


return $quantity === $onHoldQuantity;
} catch (ElementNotFoundException $exception) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should throw exception when element not found.

$variant = $orderItem->getVariant();

if ($variant->isTracked()) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Redundant

) {
$productVariant->getOnHold()->willReturn(10);
$productVariant->isTracked()->willReturn(true);
$productVariant->getName()->willReturn('t-shirt');
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need it?

) {
$productVariant->getOnHold()->willReturn(5);
$productVariant->isTracked()->willReturn(true);
$productVariant->getName()->willReturn('t-shirt');
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep we are using Assert library so i need this to pass information about the name of variant before the exception will be thrown.

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI for name we should use something like "Start wars T-Shirt" to make it closer to real data :)

$productVariant->setOnHold(5)->shouldBeCalled();

$this->decrease($order);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing spec for decreasing onHold of two different items.

*
* @return bool
*/
public function hasOnHoldLabelFor(ProductVariantInterface $productVariant);
Copy link
Contributor

Choose a reason for hiding this comment

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

This method should be removed.

What do you think about refactoring hasOn*QuantityFor(ProductVariantInterface $productVariant, $quantity) : bool to getOn*QuantityFor(ProductVariantInterface $productVariant) : int and comparing it in the context?

This way we can also show nicer exceptions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this separates templating logic (do not show onHold if onHold = 0) as the other shops may implement it differently.

@@ -1,10 +1,10 @@
{% if data.isTracked %}
<div class="ui {{ data.onHand > 0 ? 'green' : 'red' }} icon label">
<i class="cube icon"></i>
{{ data.onHand }} {{ 'sylius.ui.available_on_hand'|trans }}
<span id="sylius-{{ data.id }}-on-hand-quantity">{{ data.onHand }}</span> {{ 'sylius.ui.available_on_hand'|trans }}
Copy link
Contributor

Choose a reason for hiding this comment

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

id="sylius-{{ data.id }}-on-hand-quantity" -> class="sylius-on-hand-quantity" data-product-variant-id="{{ data.id }}"? These non-semantic IDs made only for Behat looks quite bad to me :/

@Arminek Arminek force-pushed the inventory-hold branch 4 times, most recently from ba12d2c to 95a777d Compare September 16, 2016 09:56
@michalmarcinkowski michalmarcinkowski merged commit 03723d3 into Sylius:master Sep 16, 2016
@michalmarcinkowski
Copy link
Contributor

Good job Arek! 👍

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.

None yet

6 participants