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

Fix wrong calculation of available stock during completing a payment #16307

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@managing_orders
Feature: Being unable to finalize order's payment when at least one item has become tracked after the purchase
In order to mark order's payment state as complete when there is a sufficient reserved stock
As an Administrator
I want to be able to finalize payment only when there is a sufficient reserved stock

Background:
Given the store operates on a single channel in "United States"
And the store ships everywhere for Free
And the store allows paying with "Cash on Delivery"
And the store has a product "PHP T-Shirt"
And there is a customer "john@example.com" that placed an order "#00000001"
And the customer bought 5 "PHP T-Shirt" products
And the customer "John Doe" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
And the customer chose "Free" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator

@api @ui
Scenario: Being unable to finalize order's payment when one item has become tracked after the purchase
Given I am viewing the summary of the order "#00000001"
And the "PHP T-Shirt" product's inventory has become tracked with 2 items
When I mark this order as paid
Then I should be notified that the order's payment could not be finalized due to insufficient stock
And it should have payment state "New"

@api @ui
Scenario: Being unable to finalize order's payment when one item has become tracked after the purchase
Given I am viewing the summary of the order "#00000001"
And the "PHP T-Shirt" product's inventory has become tracked with 6 items
When I mark this order as paid
Then I should be notified that the order's payment could not be finalized due to insufficient stock
And it should have payment state "New"
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
@managing_orders
Feature: Finalizing order payment
Feature: Finalizing order's payment with untracked items
In order to mark order's payment state as complete
As an Administrator
I want to be able to finalize payment

Background:
Given the store operates on a single channel in "United States"
And the store has a product "Angel T-Shirt"
And the store ships everywhere for Free
And the store allows paying with "Cash on Delivery"
And there is a customer "lucy@teamlucifer.com" that placed an order "#00000666"
And the customer bought 5 "Angel T-Shirt" products
And the customer "Lucifer Morningstar" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
And the store has a product "PHP T-Shirt"
And there is a customer "john@example.com" that placed an order "#00000001"
And the customer bought 5 "PHP T-Shirt" products
And the customer "John Doe" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
And the customer chose "Free" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator

@ui
@api @ui
Scenario: Finalizing order's payment
Given I view the summary of the order "#00000666"
Given I am viewing the summary of the order "#00000001"
When I mark this order as paid
Then I should be notified that the order's payment has been successfully completed
And it should have payment state "Completed"

@ui
Scenario: Finalizing order's payment when at least one item has become tracked after the purchase
Given I view the summary of the order "#00000666"
And the "Angel T-Shirt" product's inventory has become tracked with 2 items
When I mark this order as paid
Then I should be notified that the order's payment could not be finalized due to insufficient stock
And it should have payment state "New"

@ui
Scenario: Unable to finalize completed order's payment
@api @ui
Scenario: Being unable to finalize completed order's payment
Given this order is already paid
When I view the summary of the order "#00000666"
Then I should not be able to mark this order as paid again

@ui
Scenario: Checking the payment state of a completed order
Given this order is already paid
When I browse orders
Then this order should have order payment state "Paid"
When I view the summary of the order "#00000001"
And it should have payment state "Completed"
And I should not be able to mark this order as paid again
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@managing_orders
Feature: Finalizing order's payment with tracked items
In order to mark order's payment state as complete when there is a sufficient stock
As an Administrator
I want to be able to finalize payment

Background:
Given the store operates on a single channel in "United States"
And the store ships everywhere for Free
And the store allows paying with "Cash on Delivery"
And the store has a product "PHP T-Shirt"
And there are 3 units of tracked product "PHP T-Shirt" available in the inventory
And there is a customer "john@example.com" that placed an order "#00000001"
And the customer bought 2 "PHP T-Shirt" products
And the customer "John Doe" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
And the customer chose "Free" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator

