From e36d8a0d2fa39b14be9ad43787dc93915fc9b116 Mon Sep 17 00:00:00 2001 From: Hubert Filar Date: Tue, 10 Sep 2024 16:09:13 +0200 Subject: [PATCH] OP-289: Implement handling unpacked bundles in API --- src/Command/AddProductBundleToCartCommand.php | 8 +- ...dProductBundleToCartDtoDataTransformer.php | 16 +++- src/DependencyInjection/Configuration.php | 3 +- src/Dto/Api/AddProductBundleToCartDto.php | 6 ++ .../AddProductBundleToCartCommandFactory.php | 2 +- ...uctBundleToCartCommandFactoryInterface.php | 4 +- src/Handler/AddProductBundleToCartHandler.php | 6 +- ...ProductBundleItemToCartCommandProvider.php | 87 +++++++++++++++++++ ...ndleItemToCartCommandProviderInterface.php | 21 +++++ .../ProductBundleItemRepository.php | 31 +++++++ .../ProductBundleItemRepositoryInterface.php | 21 +++++ src/Repository/ProductBundleRepository.php | 12 +++ .../ProductBundleRepositoryInterface.php | 3 + .../AddProductBundleToCartDto.xml | 3 + src/Resources/config/services.xml | 1 + src/Resources/config/services/provider.xml | 16 ++++ src/Resources/config/services/transformer.xml | 1 + 17 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 src/Provider/AddProductBundleItemToCartCommandProvider.php create mode 100644 src/Provider/AddProductBundleItemToCartCommandProviderInterface.php create mode 100644 src/Repository/ProductBundleItemRepository.php create mode 100644 src/Repository/ProductBundleItemRepositoryInterface.php create mode 100644 src/Resources/config/services/provider.xml diff --git a/src/Command/AddProductBundleToCartCommand.php b/src/Command/AddProductBundleToCartCommand.php index 564a7bb6..6f594132 100644 --- a/src/Command/AddProductBundleToCartCommand.php +++ b/src/Command/AddProductBundleToCartCommand.php @@ -11,6 +11,7 @@ namespace BitBag\SyliusProductBundlePlugin\Command; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; final class AddProductBundleToCartCommand implements OrderIdentityAwareInterface, ProductCodeAwareInterface @@ -19,10 +20,11 @@ final class AddProductBundleToCartCommand implements OrderIdentityAwareInterface private Collection $productBundleItems; public function __construct( - private int $orderId, - private string $productCode, - private int $quantity = 1, + private readonly int $orderId, + private readonly string $productCode, + private readonly int $quantity = 1, ) { + $this->productBundleItems = new ArrayCollection(); } public function getOrderId(): int diff --git a/src/DataTransformer/AddProductBundleToCartDtoDataTransformer.php b/src/DataTransformer/AddProductBundleToCartDtoDataTransformer.php index 323a6b30..7c3fa895 100644 --- a/src/DataTransformer/AddProductBundleToCartDtoDataTransformer.php +++ b/src/DataTransformer/AddProductBundleToCartDtoDataTransformer.php @@ -14,6 +14,7 @@ use ApiPlatform\Core\DataTransformer\DataTransformerInterface; use BitBag\SyliusProductBundlePlugin\Command\AddProductBundleToCartCommand; use BitBag\SyliusProductBundlePlugin\Dto\Api\AddProductBundleToCartDto; +use BitBag\SyliusProductBundlePlugin\Provider\AddProductBundleItemToCartCommandProviderInterface; use Sylius\Component\Order\Model\OrderInterface; use Webmozart\Assert\Assert; @@ -21,6 +22,11 @@ final class AddProductBundleToCartDtoDataTransformer implements DataTransformerI { public const OBJECT_TO_POPULATE = 'object_to_populate'; + public function __construct( + private readonly AddProductBundleItemToCartCommandProviderInterface $addProductBundleItemToCartCommandProvider, + ) { + } + /** * @param AddProductBundleToCartDto|object $object */ @@ -37,8 +43,16 @@ public function transform( $productCode = $object->getProductCode(); $quantity = $object->getQuantity(); + $overwrittenVariants = $object->getOverwrittenVariants(); + $addItemToCartCommands = $this->addProductBundleItemToCartCommandProvider->provide( + $productCode, + $overwrittenVariants, + ); + + $command = new AddProductBundleToCartCommand($cart->getId(), $productCode, $quantity); + $command->setProductBundleItems($addItemToCartCommands); - return new AddProductBundleToCartCommand($cart->getId(), $productCode, $quantity); + return $command; } public function supportsTransformation( diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 569fa83a..16abc3a3 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -19,6 +19,7 @@ use BitBag\SyliusProductBundlePlugin\Entity\ProductBundleOrderItemInterface; use BitBag\SyliusProductBundlePlugin\Form\Type\ProductBundleItemType; use BitBag\SyliusProductBundlePlugin\Form\Type\ProductBundleType; +use BitBag\SyliusProductBundlePlugin\Repository\ProductBundleItemRepository; use BitBag\SyliusProductBundlePlugin\Repository\ProductBundleRepository; use Sylius\Bundle\ResourceBundle\Controller\ResourceController; use Sylius\Bundle\ResourceBundle\SyliusResourceBundle; @@ -80,7 +81,7 @@ private function addResourcesSection(ArrayNodeDefinition $node): void ->scalarNode('interface')->defaultValue(ProductBundleItemInterface::class)->cannotBeEmpty()->end() ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() ->scalarNode('factory')->defaultValue(Factory::class)->cannotBeEmpty()->end() - ->scalarNode('repository')->cannotBeEmpty()->end() + ->scalarNode('repository')->defaultValue(ProductBundleItemRepository::class)->cannotBeEmpty()->end() ->scalarNode('form')->defaultValue(ProductBundleItemType::class)->cannotBeEmpty()->end() ->end() ->end() diff --git a/src/Dto/Api/AddProductBundleToCartDto.php b/src/Dto/Api/AddProductBundleToCartDto.php index 010434d9..20ab2856 100644 --- a/src/Dto/Api/AddProductBundleToCartDto.php +++ b/src/Dto/Api/AddProductBundleToCartDto.php @@ -19,6 +19,7 @@ public function __construct( private string $productCode, private int $quantity = 1, private ?string $orderTokenValue = null, + private array $overwrittenVariants = [], ) { } @@ -41,4 +42,9 @@ public function getQuantity(): int { return $this->quantity; } + + public function getOverwrittenVariants(): array + { + return $this->overwrittenVariants; + } } diff --git a/src/Factory/AddProductBundleToCartCommandFactory.php b/src/Factory/AddProductBundleToCartCommandFactory.php index 177082f0..dd66796b 100644 --- a/src/Factory/AddProductBundleToCartCommandFactory.php +++ b/src/Factory/AddProductBundleToCartCommandFactory.php @@ -18,7 +18,7 @@ final class AddProductBundleToCartCommandFactory implements AddProductBundleToCartCommandFactoryInterface { - /** @param Collection */ + /** @param Collection $productBundleItems */ public function createNew( int $orderId, string $productCode, diff --git a/src/Factory/AddProductBundleToCartCommandFactoryInterface.php b/src/Factory/AddProductBundleToCartCommandFactoryInterface.php index 383de508..24cba89a 100644 --- a/src/Factory/AddProductBundleToCartCommandFactoryInterface.php +++ b/src/Factory/AddProductBundleToCartCommandFactoryInterface.php @@ -11,14 +11,14 @@ namespace BitBag\SyliusProductBundlePlugin\Factory; +use BitBag\SyliusProductBundlePlugin\Command\AddProductBundleItemToCartCommand; use BitBag\SyliusProductBundlePlugin\Command\AddProductBundleToCartCommand; use BitBag\SyliusProductBundlePlugin\Dto\AddProductBundleToCartDtoInterface; -use BitBag\SyliusProductBundlePlugin\Entity\ProductBundleOrderItemInterface; use Doctrine\Common\Collections\Collection; interface AddProductBundleToCartCommandFactoryInterface { - /** @param Collection $productBundleItems */ + /** @param Collection $productBundleItems */ public function createNew( int $orderId, string $productCode, diff --git a/src/Handler/AddProductBundleToCartHandler.php b/src/Handler/AddProductBundleToCartHandler.php index 004965e1..fc88282e 100644 --- a/src/Handler/AddProductBundleToCartHandler.php +++ b/src/Handler/AddProductBundleToCartHandler.php @@ -23,9 +23,9 @@ final class AddProductBundleToCartHandler implements MessageHandlerInterface { public function __construct( - private OrderRepositoryInterface $orderRepository, - private ProductRepositoryInterface $productRepository, - private CartProcessorInterface $cartProcessor, + private readonly OrderRepositoryInterface $orderRepository, + private readonly ProductRepositoryInterface $productRepository, + private readonly CartProcessorInterface $cartProcessor, ) { } diff --git a/src/Provider/AddProductBundleItemToCartCommandProvider.php b/src/Provider/AddProductBundleItemToCartCommandProvider.php new file mode 100644 index 00000000..27fa1542 --- /dev/null +++ b/src/Provider/AddProductBundleItemToCartCommandProvider.php @@ -0,0 +1,87 @@ + + * + * @throws \Exception + */ + public function provide(string $bundleCode, array $overwrittenVariants): Collection + { + $bundle = $this->productBundleRepository->findOneByProductCode($bundleCode); + if (null === $bundle) { + throw new \Exception('Product bundle not found'); + } + + $bundleItems = $this->productBundleItemRepository->findByBundleCode($bundleCode); + + $commands = []; + foreach ($bundleItems as $bundleItem) { + $command = $this->addProductBundleItemToCartCommandFactory->createNew($bundleItem); + if (!$bundle->isPackedProduct() && [] !== $overwrittenVariants) { + $this->overwriteVariant($command, $bundleItem, $overwrittenVariants); + } + $commands[] = $command; + } + + return new ArrayCollection($commands); + } + + private function overwriteVariant( + AddProductBundleItemToCartCommand $command, + ProductBundleItemInterface $bundleItem, + array $overwrittenVariants, + ): void { + foreach ($overwrittenVariants as $overwrittenVariant) { + if (null !== $overwrittenVariant['from'] && null !== $overwrittenVariant['to'] && + $bundleItem->getProductVariant()?->getCode() === $overwrittenVariant['from'] && + $this->shouldOverwriteVariant($overwrittenVariant['from'], $overwrittenVariant['to']) + ) { + /** @var ProductVariantInterface $newVariant */ + $newVariant = $this->productVariantRepository->findOneBy(['code' => $overwrittenVariant['to']]); + $command->setProductVariant($newVariant); + } + } + } + + private function shouldOverwriteVariant(string $oldVariantCode, string $newVariantCode): bool + { + $oldVariant = $this->productVariantRepository->findOneBy(['code' => $oldVariantCode]); + $newVariant = $this->productVariantRepository->findOneBy(['code' => $newVariantCode]); + + return + $oldVariant instanceof ProductVariantInterface && + $newVariant instanceof ProductVariantInterface && + $oldVariant->getProduct() === $newVariant->getProduct(); + } +} diff --git a/src/Provider/AddProductBundleItemToCartCommandProviderInterface.php b/src/Provider/AddProductBundleItemToCartCommandProviderInterface.php new file mode 100644 index 00000000..1949b599 --- /dev/null +++ b/src/Provider/AddProductBundleItemToCartCommandProviderInterface.php @@ -0,0 +1,21 @@ + */ + public function provide(string $bundleCode, array $overwrittenVariants): Collection; +} diff --git a/src/Repository/ProductBundleItemRepository.php b/src/Repository/ProductBundleItemRepository.php new file mode 100644 index 00000000..29995866 --- /dev/null +++ b/src/Repository/ProductBundleItemRepository.php @@ -0,0 +1,31 @@ +createQueryBuilder('pbi') + ->leftJoin('pbi.productBundle', 'pb') + ->leftJoin('pb.product', 'p') + ->where('p.code = :code') + ->setParameter('code', $bundleCode) + ->getQuery() + ->getResult(); + } +} diff --git a/src/Repository/ProductBundleItemRepositoryInterface.php b/src/Repository/ProductBundleItemRepositoryInterface.php new file mode 100644 index 00000000..e431613f --- /dev/null +++ b/src/Repository/ProductBundleItemRepositoryInterface.php @@ -0,0 +1,21 @@ +getQuery() ->getResult(); } + + public function findOneByProductCode(string $productCode): ?ProductBundleInterface + { + return $this->createQueryBuilder('pb') + ->select('pb') + ->leftJoin('pb.product', 'p') + ->andWhere('p.code = :productCode') + ->setParameter('productCode', $productCode) + ->getQuery() + ->getSingleResult(); + } } diff --git a/src/Repository/ProductBundleRepositoryInterface.php b/src/Repository/ProductBundleRepositoryInterface.php index b408b3f8..6ee3fde9 100644 --- a/src/Repository/ProductBundleRepositoryInterface.php +++ b/src/Repository/ProductBundleRepositoryInterface.php @@ -11,6 +11,7 @@ namespace BitBag\SyliusProductBundlePlugin\Repository; +use BitBag\SyliusProductBundlePlugin\Entity\ProductBundleInterface; use Doctrine\Common\Collections\Collection; use Sylius\Component\Product\Model\ProductVariantInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; @@ -21,4 +22,6 @@ public function getProductIds(): array; /** @param Collection $variants */ public function findBundlesByVariants(Collection $variants): array; + + public function findOneByProductCode(string $productCode): ?ProductBundleInterface; } diff --git a/src/Resources/config/serialization/AddProductBundleToCartDto.xml b/src/Resources/config/serialization/AddProductBundleToCartDto.xml index 7d6fe771..548434c4 100644 --- a/src/Resources/config/serialization/AddProductBundleToCartDto.xml +++ b/src/Resources/config/serialization/AddProductBundleToCartDto.xml @@ -17,5 +17,8 @@ shop:cart:add_product_bundle + + shop:cart:add_product_bundle + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index a968c028..064ba3c6 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -11,6 +11,7 @@ + diff --git a/src/Resources/config/services/provider.xml b/src/Resources/config/services/provider.xml new file mode 100644 index 00000000..2d70958a --- /dev/null +++ b/src/Resources/config/services/provider.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Resources/config/services/transformer.xml b/src/Resources/config/services/transformer.xml index 4f212350..ccac7ace 100644 --- a/src/Resources/config/services/transformer.xml +++ b/src/Resources/config/services/transformer.xml @@ -7,6 +7,7 @@ id="bitbag_sylius_product_bundle.data_transformer.add_product_bundle_to_cart_dto" class="BitBag\SyliusProductBundlePlugin\DataTransformer\AddProductBundleToCartDtoDataTransformer" > +