Skip to content

Commit

Permalink
Merge branch '1.13' into 1.14
Browse files Browse the repository at this point in the history
* 1.13:
  [Inventory] Add a BC layer for refactored PaymentPreCompleteListener
  Add a note to UPGRADE file about changing PaymentPreCompleteListener's constructor
  [Inventory] Extract service for checking reserved stock availability for given order item
  • Loading branch information
GSadee committed Jun 5, 2024
2 parents cbcbdd3 + 6a3be65 commit 9420df1
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 8 deletions.
14 changes: 14 additions & 0 deletions UPGRADE-1.12.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# UPGRADING FROM `v1.12.16` TO `v1.12.17`

1. Due to a bug that was causing wrong calculation of available stock during completing a payment [REF](https://github.com/Sylius/Sylius/issues/16160),
The constructor of `Sylius\Bundle\CoreBundle\EventListener\PaymentPreCompleteListener` has been modified as follows:

```diff
public function __construct(
+ private OrderItemAvailabilityCheckerInterface|AvailabilityCheckerInterface $availabilityChecker,
- private AvailabilityCheckerInterface $availabilityChecker,
)
```

If you have overwritten the service or its argument, check the correct functioning.

# UPGRADING FROM `v1.12.13` TO `v1.12.14`

1. The `Accept-Language` header should now correctly resolve locale codes based on RFC 4647 using Symfony's request language negotiation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@
namespace Sylius\Bundle\CoreBundle\EventListener;

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

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

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

if ($this->availabilityChecker instanceof OrderItemAvailabilityCheckerInterface) {
if (!$this->availabilityChecker->isReservedStockSufficient($orderItem)) {
$this->stopEvent($event, $variant->getCode());

break;
}

continue;
}

if (!$this->isStockSufficient($variant, $orderItem->getQuantity())) {
$event->setMessageType('error');
$event->setMessage('sylius.resource.payment.cannot_be_completed');
$event->setMessageParameters(['%productVariantCode%' => $variant->getCode()]);
$event->stopPropagation();
$this->stopEvent($event, $variant->getCode());

break;
}
Expand All @@ -50,4 +64,12 @@ private function isStockSufficient(ProductVariantInterface $variant, int $quanti
$variant->getOnHand() - $quantity >= 0
;
}

private function stopEvent(ResourceControllerEvent $event, string $variantCode): void
{
$event->setMessageType('error');
$event->setMessage('sylius.resource.payment.cannot_be_completed');
$event->setMessageParameters(['%productVariantCode%' => $variantCode]);
$event->stopPropagation();
}
}
3 changes: 3 additions & 0 deletions src/Sylius/Bundle/CoreBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
</service>
<service id="Sylius\Component\Customer\Context\CustomerContextInterface" alias="sylius.context.customer" />

<service id="sylius.inventory.order_item_availability_checker" class="Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityChecker"/>
<service id="Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityCheckerInterface" alias="sylius.inventory.order_item_availability_checker" />

<service id="sylius.inventory.order_inventory_operator" class="Sylius\Component\Core\Inventory\Operator\OrderInventoryOperator"/>
<service id="Sylius\Component\Core\Inventory\Operator\OrderInventoryOperatorInterface" alias="sylius.inventory.order_inventory_operator" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
</service>

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,74 @@
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
use Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityCheckerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;

final class PaymentPreCompleteListenerSpec extends ObjectBehavior
{
function it_does_nothing_if_no_item_is_tracked(
function let(OrderItemAvailabilityCheckerInterface $orderItemAvailabilityChecker)
{
$this->beConstructedWith($orderItemAvailabilityChecker);
}

function it_does_nothing_if_reserved_stock_is_sufficient(
OrderItemAvailabilityCheckerInterface $orderItemAvailabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
): void {
$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));

$orderItemAvailabilityChecker->isReservedStockSufficient($orderItem)->willReturn(true);

$event->setMessageType('error')->shouldNotBeCalled();
$event->setMessage('sylius.resource.payment.cannot_be_completed')->shouldNotBeCalled();
$event->stopPropagation()->shouldNotBeCalled();

$this->checkStockAvailability($event);
}

function it_prevents_completing_the_payment_if_reserved_stock_is_not_sufficient(
OrderItemAvailabilityCheckerInterface $orderItemAvailabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));

$orderItemAvailabilityChecker->isReservedStockSufficient($orderItem)->willReturn(false);

$orderItem->getVariant()->willReturn($variant);
$variant->getCode()->willReturn('CODE');

$event->setMessageType('error')->shouldBeCalled();
$event->setMessage('sylius.resource.payment.cannot_be_completed')->shouldBeCalled();
$event->setMessageParameters(['%productVariantCode%' => 'CODE'])->shouldBeCalled();
$event->stopPropagation()->shouldBeCalled();