@api @ui
Scenario: Finalizing order's payment
Given I am viewing the summary of the order "#00000001"
When I mark this order as paid
Then I should be notified that the order's payment has been successfully completed
And it should have payment state "Completed"
34 changes: 34 additions & 0 deletions src/Sylius/Behat/Context/Api/Admin/ManagingOrdersContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function __construct(
/**
* @Given /^I am viewing the summary of (this order)$/
* @When I view the summary of the order :order
* @Given I am viewing the summary of the order :order
*/
public function iSeeTheOrder(OrderInterface $order): void
{
Expand Down Expand Up @@ -311,4 +312,37 @@ public function theAdministratorShouldSeeThatThisOrderHasBeenPlacedIn(

Assert::same($currencyCode, $currency);
}

/**
* @Then I should be notified that the order's payment has been successfully completed
*/
public function iShouldBeNotifiedThatTheOrdersPaymentHasBeenSuccessfullyCompleted(): void
{
Assert::true($this->responseChecker->isUpdateSuccessful($this->client->getLastResponse()));
}

/**
* @Then /^I should not be able to mark (this order) as paid again$/
*/
public function iShouldNotBeAbleToMarkThisOrderAsPaidAgain(OrderInterface $order): void
{
$this->client->applyTransition(
Resources::PAYMENTS,
(string) $order->getLastPayment()->getId(),
PaymentTransitions::TRANSITION_COMPLETE,
);

Assert::false($this->responseChecker->isUpdateSuccessful($this->client->getLastResponse()));
}

/**
* @Then I should be notified that the order's payment could not be finalized due to insufficient stock
*/
public function iShouldBeNotifiedThatTheOrdersPaymentCouldNotBeFinalizedDueToInsufficientStock(): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
'Not enough units to decrease on hold quantity from the inventory of a variant',
);
}
}
36 changes: 24 additions & 12 deletions src/Sylius/Behat/Context/Setup/ProductContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -620,26 +620,25 @@ public function thisProductHasAnOptionWithoutAnyValues(ProductInterface $product
/**
* @Given /^there (?:is|are) (\d+) unit(?:|s) of (product "([^"]+)") available in the inventory$/
*/
public function thereIsQuantityOfProducts($quantity, ProductInterface $product)
public function thereIsQuantityOfProductAvailableInTheInventory(int $quantity, ProductInterface $product): void
{
/** @var ProductVariantInterface $productVariant */
$productVariant = $this->defaultVariantResolver->getVariant($product);
$productVariant->setOnHand((int) $quantity);
$this->updateOnHand($product, $quantity);
}

$this->objectManager->flush();
/**
* @Given /^there (?:is|are) (\d+) unit(?:|s) of tracked (product "([^"]+)") available in the inventory$/
*/
public function thereIsQuantityOfTrackedProductAvailableInTheInventory(int $quantity, ProductInterface $product): void
{
$this->updateOnHand($product, $quantity, true);
}

/**
* @Given /^the (product "([^"]+)") is out of stock$/
*/
public function theProductIsOutOfStock(ProductInterface $product)
public function theProductIsOutOfStock(ProductInterface $product): void
{
/** @var ProductVariantInterface $productVariant */
$productVariant = $this->defaultVariantResolver->getVariant($product);
$productVariant->setTracked(true);
$productVariant->setOnHand(0);

$this->objectManager->flush();
$this->updateOnHand($product, 0, true);
}

/**
Expand Down Expand Up @@ -1393,4 +1392,17 @@ private function createProductTaxon(TaxonInterface $taxon, ProductInterface $pro

return $productTaxon;
}

private function updateOnHand(ProductInterface $product, int $onHand, ?bool $tracked = null): void
{
/** @var ProductVariantInterface $productVariant */
$productVariant = $this->defaultVariantResolver->getVariant($product);
$productVariant->setOnHand($onHand);

if ($tracked !== null) {
$productVariant->setTracked($tracked);
}

$this->objectManager->flush();
}
}
9 changes: 5 additions & 4 deletions src/Sylius/Behat/Context/Ui/Admin/ManagingOrdersContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ public function iBrowseOrderHistory(OrderInterface $order)

