diff --git a/vendor/magento/module-inventory-reservation-cli/Command/ShowInconsistencies.php b/vendor/magento/module-inventory-reservation-cli/Command/ShowInconsistencies.php index e0a1c83f272..53ee96401ac 100644 --- a/vendor/magento/module-inventory-reservation-cli/Command/ShowInconsistencies.php +++ b/vendor/magento/module-inventory-reservation-cli/Command/ShowInconsistencies.php @@ -7,11 +7,7 @@ declare(strict_types=1); namespace Magento\InventoryReservationCli\Command; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Validation\ValidationException; -use Magento\InventoryConfigurationApi\Exception\SkuIsNotAssignedToStockException; use Magento\InventoryReservationCli\Model\GetSalableQuantityInconsistencies; -use Magento\InventoryReservationCli\Model\ResourceModel\GetOrdersTotalCount; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\FilterCompleteOrders; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\FilterIncompleteOrders; @@ -46,11 +42,6 @@ class ShowInconsistencies extends Command private $filterIncompleteOrders; /** - * @var GetOrdersTotalCount - */ - private $getOrdersTotalCount; - - /** * @var LoggerInterface */ private $logger; @@ -59,21 +50,18 @@ class ShowInconsistencies extends Command * @param GetSalableQuantityInconsistencies $getSalableQuantityInconsistencies * @param FilterCompleteOrders $filterCompleteOrders * @param FilterIncompleteOrders $filterIncompleteOrders - * @param GetOrdersTotalCount $getOrdersTotalCount * @param LoggerInterface $logger */ public function __construct( GetSalableQuantityInconsistencies $getSalableQuantityInconsistencies, FilterCompleteOrders $filterCompleteOrders, FilterIncompleteOrders $filterIncompleteOrders, - GetOrdersTotalCount $getOrdersTotalCount, LoggerInterface $logger ) { parent::__construct(); $this->getSalableQuantityInconsistencies = $getSalableQuantityInconsistencies; $this->filterCompleteOrders = $filterCompleteOrders; $this->filterIncompleteOrders = $filterIncompleteOrders; - $this->getOrdersTotalCount = $getOrdersTotalCount; $this->logger = $logger; } @@ -179,31 +167,22 @@ class ShowInconsistencies extends Command * @param InputInterface $input * @param OutputInterface $output * @return int - * @throws LocalizedException - * @throws ValidationException - * @throws SkuIsNotAssignedToStockException */ public function execute(InputInterface $input, OutputInterface $output): int { $startTime = microtime(true); $isRawOutput = (bool)$input->getOption('raw'); $bunchSize = (int)$input->getOption('bunch-size'); - - $maxPage = $this->retrieveMaxPage($bunchSize); $hasInconsistencies = false; - - for ($page = 1; $page <= $maxPage; $page++) { - $startBunchExecution = microtime(true); - - $inconsistencies = $this->getSalableQuantityInconsistencies->execute($bunchSize, $page); + $startBunchExecution = microtime(true); + $page = 1; + foreach ($this->getSalableQuantityInconsistencies->execute($bunchSize) as $inconsistencies) { if ($input->getOption('complete-orders')) { $inconsistencies = $this->filterCompleteOrders->execute($inconsistencies); } elseif ($input->getOption('incomplete-orders')) { $inconsistencies = $this->filterIncompleteOrders->execute($inconsistencies); } - $hasInconsistencies = !empty($inconsistencies); - if ($isRawOutput) { $this->rawOutput($output, $inconsistencies); } else { @@ -219,6 +198,9 @@ class ShowInconsistencies extends Command 'page' => $page, ] ); + $page++; + $hasInconsistencies = $hasInconsistencies || !empty($inconsistencies); + $startBunchExecution = microtime(true); } if ($hasInconsistencies === false) { @@ -236,16 +218,4 @@ class ShowInconsistencies extends Command return -1; } - - /** - * Retrieve max page for given bunch size - * - * @param int $bunchSize - * @return int - */ - private function retrieveMaxPage(int $bunchSize): int - { - $ordersTotalCount = $this->getOrdersTotalCount->execute(); - return (int)ceil($ordersTotalCount / $bunchSize); - } } diff --git a/vendor/magento/module-inventory-reservation-cli/Model/GetSalableQuantityInconsistencies.php b/vendor/magento/module-inventory-reservation-cli/Model/GetSalableQuantityInconsistencies.php index d1a36fa3631..76f627a94c3 100644 --- a/vendor/magento/module-inventory-reservation-cli/Model/GetSalableQuantityInconsistencies.php +++ b/vendor/magento/module-inventory-reservation-cli/Model/GetSalableQuantityInconsistencies.php @@ -7,18 +7,16 @@ declare(strict_types=1); namespace Magento\InventoryReservationCli\Model; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Validation\ValidationException; -use Magento\InventoryConfigurationApi\Exception\SkuIsNotAssignedToStockException; +use Magento\InventoryReservationCli\Model\ResourceModel\GetOrdersTotalCount; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\AddCompletedOrdersToForUnresolvedReservations; -use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\AddExistingReservations; +use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\LoadExistingReservations; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\AddExpectedReservations; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\Collector; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\CollectorFactory; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\FilterExistingOrders; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\FilterManagedStockProducts; use Magento\InventoryReservationCli\Model\SalableQuantityInconsistency\FilterUnresolvedReservations; -use Traversable; +use Magento\InventoryReservationsApi\Model\ReservationInterface; /** * Filter orders for missing initial reservation @@ -36,9 +34,9 @@ class GetSalableQuantityInconsistencies private $addExpectedReservations; /** - * @var AddExistingReservations + * @var LoadExistingReservations */ - private $addExistingReservations; + private $loadExistingReservations; /** * @var AddCompletedOrdersToForUnresolvedReservations @@ -61,56 +59,126 @@ class GetSalableQuantityInconsistencies private $filterManagedStockProducts; /** + * @var GetOrdersTotalCount + */ + private $getOrdersTotalCount; + + /** + * @var ReservationInterface[] + */ + private $existingReservations; + + /** * @param CollectorFactory $collectorFactory * @param AddExpectedReservations $addExpectedReservations - * @param AddExistingReservations $addExistingReservations + * @param LoadExistingReservations $loadExistingReservations * @param AddCompletedOrdersToForUnresolvedReservations $addCompletedOrdersToUnresolved * @param FilterExistingOrders $filterExistingOrders * @param FilterUnresolvedReservations $filterUnresolvedReservations * @param FilterManagedStockProducts $filterManagedStockProducts + * @param GetOrdersTotalCount $getOrdersTotalCount */ public function __construct( CollectorFactory $collectorFactory, AddExpectedReservations $addExpectedReservations, - AddExistingReservations $addExistingReservations, + LoadExistingReservations $loadExistingReservations, AddCompletedOrdersToForUnresolvedReservations $addCompletedOrdersToUnresolved, FilterExistingOrders $filterExistingOrders, FilterUnresolvedReservations $filterUnresolvedReservations, - FilterManagedStockProducts $filterManagedStockProducts + FilterManagedStockProducts $filterManagedStockProducts, + GetOrdersTotalCount $getOrdersTotalCount ) { $this->collectorFactory = $collectorFactory; $this->addExpectedReservations = $addExpectedReservations; - $this->addExistingReservations = $addExistingReservations; + $this->loadExistingReservations = $loadExistingReservations; $this->addCompletedOrdersToUnresolved = $addCompletedOrdersToUnresolved; $this->filterExistingOrders = $filterExistingOrders; $this->filterUnresolvedReservations = $filterUnresolvedReservations; $this->filterManagedStockProducts = $filterManagedStockProducts; + $this->getOrdersTotalCount = $getOrdersTotalCount; } /** - * Filter orders for missing initial reservation + * Load filtered orders for missing initial reservation by bunch size + * + * The method returns inconsistencies in bunches by Generator to avoid out of memory exception * * @param int $bunchSize - * @param int $page - * @return SalableQuantityInconsistency[] - * @throws LocalizedException - * @throws SkuIsNotAssignedToStockException - * @throws ValidationException + * @return \Generator */ - public function execute(int $bunchSize = 50, int $page = 1): array + public function execute(int $bunchSize = 50): \Generator { - /** @var Collector $collector */ - $collector = $this->collectorFactory->create(); - $this->addExpectedReservations->execute($collector, $bunchSize, $page); - $this->addExistingReservations->execute($collector); - $this->addCompletedOrdersToUnresolved->execute($collector); + $maxPage = $this->retrieveMaxPage($bunchSize) ?: 1; + for ($page = 1; $page <= $maxPage; $page++) { + $collector = $this->collectorFactory->create(); + $this->addExpectedReservations->execute($collector, $bunchSize, $page); + $this->mergeExistingReservations($collector, $page === $maxPage); + $this->addCompletedOrdersToUnresolved->execute($collector); + $items = $this->filterItems($collector->getItems()); + unset($collector); - $items = $collector->getItems(); + yield $items; + } + + $this->existingReservations = null; + } + + /** + * Filter list of inconsistencies + * + * @param array $items + * @return array + */ + private function filterItems(array $items): array + { + $items = $this->filterUnresolvedReservations->execute($items); $items = $this->filterManagedStockProducts->execute($items); $items = $this->filterUnresolvedReservations->execute($items); $items = $this->filterExistingOrders->execute($items); - unset($collector); return $items; } + + /** + * Retrieve max page for given bunch size + * + * @param int $bunchSize + * @return int + */ + private function retrieveMaxPage(int $bunchSize): int + { + $ordersTotalCount = $this->getOrdersTotalCount->execute(); + return (int) ceil($ordersTotalCount / $bunchSize); + } + + /** + * @param Collector $collector + * @param bool $isLastPage + */ + private function mergeExistingReservations(Collector $collector, bool $isLastPage): void + { + foreach ($this->getExistingReservations() as $key => $reservations) { + /** Adds the rest of the existing reservations to the last page */ + if (isset($collector->getItems()[$key]) || $isLastPage) { + foreach ($reservations as $reservation) { + $collector->addReservation($reservation); + } + unset($this->existingReservations[$key]); + } + } + } + + /** + * Get existing reservations + * + * @return ReservationInterface[] + */ + private function getExistingReservations(): array + { + if ($this->existingReservations === null) { + $this->existingReservations = $this->loadExistingReservations->execute(); + } + + return $this->existingReservations; + } } diff --git a/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrderItemsDataForOrdersInNotFinalState.php b/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrderItemsDataForOrdersInNotFinalState.php index 000d296a366..4605c1f4e7b 100644 --- a/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrderItemsDataForOrdersInNotFinalState.php +++ b/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrderItemsDataForOrdersInNotFinalState.php @@ -85,7 +85,15 @@ class GetOrderItemsDataForOrdersInNotFinalState ->join( ['item' => $orderItemTableName], 'item.order_id = main_table.entity_id', - ['item.sku', 'item.qty_ordered'] + [ + 'item.sku', + 'item.is_virtual', + 'item.qty_ordered', + 'item.qty_canceled', + 'item.qty_invoiced', + 'item.qty_refunded', + 'item.qty_shipped' + ] ) ->where('main_table.entity_id IN (?)', $entityIds) ->where('item.product_type IN (?)', $this->allowedProductTypesForSourceItemManagement->execute()); diff --git a/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrdersTotalCount.php b/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrdersTotalCount.php index 04a7ae7f4c1..6301c9e1eff 100644 --- a/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrdersTotalCount.php +++ b/vendor/magento/module-inventory-reservation-cli/Model/ResourceModel/GetOrdersTotalCount.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace Magento\InventoryReservationCli\Model\ResourceModel; use Magento\Framework\App\ResourceConnection; +use Magento\InventoryReservationCli\Model\GetCompleteOrderStateList; /** * Get count of all existing orders @@ -20,12 +21,20 @@ class GetOrdersTotalCount private $resourceConnection; /** + * @var GetCompleteOrderStateList + */ + private $getCompleteOrderStateList; + + /** * @param ResourceConnection $resourceConnection + * @param GetCompleteOrderStateList $getCompleteOrderStateList */ public function __construct( - ResourceConnection $resourceConnection + ResourceConnection $resourceConnection, + GetCompleteOrderStateList $getCompleteOrderStateList ) { $this->resourceConnection = $resourceConnection; + $this->getCompleteOrderStateList = $getCompleteOrderStateList; } /** @@ -37,13 +46,15 @@ class GetOrdersTotalCount { $connection = $this->resourceConnection->getConnection('sales'); $orderTableName = $this->resourceConnection->getTableName('sales_order', 'sales'); - - $query = $connection - ->select() + $query = $connection->select() ->from( ['main_table' => $orderTableName], ['count' => new \Zend_Db_Expr('COUNT(main_table.entity_id)')] + )->where( + 'main_table.state NOT IN (?)', + $this->getCompleteOrderStateList->execute() ); + return (int)$connection->fetchOne($query); } } diff --git a/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExistingReservations.php b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExistingReservations.php index 52413dd2121..65e64762431 100644 --- a/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExistingReservations.php +++ b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExistingReservations.php @@ -14,6 +14,8 @@ use Magento\InventoryReservationsApi\Model\ReservationBuilderInterface; /** * Add existing reservations + * + * @deprecated */ class AddExistingReservations { diff --git a/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExpectedReservations.php b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExpectedReservations.php index 88e999c5d00..2d0f25c77f2 100644 --- a/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExpectedReservations.php +++ b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/AddExpectedReservations.php @@ -72,7 +72,7 @@ class AddExpectedReservations $reservation = $this->reservationBuilder ->setSku($data['sku']) - ->setQuantity((float)$data['qty_ordered']) + ->setQuantity($this->calculateReservationQty($data)) ->setStockId($stockId) ->setMetadata($this->serializer->serialize( [ @@ -86,4 +86,20 @@ class AddExpectedReservations $collector->addOrderData($data); } } + + /** + * Return reservation qty amount + * + * @param array $data + * @return float + */ + private function calculateReservationQty(array $data): float + { + $qty = $data['qty_ordered']; + $qty -= $data['qty_canceled']; + $qty -= $data['qty_refunded']; + $qty -= $data['is_virtual'] ? $data['qty_invoiced'] : $data['qty_shipped']; + + return (float)$qty; + } } diff --git a/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/LoadExistingReservations.php b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/LoadExistingReservations.php new file mode 100644 index 00000000000..e6beabbde47 --- /dev/null +++ b/vendor/magento/module-inventory-reservation-cli/Model/SalableQuantityInconsistency/LoadExistingReservations.php @@ -0,0 +1,113 @@ +getReservationsList = $getReservationsList; + $this->serializer = $serializer; + $this->reservationBuilder = $reservationBuilder; + $this->getOrderIncrementId = $getOrderIncrementId; + } + + /** + * Load existing reservations + * + * @return array + */ + public function execute(): array + { + $result = []; + $reservationList = $this->getReservationsList->execute(); + foreach ($reservationList as $reservation) { + /** @var array $metadata */ + $metadata = $this->serializer->unserialize($reservation['metadata']); + if ($metadata['object_type'] !== 'order') { + continue; + } + + if (!isset($metadata['object_increment_id'])) { + $metadata['object_increment_id'] = $this->getOrderIncrementId( + (int)$metadata['object_id'] + ); + } + $reservationModel = $this->reservationBuilder + ->setMetadata($this->serializer->serialize($metadata)) + ->setStockId((int)$reservation['stock_id']) + ->setSku($reservation['sku']) + ->setQuantity((float)$reservation['quantity']) + ->build(); + + $key = $metadata['object_increment_id'] . '-' . $reservation['stock_id']; + $result[$key][] = $reservationModel; + } + + return $result; + } + + /** + * Load order increment id by order id + * + * @param int $orderId + * @return string + */ + private function getOrderIncrementId(int $orderId): string + { + if (!isset($this->orderIncrementIds[$orderId])) { + $this->orderIncrementIds[$orderId] = $this->getOrderIncrementId->execute($orderId); + } + + return $this->orderIncrementIds[$orderId]; + } +}