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 63558c0290e2..326245d494c4 100644
--- a/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php
+++ b/vendor/magento/module-sales/Controller/Adminhtml/Order/Creditmemo/Save.php
@@ -8,7 +8,7 @@
 use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
 use Magento\Backend\App\Action;
 use Magento\Sales\Helper\Data as SalesData;
-use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Creditmemo;
 use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender;
 
 class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface
@@ -18,7 +18,7 @@ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterfac
      *
      * @see _isAllowed()
      */
-    const ADMIN_RESOURCE = 'Magento_Sales::sales_creditmemo';
+    public const ADMIN_RESOURCE = 'Magento_Sales::sales_creditmemo';
 
     /**
      * @var \Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader
@@ -85,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(
@@ -141,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 afec030075ab..8875899575cc 100644
--- a/vendor/magento/module-sales/Model/Order.php
+++ b/vendor/magento/module-sales/Model/Order.php
@@ -816,7 +816,6 @@ public function canComment()
      * Retrieve order shipment availability
      *
      * @return bool
-     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
      */
     public function canShip()
     {
@@ -832,27 +831,26 @@ public function canShip()
             return false;
         }
 
+        return $this->checkItemShipping();
+    }
+
+    /**
+     * Check if at least one of the order items can be shipped
+     *
+     * @return bool
+     */
+    private function checkItemShipping(): bool
+    {
         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() && !$this->isRefunded($item)) {
+            if ($qtyToShip > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) {
                 return true;
             }
         }
-        return false;
-    }
 
-    /**
-     * Check if item is refunded.
-     *
-     * @param OrderItemInterface $item
-     * @return bool
-     */
-    private function isRefunded(OrderItemInterface $item)
-    {
-        return $item->getQtyRefunded() == $item->getQtyOrdered();
+        return false;
     }
 
     /**
diff --git a/vendor/magento/module-sales/Model/Order/Item.php b/vendor/magento/module-sales/Model/Order/Item.php
index bc55b2229770..2b4e008d0324 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 51c45ed5e5a0..c83f262596e3 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;
     }
 }