/**
* @Given /^I am viewing the summary of (this order)$/
* @Given I am viewing the summary of the order :order
* @When I view the summary of the order :order
*/
public function iSeeTheOrder(OrderInterface $order)
public function iViewTheSummaryOfTheOrder(OrderInterface $order): void
{
$this->showPage->open(['id' => $order->getId()]);
}
Expand Down Expand Up @@ -273,7 +274,7 @@ public function itShouldBeShippedTo(
string $countryName,
) {
if (null !== $order) {
$this->iSeeTheOrder($order);
$this->iViewTheSummaryOfTheOrder($order);
}

Assert::true($this->showPage->hasShippingAddress($customerName, $street, $postcode, $city, $countryName));
Expand Down Expand Up @@ -305,7 +306,7 @@ public function itShouldBeBilledTo(
string $countryName,
) {
if (null !== $order) {
$this->iSeeTheOrder($order);
$this->iViewTheSummaryOfTheOrder($order);
}

Assert::true($this->showPage->hasBillingAddress($customerName, $street, $postcode, $city, $countryName));
Expand Down Expand Up @@ -534,7 +535,7 @@ public function iShouldBeNotifiedThatTheOrderSPaymentHasBeenSuccessfullyComplete
/**
* @Then I should be notified that the order's payment could not be finalized due to insufficient stock
*/
public function iShouldBeNotifiedThatTheOrderSPaymentCouldNotBeFinalizedDueToInsufficientStock()
public function iShouldBeNotifiedThatTheOrdersPaymentCouldNotBeFinalizedDueToInsufficientStock(): void
{
$this->notificationChecker->checkNotification(
'The payment cannot be completed due to insufficient stock of the',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ api_platform:
Sylius\Bundle\ApiBundle\Exception\ProvinceCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\ShippingMethodCannotBeRemoved: 422
Sylius\Bundle\ApiBundle\Exception\ZoneCannotBeRemoved: 422
Sylius\Component\Core\Inventory\Exception\NotEnoughUnitsOnHandException: 422
Sylius\Component\Core\Inventory\Exception\NotEnoughUnitsOnHoldException: 422
NoResponseMate marked this conversation as resolved.
Show resolved Hide resolved
Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException: 400
collection:
pagination:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@

use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;

final class PaymentPreCompleteListener
{
public function __construct(
private AvailabilityCheckerInterface $availabilityChecker,
) {
}

public function checkStockAvailability(ResourceControllerEvent $event): void
{
/** @var PaymentInterface $payment */
Expand All @@ -33,7 +28,7 @@ public function checkStockAvailability(ResourceControllerEvent $event): void
foreach ($orderItems as $orderItem) {
$variant = $orderItem->getVariant();

if (!$this->availabilityChecker->isStockSufficient($variant, $orderItem->getQuantity())) {
if (!$this->isStockSufficient($variant, $orderItem->getQuantity())) {
$event->setMessageType('error');
$event->setMessage('sylius.resource.payment.cannot_be_completed');
$event->setMessageParameters(['%productVariantCode%' => $variant->getCode()]);
Expand All @@ -43,4 +38,16 @@ public function checkStockAvailability(ResourceControllerEvent $event): void
}
}
}

private function isStockSufficient(ProductVariantInterface $variant, int $quantity): bool
{
if (!$variant->isTracked()) {
return true;
}

return
$variant->getOnHold() - $quantity >= 0 &&
$variant->getOnHand() - $quantity >= 0
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
</service>

<service id="Sylius\Bundle\CoreBundle\EventListener\PaymentPreCompleteListener">
<argument type="service" id="Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface" />
<tag name="kernel.event_listener" event="sylius.payment.pre_complete" method="checkStockAvailability" />
</service>

Expand Down