diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
index 8138e193e7978..1278d156ba869 100644
--- a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
+++ b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
@@ -158,7 +158,7 @@ protected function canRefundItem($item, $qtys = [], $invoiceQtysRefundLimits = [
if ($item->isDummy()) {
if ($item->getHasChildren()) {
foreach ($item->getChildrenItems() as $child) {
- if (empty($qtys)) {
+ if (empty($qtys) || (count(array_unique($qtys)) === 1 && (int)end($qtys) === 0)) {
if ($this->canRefundNoDummyItem($child, $invoiceQtysRefundLimits)) {
return true;
}
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup.xml
new file mode 100644
index 0000000000000..65a64bdf9eb90
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ Clicks on the 'Credit Memos' section on the Admin Orders edit page. Fills in the provided Refund details (child item qty, Shipping Refund, Adjustment Refund, Adjustment Fee and Row number).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml
index 74b773b0830d9..fa98843b0199a 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml
@@ -24,5 +24,6 @@
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml
new file mode 100644
index 0000000000000..3a9b252a95a3d
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditmemoWithBundleProductTest.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+ 1
+
+
+
+
+
+ 2
+ 1
+
+
+
+
+
+
+
+
+ 2
+ 1
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoFactoryTest.php
new file mode 100644
index 0000000000000..4cf571d3b6108
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoFactoryTest.php
@@ -0,0 +1,123 @@
+orderItemMock = $this->createPartialMock(
+ Item::class,
+ ['getChildrenItems', 'isDummy', 'getHasChildren', 'getId', 'getParentItemId']
+ );
+ $this->orderChildItemOneMock = $this->createPartialMock(
+ Item::class,
+ ['getQtyToRefund', 'getId']
+ );
+ $this->orderChildItemTwoMock = $this->createPartialMock(
+ Item::class,
+ ['getQtyToRefund', 'getId']
+ );
+ $this->testMethod = new ReflectionMethod(CreditmemoFactory::class, 'canRefundItem');
+
+ $objectManagerHelper = new ObjectManagerHelper($this);
+ $this->subject = $objectManagerHelper->getObject(CreditmemoFactory::class, []);
+ }
+
+ /**
+ * Check if order item can be refunded
+ * @return void
+ */
+ public function testCanRefundItem(): void
+ {
+ $orderItemQtys = [
+ 2 => 0,
+ 3 => 0
+ ];
+ $invoiceQtysRefundLimits = [];
+
+ $this->orderItemMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+ $this->orderItemMock->expects($this->any())
+ ->method('getParentItemId')
+ ->willReturn(false);
+ $this->orderItemMock->expects($this->any())
+ ->method('isDummy')
+ ->willReturn(true);
+ $this->orderItemMock->expects($this->any())
+ ->method('getHasChildren')
+ ->willReturn(true);
+
+ $this->orderChildItemOneMock->expects($this->any())
+ ->method('getQtyToRefund')
+ ->willReturn(1);
+ $this->orderChildItemOneMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(2);
+
+ $this->orderChildItemTwoMock->expects($this->any())
+ ->method('getQtyToRefund')
+ ->willReturn(1);
+ $this->orderChildItemTwoMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(3);
+ $this->orderItemMock->expects($this->any())
+ ->method('getChildrenItems')
+ ->willReturn([$this->orderChildItemOneMock, $this->orderChildItemTwoMock]);
+
+ $this->testMethod->setAccessible(true);
+
+ $this->assertTrue(
+ $this->testMethod->invoke(
+ $this->subject,
+ $this->orderItemMock,
+ $orderItemQtys,
+ $invoiceQtysRefundLimits
+ )
+ );
+ }
+}