From d0388eda525a99a95e6f2058ff7e5ff388b5f064 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 3 Dec 2024 10:58:23 +0100 Subject: [PATCH] fix #2: stock decrease on order cancel --- CHANGELOG.md | 5 +- .../Isotope/PreOrderStatusUpdateListener.php | 133 ++++++++++++------ src/ProductAttribute/StockAttribute.php | 2 +- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17cf413..f1e349c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [0.1.3] - 2024-12-03 +- Fixed: [#2](https://github.com/heimrichhannot/contao-isotope-stock-bundle/issues/2) stock decrease on order cancel + ## [0.1.2] - 2024-09-16 - Fixed: exception in migration if fields not exist @@ -9,4 +12,4 @@ All notable changes to this project will be documented in this file. - Fixed: exception in stock calculation on post checkout ## [0.1.0] - 2023-11-17 -Initial version. \ No newline at end of file +Initial version. diff --git a/src/EventListener/Isotope/PreOrderStatusUpdateListener.php b/src/EventListener/Isotope/PreOrderStatusUpdateListener.php index ac6bade..91372af 100644 --- a/src/EventListener/Isotope/PreOrderStatusUpdateListener.php +++ b/src/EventListener/Isotope/PreOrderStatusUpdateListener.php @@ -2,11 +2,13 @@ namespace HeimrichHannot\IsotopeStockBundle\EventListener\Isotope; +use Doctrine\DBAL\Connection; use HeimrichHannot\IsotopeStockBundle\ProductAttribute\MaxOrderSizeAttribute; use HeimrichHannot\IsotopeStockBundle\ProductAttribute\StockAttribute; use HeimrichHannot\UtilsBundle\Util\Utils; use Isotope\Model\OrderStatus; use Isotope\Model\ProductCollection\Order; +use Isotope\Model\ProductCollectionItem; use Isotope\ServiceAnnotation\IsotopeHook; /** @@ -15,15 +17,16 @@ class PreOrderStatusUpdateListener { public function __construct( - private Utils $utils, - private StockAttribute $stockAttribute, - private MaxOrderSizeAttribute $maxOrderSizeAttribute, + private readonly Connection $connection, + private readonly Utils $utils, + private readonly StockAttribute $stockAttribute, + private readonly MaxOrderSizeAttribute $maxOrderSizeAttribute, ) { } /** - * @return bool Cancel the order status transition + * @return bool Cancel the order status transition if the stock increase/decrease fails */ public function __invoke(Order $order, OrderStatus $newsStatus, array $updates): bool { @@ -32,52 +35,96 @@ public function __invoke(Order $order, OrderStatus $newsStatus, array $updates): return false; } - $oldStatus = OrderStatus::findByPk($order->order_status); - if (!$oldStatus) { + if (!$oldStatus = OrderStatus::findByPk($order->order_status)) { return false; } - // e.g. new -> cancelled => increase the stock based on the order item's setQuantity-values (no validation required, of course) - if (!$oldStatus->stock_increaseStock && $newsStatus->stock_increaseStock) { - foreach ($order->getItems() as $item) { - $product = $item->getProduct(); - if (!$product) { - continue; - } - - if ($this->stockAttribute->isActive($product)) { - $product->stock = (int)$product->stock + (int)$item->quantity; - $product->save(); - } - - if ($this->maxOrderSizeAttribute->isActive($product)) { - if (!$this->maxOrderSizeAttribute->validateQuantity($product, $item->quantity)) { - return true; - } - } - - } + if ((bool) $oldStatus->stock_increaseStock === (bool) $newsStatus->stock_increaseStock) + // No stock action change? Nothing to do here. + { + return false; } - // e.g. cancelled -> new => decrease the stock after validation - elseif ($oldStatus->stock_increaseStock && !$newsStatus->stock_increaseStock) { - foreach ($order->getItems() as $item) { - if (!$product = $item->getProduct()) { - continue; - } - - - if (null !== ($product = $item->getProduct())) { - if (!$this->stockAttribute->validateQuantity($product, $item->quantity)) { - // if the validation breaks for only one product collection item -> cancel the order status transition - return true; - } - } - - $product->stock = (int)$product->stock - (int)$item->quantity; - $product->save(); + + // Determine the appropriate callback based on stock change + // e.g. new -> cancelled: increase the stock based on the ordered item's quantity + // e.g. cancelled -> new: decrease the stock + $callback = !$oldStatus->stock_increaseStock && $newsStatus->stock_increaseStock + ? 'increaseStock' + : 'decreaseStock'; + + foreach ($order->getItems() as $item) + { + if (!$this->$callback($item)) + { + return true; } } return false; } + + /** + * @param ProductCollectionItem $item + * @return bool False if the stock increase failed + * @noinspection PhpUnused + */ + protected function increaseStock(ProductCollectionItem $item): bool + { + if (!$product = $item->getProduct()) { + return true; + } + + if ($this->stockAttribute->isActive($product)) + { + $newStock = (int)$product->stock + (int)$item->quantity; + + $this->connection + ->prepare("UPDATE `{$product::getTable()}` SET stock = ? WHERE id = ?") + ->executeStatement([$newStock, $product->id]); + + $product->stock = $newStock; + } + + if ($this->maxOrderSizeAttribute->isActive($product) + && !$this->maxOrderSizeAttribute->validateQuantity($product, $item->quantity)) + // validation needed after stock increase + { + return false; + } + + return true; + } + + /** + * @param ProductCollectionItem $item + * @return bool False if the stock decrease failed + * @noinspection PhpUnused + */ + protected function decreaseStock(ProductCollectionItem $item): bool + { + if (!$product = $item->getProduct()) { + return true; + } + + if (!$this->stockAttribute->validateQuantity($product, $item->quantity)) + // validation needed before stock decrease + // if the validation breaks, cancel the order status transition + { + return false; + } + + $newStock = (int)$product->stock - (int)$item->quantity; + + if ($newStock < 0) { + return false; + } + + $this->connection + ->prepare("UPDATE `{$product::getTable()}` SET stock = ? WHERE id = ?") + ->executeStatement([$newStock, $product->id]); + + $product->stock = $newStock; + + return true; + } } \ No newline at end of file diff --git a/src/ProductAttribute/StockAttribute.php b/src/ProductAttribute/StockAttribute.php index 9536763..21f2300 100644 --- a/src/ProductAttribute/StockAttribute.php +++ b/src/ProductAttribute/StockAttribute.php @@ -24,7 +24,7 @@ public function validateQuantity(IsotopeProduct $product, int $quantity): bool return true; } - if (0 === (int)$product->stock) { + if ((int)$product->stock < 1) { $this->addErrorMessage($this->translator->trans('MSC.stockEmpty', [$product->getName()], 'contao_default')); return false; }