Skip to content

Commit ee0a8ab

Browse files
committed
OP-289: Add new inventory management logic
1 parent 11b768e commit ee0a8ab

13 files changed

Lines changed: 732 additions & 0 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;
13+
14+
final class BundledProductsInventoryManagementFeatureFlagChecker implements FeatureFlagCheckerInterface
15+
{
16+
public function __construct(
17+
private readonly bool $enabled,
18+
) {
19+
}
20+
21+
public function isEnabled(): bool
22+
{
23+
return $this->enabled;
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;
13+
14+
interface FeatureFlagCheckerInterface
15+
{
16+
public function isEnabled(): bool;
17+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;
13+
14+
use BitBag\SyliusProductBundlePlugin\Entity\ProductInterface;
15+
use Sylius\Component\Core\Inventory\Checker\OrderItemAvailabilityCheckerInterface;
16+
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;
17+
18+
final class OrderItemAvailabilityChecker implements OrderItemAvailabilityCheckerInterface
19+
{
20+
public function __construct(
21+
private readonly OrderItemAvailabilityCheckerInterface $decorated,
22+
private readonly FeatureFlagCheckerInterface $featureFlagChecker,
23+
private readonly ProductBundleOrderItemAvailabilityCheckerInterface $bundleOrderItemAvailabilityChecker,
24+
) {
25+
}
26+
27+
public function isReservedStockSufficient(BaseOrderItemInterface $orderItem): bool
28+
{
29+
if (!$this->featureFlagChecker->isEnabled()) {
30+
return $this->decorated->isReservedStockSufficient($orderItem);
31+
}
32+
33+
/** @var ProductInterface $product */
34+
$product = $orderItem->getProduct();
35+
if (!$product->isBundle()) {
36+
return $this->decorated->isReservedStockSufficient($orderItem);
37+
}
38+
39+
return $this->bundleOrderItemAvailabilityChecker->areOrderedBundledProductVariantsAvailable($orderItem);
40+
}
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;
13+
14+
use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
15+
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;
16+
use Sylius\Component\Core\Model\ProductVariantInterface;
17+
18+
final class ProductBundleOrderItemAvailabilityChecker implements ProductBundleOrderItemAvailabilityCheckerInterface
19+
{
20+
public function areOrderedBundledProductVariantsAvailable(BaseOrderItemInterface|OrderItemInterface $orderItem): bool
21+
{
22+
if (!$orderItem instanceof OrderItemInterface) {
23+
return true;
24+
}
25+
26+
foreach ($orderItem->getProductBundleOrderItems() as $bundleOrderItem) {
27+
$quantity = $orderItem->getQuantity() * (int) $bundleOrderItem->getQuantity();
28+
/** @var ProductVariantInterface $variant */
29+
$variant = $bundleOrderItem->getProductVariant();
30+
if (!$variant->isTracked()) {
31+
continue;
32+
}
33+
34+
if (0 > (int) $variant->getOnHold() - $quantity || 0 > (int) $variant->getOnHand() - $quantity) {
35+
return false;
36+
}
37+
}
38+
39+
return true;
40+
}
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Checker;
13+
14+
use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
15+
use Sylius\Component\Core\Model\OrderItemInterface as BaseOrderItemInterface;
16+
17+
interface ProductBundleOrderItemAvailabilityCheckerInterface
18+
{
19+
public function areOrderedBundledProductVariantsAvailable(BaseOrderItemInterface|OrderItemInterface $orderItem): bool;
20+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusProductBundlePlugin\Inventory\Operator;
13+
14+
use BitBag\SyliusProductBundlePlugin\Entity\OrderItemInterface;
15+
use BitBag\SyliusProductBundlePlugin\Entity\ProductInterface;
16+
use BitBag\SyliusProductBundlePlugin\Inventory\Checker\FeatureFlagCheckerInterface;
17+
use Doctrine\DBAL\LockMode;
18+
use Doctrine\ORM\EntityManagerInterface;
19+
use Sylius\Component\Core\Inventory\Operator\OrderInventoryOperatorInterface;
20+
use Sylius\Component\Core\Model\OrderInterface;
21+
use Sylius\Component\Core\Model\ProductVariantInterface;
22+
use Sylius\Component\Core\OrderPaymentStates;
23+
24+
final class OrderInventoryOperator implements OrderInventoryOperatorInterface
25+
{
26+
public function __construct(
27+
private readonly OrderInventoryOperatorInterface $decorated,
28+
private readonly EntityManagerInterface $productVariantManager,
29+
private readonly FeatureFlagCheckerInterface $featureFlagChecker,
30+
private readonly ProductBundleOrderInventoryOperatorInterface $productBundleOrderInventoryOperator,
31+
) {
32+
}
33+
34+
public function cancel(OrderInterface $order): void
35+
{
36+
$this->lockOrderProductVariants($order);
37+
38+
if (!$this->featureFlagChecker->isEnabled()) {
39+
$this->decorated->cancel($order);
40+
}
41+
42+
if (in_array(
43+
$order->getPaymentState(),
44+
[OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_REFUNDED],
45+
true,
46+
)) {
47+
$this->productBundleOrderInventoryOperator->giveBack($order);
48+
49+
return;
50+
}
51+
52+
$this->productBundleOrderInventoryOperator->release($order);
53+
}
54+
55+
public function hold(OrderInterface $order): void
56+
{
57+
$this->lockOrderProductVariants($order);
58+
59+
if (!$this->featureFlagChecker->isEnabled()) {
60+
$this->decorated->hold($order);
61+
62+
return;
63+
}
64+
65+
$this->productBundleOrderInventoryOperator->hold($order);
66+
}
67+
68+
public function sell(OrderInterface $order): void
69+
{
70+
$this->lockOrderProductVariants($order);
71+
72+
if (!$this->featureFlagChecker->isEnabled()) {
73+
$this->decorated->sell($order);
74+
75+
return;
76+
}
77+
78+
$this->productBundleOrderInventoryOperator->sell($order);
79+
}
80+
81+
private function lockOrderProductVariants(OrderInterface $order): void
82+
{
83+
/** @var OrderItemInterface $orderItem */
84+
foreach ($order->getItems() as $orderItem) {
85+
$this->lockOrderItemProductVariants($orderItem);
86+
}
87+
}
88+
89+
private function lockOrderItemProductVariants(OrderItemInterface $orderItem): void
90+
{
91+
/** @var ProductInterface $product */
92+
$product = $orderItem->getProduct();
93+
if ($this->featureFlagChecker->isEnabled() && $product->isBundle()) {
94+
$this->lockBundledOrderItemProductVariants($orderItem);
95+
} else {
96+
$this->lockOrderItemProductVariant($orderItem);
97+
}
98+
}
99+
100+
private function lockOrderItemProductVariant(OrderItemInterface $orderItem): void
101+
{
102+
$this->lockProductVariant($orderItem->getVariant());
103+
}
104+
105+
private function lockBundledOrderItemProductVariants(OrderItemInterface $orderItem): void
106+
{
107+
foreach ($orderItem->getProductBundleOrderItems() as $bundleOrderItem) {
108+
$this->lockProductVariant($bundleOrderItem->getProductVariant());
109+
}
110+
}
111+
112+
private function lockProductVariant(?ProductVariantInterface $variant): void
113+
{
114+
if (null === $variant) {
115+
throw new \InvalidArgumentException('Variant cannot be null');
116+
}
117+
118+
if (!$variant->isTracked()) {
119+
return;
120+
}
121+
122+
$this->productVariantManager->lock($variant, LockMode::OPTIMISTIC, $variant->getVersion());
123+
}
124+
}

0 commit comments

Comments
 (0)