Skip to content

Commit

Permalink
Merge pull request #261 from BitBagCommerce/op-282-bulk-actions-fix
Browse files Browse the repository at this point in the history
OP-282: Bulk actions fix
  • Loading branch information
GracjanJozefczyk authored Jun 25, 2024
2 parents 7152d14 + 51162df commit 46abe3a
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 0 deletions.
176 changes: 176 additions & 0 deletions spec/Validator/InStockWishlistValidatorSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace spec\BitBag\SyliusWishlistPlugin\Validator;

use BitBag\SyliusWishlistPlugin\Validator\InStockWishlistValidator;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use stdClass;
use Sylius\Bundle\CoreBundle\Validator\Constraints\CartItemAvailability;
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Order\Model\OrderInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

final class InStockWishlistValidatorSpec extends ObjectBehavior
{
public function let(
AvailabilityCheckerInterface $availabilityChecker,
RequestStack $requestStack,
Router $router,
): void {
$this->beConstructedWith($availabilityChecker, $requestStack, $router);
}

public function it_is_initializable(): void
{
$this->shouldHaveType(InStockWishlistValidator::class);
}

public function it_should_throw_exception_if_value_is_not_instance_of_command_interface(
Constraint $constraint,
stdClass $value,
): void {
$this->shouldThrow(\Exception::class)->during(
'validate',
[$value, $constraint],
);
}

public function it_should_throw_exception_if_constraint_is_not_instance_of_command_interface(
Constraint $constraint,
AddToCartCommandInterface $value,
): void {
$this->shouldThrow(\Exception::class)->during(
'validate',
[$value, $constraint],
);
}

public function it_should_throw_exception_if_request_is_null(
RequestStack $requestStack,
AddToCartCommandInterface $value,
): void {
$constraint = new CartItemAvailability();

$requestStack->getCurrentRequest()->willReturn(null);

$this->shouldThrow(\Exception::class)->during(
'validate',
[$value, $constraint],
);
}

public function it_should_do_nothing_if_the_request_is_not_add_to_cart(
RequestStack $requestStack,
AddToCartCommandInterface $value,
Request $request,
Router $router,
): void {
$constraint = new CartItemAvailability();
$exampleArray = [
'_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_remove_selected_products',
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.remove_selected_products_from_wishlist',
'_locale' => 'en_US',
'wishlistId' => '4',
];

$requestStack->getCurrentRequest()->willReturn($request);
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/delete');
$router->match('/en_US/wishlist/4/products/delete')->willReturn($exampleArray);

$this->validate($value, $constraint)->shouldReturn(null);
}

public function it_should_do_nothing_if_current_stock_is_not_empty(
AvailabilityCheckerInterface $availabilityChecker,
RequestStack $requestStack,
AddToCartCommandInterface $value,
Request $request,
Router $router,
OrderItemInterface $cartItem,
ExecutionContextInterface $context,
ProductVariantInterface $variant,
OrderInterface $order,
): void {
$constraint = new CartItemAvailability();
$exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products',
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart',
'_locale' => 'en_US',
'wishlistId' => '4',
];

$requestStack->getCurrentRequest()->willReturn($request);
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add');
$router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray);
$value->getCartItem()->willReturn($cartItem);
$cartItem->getVariant()->willReturn($variant);
$cartItem->getQuantity()->willReturn(5);
$value->getCart()->willReturn($order);
$itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]);
$order->getItems()->willReturn($itemsCollection);
$cartItem->equals(Argument::any())->willReturn(false);
$availabilityChecker->isStockSufficient($variant, 5)->willReturn(true);

$context->addViolation(Argument::any(), Argument::any())->shouldNotBeCalled();

$this->validate($value, $constraint)->shouldReturn(null);
}

public function it_should_do_build_violation_if_current_stock_is_empty_and_user_added_to_cart_item_from_wishlist(
AvailabilityCheckerInterface $availabilityChecker,
RequestStack $requestStack,
AddToCartCommandInterface $value,
Request $request,
Router $router,
OrderItemInterface $cartItem,
OrderInterface $order,
ProductVariantInterface $variant,
ExecutionContextInterface $context,
): void {
$constraint = new CartItemAvailability();
$constraint->message = 'sylius.cart_item.not_available';
$exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products',
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart',
'_locale' => 'en_US',
'wishlistId' => '4',
];

