diff --git a/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php b/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php index a0cb5a4dc8ac8..52c3e47626caa 100644 --- a/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php +++ b/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php @@ -8,6 +8,7 @@ use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Sales\Helper\Data as SalesData; +use Magento\Sales\Model\Order\Creditmemo; use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender; class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface @@ -17,7 +18,7 @@ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterfac * * @see _isAllowed() */ - public const ADMIN_RESOURCE = 'Magento_Sales::creditmemo'; + public const ADMIN_RESOURCE = 'Magento_Sales::sales_creditmemo'; /** * @var \Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader @@ -84,6 +85,7 @@ public function execute() $this->creditmemoLoader->setCreditmemo($this->getRequest()->getParam('creditmemo')); $this->creditmemoLoader->setInvoiceId($this->getRequest()->getParam('invoice_id')); $creditmemo = $this->creditmemoLoader->load(); + $this->adjustCreditMemoItemQuantities($creditmemo); if ($creditmemo) { if (!$creditmemo->isValidGrandTotal()) { throw new \Magento\Framework\Exception\LocalizedException( @@ -140,4 +142,33 @@ public function execute() $resultRedirect->setPath('sales/*/new', ['_current' => true]); return $resultRedirect; } + + /** + * Adjust credit memo parent item quantities with children quantities + * + * @param Creditmemo $creditMemo + * @return void + */ + private function adjustCreditMemoItemQuantities(Creditmemo $creditMemo): void + { + $items = $creditMemo->getAllItems(); + $parentQuantities = []; + foreach ($items as $item) { + if ($parentId = $item->getOrderItem()->getParentItemId()) { + if (empty($parentQuantities[$parentId])) { + $parentQuantities[$parentId] = $item->getQty(); + } else { + $parentQuantities[$parentId] += $item->getQty(); + } + } + } + + foreach ($parentQuantities as $parentId => $quantity) { + foreach ($items as $item) { + if ($item->getOrderItemId() == $parentId) { + $item->setQty($quantity); + } + } + } + } } diff --git a/vendor/magento/module-sales/Model/Order.php b/vendor/magento/module-sales/Model/Order.php index f272a4638a170..8875899575ccc 100644 --- a/vendor/magento/module-sales/Model/Order.php +++ b/vendor/magento/module-sales/Model/Order.php @@ -5,6 +5,7 @@ */ namespace Magento\Sales\Model; +use Magento\Catalog\Model\Product\Type; use Magento\Config\Model\Config\Source\Nooptreq; use Magento\Directory\Model\Currency; use Magento\Directory\Model\RegionFactory; @@ -197,7 +198,8 @@ class Order extends AbstractModel implements EntityInterface, OrderInterface /** * @var \Magento\Catalog\Api\ProductRepositoryInterface - * @deprecated 100.1.0 Remove unused dependency. + * @deprecated 100.1.0 + * @see Remove unused dependency */ protected $productRepository; @@ -814,7 +816,6 @@ public function canComment() * Retrieve order shipment availability * * @return bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function canShip() { @@ -830,24 +831,26 @@ public function canShip() return false; } - foreach ($this->getAllItems() as $item) { - if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && - !$item->getLockedDoShip() && !$this->isRefunded($item)) { - return true; - } - } - return false; + return $this->checkItemShipping(); } /** - * Check if item is refunded. + * Check if at least one of the order items can be shipped * - * @param OrderItemInterface $item * @return bool */ - private function isRefunded(OrderItemInterface $item) + private function checkItemShipping(): bool { - return $item->getQtyRefunded() == $item->getQtyOrdered(); + foreach ($this->getAllItems() as $item) { + $qtyToShip = !$item->getParentItem() || $item->getParentItem()->getProductType() !== Type::TYPE_BUNDLE ? + $item->getQtyToShip() : $item->getSimpleQtyToShip(); + + if ($qtyToShip > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) { + return true; + } + } + + return false; } /** diff --git a/vendor/magento/module-sales/Model/Order/Item.php b/vendor/magento/module-sales/Model/Order/Item.php index bc55b2229770d..2b4e008d03247 100644 --- a/vendor/magento/module-sales/Model/Order/Item.php +++ b/vendor/magento/module-sales/Model/Order/Item.php @@ -24,36 +24,38 @@ */ class Item extends AbstractModel implements OrderItemInterface { - const STATUS_PENDING = 1; + public const STATUS_PENDING = 1; // No items shipped, invoiced, canceled, refunded nor backordered - const STATUS_SHIPPED = 2; + public const STATUS_SHIPPED = 2; // When qty ordered - [qty canceled + qty returned] = qty shipped - const STATUS_INVOICED = 9; + public const STATUS_INVOICED = 9; // When qty ordered - [qty canceled + qty returned] = qty invoiced - const STATUS_BACKORDERED = 3; + public const STATUS_BACKORDERED = 3; // When qty ordered - [qty canceled + qty returned] = qty backordered - const STATUS_CANCELED = 5; + public const STATUS_CANCELED = 5; // When qty ordered = qty canceled - const STATUS_PARTIAL = 6; + public const STATUS_PARTIAL = 6; // If [qty shipped or(max of two) qty invoiced + qty canceled + qty returned] // < qty ordered - const STATUS_MIXED = 7; + public const STATUS_MIXED = 7; // All other combinations - const STATUS_REFUNDED = 8; + public const STATUS_REFUNDED = 8; // When qty ordered = qty refunded - const STATUS_RETURNED = 4; + public const STATUS_RETURNED = 4; - // When qty ordered = qty returned // not used at the moment - - // When qty ordered = qty returned // not used at the moment + /** + * When qty ordered = qty returned // not used at the moment + * + * @var string + */ protected $_eventPrefix = 'sales_order_item'; /** diff --git a/vendor/magento/module-sales/Model/ResourceModel/Order/Handler/State.php b/vendor/magento/module-sales/Model/ResourceModel/Order/Handler/State.php index 51c45ed5e5a0d..c83f262596e3d 100644 --- a/vendor/magento/module-sales/Model/ResourceModel/Order/Handler/State.php +++ b/vendor/magento/module-sales/Model/ResourceModel/Order/Handler/State.php @@ -18,84 +18,87 @@ class State * * @param Order $order * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function check(Order $order) { $currentState = $order->getState(); - if ($currentState == Order::STATE_NEW && $order->getIsInProcess()) { + if ($this->checkForProcessingState($order, $currentState)) { $order->setState(Order::STATE_PROCESSING) ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)); $currentState = Order::STATE_PROCESSING; } + if ($order->isCanceled() || $order->canUnhold() || $order->canInvoice()) { + return $this; + } + + if ($this->checkForClosedState($order, $currentState)) { + $order->setState(Order::STATE_CLOSED) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); + return $this; + } - if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice()) { - if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE]) - && !$order->canCreditmemo() - && !$order->canShip() - && $order->getIsNotVirtual() - ) { - $order->setState(Order::STATE_CLOSED) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); - } elseif ($currentState === Order::STATE_PROCESSING - && (!$order->canShip() || $this->isPartiallyRefundedOrderShipped($order)) - ) { - $order->setState(Order::STATE_COMPLETE) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); - } elseif ($order->getIsVirtual() && $order->getStatus() === Order::STATE_CLOSED) { - $order->setState(Order::STATE_CLOSED); - } + if ($this->checkForCompleteState($order, $currentState)) { + $order->setState(Order::STATE_COMPLETE) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); + return $this; } + return $this; } /** - * Check if all items are remaining items after partially refunded are shipped + * Check if order can be automatically switched to complete state * * @param Order $order + * @param string|null $currentState * @return bool */ - public function isPartiallyRefundedOrderShipped(Order $order): bool + private function checkForCompleteState(Order $order, ?string $currentState): bool { - $isPartiallyRefundedOrderShipped = false; - if ($this->getShippedItems($order) > 0 - && $order->getTotalQtyOrdered() <= $this->getRefundedItems($order) + $this->getShippedItems($order)) { - $isPartiallyRefundedOrderShipped = true; + if ($currentState === Order::STATE_PROCESSING && !$order->canShip()) { + return true; } - return $isPartiallyRefundedOrderShipped; + return false; } /** - * Get all refunded items number + * Check if order can be automatically switched to closed state * * @param Order $order - * @return int + * @param string|null $currentState + * @return bool */ - private function getRefundedItems(Order $order): int + private function checkForClosedState(Order $order, ?string $currentState): bool { - $numOfRefundedItems = 0; - foreach ($order->getAllItems() as $item) { - if ($item->getProductType() == 'simple') { - $numOfRefundedItems += (int)$item->getQtyRefunded(); - } + if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE]) + && !$order->canCreditmemo() + && !$order->canShip() + && $order->getIsNotVirtual() + ) { + return true; } - return $numOfRefundedItems; + + if ($order->getIsVirtual() && $order->getStatus() === Order::STATE_CLOSED) { + return true; + } + + return false; } /** - * Get all shipped items number + * Check if order can be automatically switched to processing state * * @param Order $order - * @return int + * @param string|null $currentState + * @return bool */ - private function getShippedItems(Order $order): int + private function checkForProcessingState(Order $order, ?string $currentState): bool { - $numOfShippedItems = 0; - foreach ($order->getAllItems() as $item) { - $numOfShippedItems += (int)$item->getQtyShipped(); + if ($currentState == Order::STATE_NEW && $order->getIsInProcess()) { + return true; } - return $numOfShippedItems; + + return false; } }