$this->checkStockAvailability($event);
}
function it_does_nothing_if_no_item_is_tracked_when_order_item_availability_checker_is_not_passed(
AvailabilityCheckerInterface $availabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$this->beConstructedWith($availabilityChecker);

$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
Expand All @@ -46,13 +100,16 @@ function it_does_nothing_if_no_item_is_tracked(
$this->checkStockAvailability($event);
}

function it_does_nothing_if_stock_is_sufficient_for_items(
function it_does_nothing_if_stock_is_sufficient_for_items_when_order_item_availability_checker_is_not_passed(
AvailabilityCheckerInterface $availabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$this->beConstructedWith($availabilityChecker);

$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
Expand All @@ -71,13 +128,16 @@ function it_does_nothing_if_stock_is_sufficient_for_items(
$this->checkStockAvailability($event);
}

function it_prevents_completing_the_payment_if_on_hold_amount_is_not_sufficient_for_item(
function it_prevents_completing_the_payment_if_on_hold_amount_is_not_sufficient_for_item_when_order_item_availability_checker_is_not_passed(
AvailabilityCheckerInterface $availabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$this->beConstructedWith($availabilityChecker);

$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
Expand All @@ -98,13 +158,16 @@ function it_prevents_completing_the_payment_if_on_hold_amount_is_not_sufficient_
$this->checkStockAvailability($event);
}

function it_prevents_completing_the_payment_if_on_hand_amount_is_not_sufficient_for_item(
function it_prevents_completing_the_payment_if_on_hand_amount_is_not_sufficient_for_item_when_order_item_availability_checker_is_not_passed(
AvailabilityCheckerInterface $availabilityChecker,
ResourceControllerEvent $event,
PaymentInterface $payment,
OrderInterface $order,
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$this->beConstructedWith($availabilityChecker);

$event->getSubject()->willReturn($payment);
$payment->getOrder()->willReturn($order);
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Component\Core\Inventory\Checker;

use Sylius\Component\Core\Model\OrderItemInterface;

final class OrderItemAvailabilityChecker implements OrderItemAvailabilityCheckerInterface
{
public function isReservedStockSufficient(OrderItemInterface $orderItem): bool
{
$variant = $orderItem->getVariant();
if (!$variant->isTracked()) {
return true;
}

$quantity = $orderItem->getQuantity();

return
$variant->getOnHold() - $quantity >= 0 &&
$variant->getOnHand() - $quantity >= 0
;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Component\Core\Inventory\Checker;

use Sylius\Component\Core\Model\OrderItemInterface;

interface OrderItemAvailabilityCheckerInterface
{
public function isReservedStockSufficient(OrderItemInterface $orderItem): bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace spec\Sylius\Component\Core\Inventory\Checker;

use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityCheckerInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;

final class OrderItemAvailabilityCheckerSpec extends ObjectBehavior
{
function it_implements_an_order_item_availability_checker_interface(): void
{
$this->shouldImplement(OrderItemAvailabilityCheckerInterface::class);
}

function it_returns_true_if_variant_is_untracked(
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$orderItem->getVariant()->willReturn($variant);
$variant->isTracked()->willReturn(false);

$this->isReservedStockSufficient($orderItem)->shouldReturn(true);
}

function it_returns_true_if_stock_is_sufficient(
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$orderItem->getVariant()->willReturn($variant);
$orderItem->getQuantity()->willReturn(2);

$variant->isTracked()->willReturn(true);
$variant->getOnHold()->willReturn(2);
$variant->getOnHand()->willReturn(2);

$this->isReservedStockSufficient($orderItem)->shouldReturn(true);
}

function it_returns_false_if_on_hold_value_is_not_sufficient(
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$orderItem->getVariant()->willReturn($variant);
$orderItem->getQuantity()->willReturn(2);

$variant->isTracked()->willReturn(true);
$variant->getOnHold()->willReturn(1);
$variant->getOnHand()->willReturn(2);

$this->isReservedStockSufficient($orderItem)->shouldReturn(false);
}

function it_returns_false_if_on_hand_value_is_not_sufficient(
OrderItemInterface $orderItem,
ProductVariantInterface $variant,
): void {
$orderItem->getVariant()->willReturn($variant);
$orderItem->getQuantity()->willReturn(2);

$variant->isTracked()->willReturn(true);
$variant->getOnHold()->willReturn(2);
$variant->getOnHand()->willReturn(1);

$this->isReservedStockSufficient($orderItem)->shouldReturn(false);
}
}

0 comments on commit 9420df1

Please sign in to comment.