$requestStack->getCurrentRequest()->willReturn($request);
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add');
$router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray);
$value->getCartItem()->willReturn($cartItem);
$cartItem->getVariant()->willReturn($variant);
$cartItem->getQuantity()->willReturn(5);
$value->getCart()->willReturn($order);
$itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]);
$order->getItems()->willReturn($itemsCollection);
$cartItem->equals(Argument::any())->willReturn(false);
$availabilityChecker->isStockSufficient($variant, 5)->willReturn(false);
$variant->getInventoryName()->willReturn('Red T-shirt');

$context->addViolation(
$constraint->message,
['%itemName%' => 'Red T-shirt'],
)->shouldBeCalled();

$this->initialize($context);
$this->validate($value, $constraint);
}
}
18 changes: 18 additions & 0 deletions src/Resources/config/services/validator.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service class="BitBag\SyliusWishlistPlugin\Validator\InStockWishlistValidator"
id="bitbag.sylius_wishlist_plugin.validator.wishlist_in_stock_validator"
decorates="sylius.validator.cart_item_availability">
<argument type="service" id="sylius.availability_checker.default" />
<argument type="service" id="request_stack" />
<argument type="service" id="router" />
<tag name="validator.constraint_validator" alias="bitbag_sylius_wishlist_plugin_validator_wishlist_in_stock_validator"/>
</service>
</services>

</container>
12 changes: 12 additions & 0 deletions src/Resources/config/validation/AddToCartCommand.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>

<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Bundle\OrderBundle\Controller\AddToCartCommand">
<constraint name="BitBag\SyliusWishlistPlugin\Validator\Constraints\InStockWishlistConstraint">
<option name="message">sylius.cart_item.not_available</option>
<option name="groups">
<value>wishlist_in_stock</value>
</option>
</constraint>
</class>
</constraint-mapping>
31 changes: 31 additions & 0 deletions src/Validator/Constraints/InStockWishlistConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace BitBag\SyliusWishlistPlugin\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

final class InStockWishlistConstraint extends Constraint
{
public string $message = 'sylius.cart_item.not_available';

public const ADD_PRODUCTS_ROUTE = 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products';

public function validatedBy(): string
{
return 'bitbag_sylius_wishlist_plugin_validator_wishlist_in_stock_validator';
}

public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}
88 changes: 88 additions & 0 deletions src/Validator/InStockWishlistValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace BitBag\SyliusWishlistPlugin\Validator;

use BitBag\SyliusWishlistPlugin\Validator\Constraints\InStockWishlistConstraint;
use Sylius\Bundle\CoreBundle\Validator\Constraints\CartItemAvailability;
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Inventory\Model\StockableInterface;
use Sylius\Component\Order\Model\OrderInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;

final class InStockWishlistValidator extends ConstraintValidator
{
public function __construct(
private AvailabilityCheckerInterface $availabilityChecker,
private RequestStack $requestStack,
private Router $router,
) {
}

public function validate(mixed $value, Constraint $constraint): void
{
Assert::isInstanceOf($value, AddToCartCommandInterface::class);

Assert::isInstanceOf($constraint, CartItemAvailability::class);

$request = $this->requestStack->getCurrentRequest();

Assert::notNull($request);

try {
$route = $this->router->match($request->getPathInfo());
} catch (ResourceNotFoundException $exception) {
throw new AccessDeniedHttpException('Access denied');
}

if (array_key_exists('_route', $route) &&
InStockWishlistConstraint::ADD_PRODUCTS_ROUTE !== $route['_route']) {
return;
}

/** @var OrderItemInterface $cartItem */
$cartItem = $value->getCartItem();

/** @var StockableInterface $variant */
$variant = $cartItem->getVariant();

$isStockSufficient = $this->availabilityChecker->isStockSufficient(
$variant,
$cartItem->getQuantity() + $this->getExistingCartItemQuantityFromCart($value->getCart(), $cartItem),
);

if (!$isStockSufficient) {
$this->context->addViolation(
$constraint->message,
['%itemName%' => $variant->getInventoryName()],
);
}
}

private function getExistingCartItemQuantityFromCart(OrderInterface $cart, OrderItemInterface $cartItem): int
{
foreach ($cart->getItems() as $existingCartItem) {
if ($existingCartItem->equals($cartItem)) {
return $existingCartItem->getQuantity();
}
}

return 0;
}
}

0 comments on commit 46abe3a

Please sign in to comment.