Skip to content

Commit

Permalink
OP-289: Add new inventory management logic
Browse files Browse the repository at this point in the history
  • Loading branch information
hmfilar committed Sep 16, 2024
1 parent 11b768e commit 0643e53
Show file tree
Hide file tree
Showing 12 changed files with 561 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;

final class BundledProductsInventoryManagementFeatureFlagChecker implements FeatureFlagCheckerInterface
{
public function __construct(
private readonly bool $enabled,
) {
}

public function isEnabled(): bool
{
return $this->enabled;
}
}
17 changes: 17 additions & 0 deletions src/Inventory/Checker/FeatureFlagCheckerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;

interface FeatureFlagCheckerInterface
{
public function isEnabled(): bool;
}
41 changes: 41 additions & 0 deletions src/Inventory/Checker/OrderItemAvailabilityChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;

use BitBag\SyliusProductBundlePlugin\Entity\ProductInterface;
use Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityCheckerInterface;
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;

final class OrderItemAvailabilityChecker implements OrderItemAvailabilityCheckerInterface
{
public function __construct(
private readonly OrderItemAvailabilityCheckerInterface $decorated,
private readonly FeatureFlagCheckerInterface $featureFlagChecker,
private readonly ProductBundleOrderItemAvailabilityCheckerInterface $bundleOrderItemAvailabilityChecker,
) {
}

public function isReservedStockSufficient(BaseOrderItemInterface $orderItem): bool
{
if (!$this->featureFlagChecker->isEnabled()) {
return $this->decorated->isReservedStockSufficient($orderItem);
}

/** @var ProductInterface $product */
$product = $orderItem->getProduct();
if (!$product->isBundle()) {
return $this->decorated->isReservedStockSufficient($orderItem);
}

return $this->bundleOrderItemAvailabilityChecker->areOrderedBundledProductVariantsAvailable($orderItem);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;

use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;

final class ProductBundleOrderItemAvailabilityChecker implements ProductBundleOrderItemAvailabilityCheckerInterface
{
public function areOrderedBundledProductVariantsAvailable(BaseOrderItemInterface|OrderItemInterface $orderItem): bool
{
if (!$orderItem instanceof OrderItemInterface) {
return true;
}

foreach ($orderItem->getProductBundleOrderItems() as $bundleOrderItem) {
$quantity = $orderItem->getQuantity() * (int) $bundleOrderItem->getQuantity();
/** @var ProductVariantInterface $variant */
$variant = $bundleOrderItem->getProductVariant();
if (!$variant->isTracked()) {
continue;
}

if (0 > (int) $variant->getOnHold() - $quantity || 0 > (int) $variant->getOnHand() - $quantity) {
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;

use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;

interface ProductBundleOrderItemAvailabilityCheckerInterface
{
public function areOrderedBundledProductVariantsAvailable(BaseOrderItemInterface|OrderItemInterface $orderItem): bool;
}
124 changes: 124 additions & 0 deletions src/Inventory/Operator/OrderInventoryOperator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?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 hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusProductBundlePlugin\Inventory\Operator;

use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
use BitBag\SyliusProductBundlePlugin\Entity\ProductInterface;
use BitBag\SyliusProductBundlePlugin\Inventory\Checker\FeatureFlagCheckerInterface;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManagerInterface;
use Sylius\Component\Core\Inventory\Operator\OrderInventoryOperatorInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\OrderPaymentStates;

final class OrderInventoryOperator implements OrderInventoryOperatorInterface
{
public function __construct(
private readonly OrderInventoryOperatorInterface $decorated,
private readonly EntityManagerInterface $productVariantManager,
private readonly FeatureFlagCheckerInterface $featureFlagChecker,
private readonly ProductBundleOrderInventoryOperatorInterface $productBundleOrderInventoryOperator,
) {
}

public function cancel(OrderInterface $order): void
{
$this->lockOrderProductVariants($order);

if (!$this->featureFlagChecker->isEnabled()) {
$this->decorated->cancel($order);
}

if (in_array(
$order->getPaymentState(),
[OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_REFUNDED],
true,
)) {
$this->productBundleOrderInventoryOperator->giveBack($order);

return;
}

$this->productBundleOrderInventoryOperator->release($order);
}

public function hold(OrderInterface $order): void
{
$this->lockOrderProductVariants($order);

if (!$this->featureFlagChecker->isEnabled()) {
$this->decorated->hold($order);

return;
}

$this->productBundleOrderInventoryOperator->hold($order);
}

public function sell(OrderInterface $order): void
{
$this->lockOrderProductVariants($order);

if (!$this->featureFlagChecker->isEnabled()) {
$this->decorated->sell($order);

return;
}

$this->productBundleOrderInventoryOperator->sell($order);
}

private function lockOrderProductVariants(OrderInterface $order): void
{
/** @var OrderItemInterface $orderItem */
foreach ($order->getItems() as $orderItem) {
$this->lockOrderItemProductVariants($orderItem);
}
}

private function lockOrderItemProductVariants(OrderItemInterface $orderItem): void
{
/** @var ProductInterface $product */
$product = $orderItem->getProduct();
if ($this->featureFlagChecker->isEnabled() && $product->isBundle()) {
$this->lockBundledOrderItemProductVariants($orderItem);
} else {
$this->lockOrderItemProductVariant($orderItem);
}
}

private function lockOrderItemProductVariant(OrderItemInterface $orderItem): void
{
$this->lockProductVariant($orderItem->getVariant());
}

private function lockBundledOrderItemProductVariants(OrderItemInterface $orderItem): void
{
foreach ($orderItem->getProductBundleOrderItems() as $bundleOrderItem) {
$this->lockProductVariant($bundleOrderItem->getProductVariant());
}
}

private function lockProductVariant(?ProductVariantInterface $variant): void
{
if (null === $variant) {
throw new \InvalidArgumentException('Variant cannot be null');
}

if (!$variant->isTracked()) {
return;
}

$this->productVariantManager->lock($variant, LockMode::OPTIMISTIC, $variant->getVersion());
}
}
Loading

0 comments on commit 0643e53

Please sign in to comment.