Skip to content

Commit f7fafca

Browse files
ENGCOM-7499: #23440 fix Refund for bundle product without receiving product back e… #27455
- Merge Pull Request #27455 from KiuNguyen/magento2:fixbug-23440-Refund-bundle-product-without-receiving-product-back-error - Merged commits: 1. d0ab2a3 2. d03d133 3. 4807ebb 4. b88d88e 5. 10d5f73 6. b13b0ec 7. 4b28ae3 8. 9de9cf0 9. a7ea256 10. df1b766 11. 84ff076 12. a4d0b79
2 parents 3f1099e + a4d0b79 commit f7fafca

File tree

5 files changed

+274
-1
lines changed

5 files changed

+274
-1
lines changed

app/code/Magento/Sales/Model/Order/CreditmemoFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ protected function canRefundItem($item, $qtys = [], $invoiceQtysRefundLimits = [
158158
if ($item->isDummy()) {
159159
if ($item->getHasChildren()) {
160160
foreach ($item->getChildrenItems() as $child) {
161-
if (empty($qtys)) {
161+
if (empty($qtys) || (count(array_unique($qtys)) === 1 && (int)end($qtys) === 0)) {
162162
if ($this->canRefundNoDummyItem($child, $invoiceQtysRefundLimits)) {
163163
return true;
164164
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup">
12+
<annotations>
13+
<description>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).</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="itemQtyToRefund" type="string" defaultValue="0"/>
17+
<argument name="shippingRefund" type="string" defaultValue="0"/>
18+
<argument name="adjustmentRefund" type="string" defaultValue="0"/>
19+
<argument name="adjustmentFee" type="string" defaultValue="0"/>
20+
<argument name="rowNumberItemOne" type="string" defaultValue="3"/>
21+
<argument name="rowNumberItemTwo" type="string" defaultValue="5"/>
22+
<argument name="rowNumberItemThree" type="string" defaultValue="6"/>
23+
</arguments>
24+
25+
<!-- Click 'Credit Memo' button -->
26+
<click selector="{{AdminOrderDetailsMainActionsSection.creditMemo}}" stepKey="clickCreateCreditMemo"/>
27+
<seeInCurrentUrl url="{{AdminCreditMemoNewPage.url}}" stepKey="seeNewCreditMemoPage"/>
28+
<see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Memo" stepKey="seeNewMemoInPageTitle"/>
29+
30+
<!-- Fill data from dataset: refund -->
31+
<scrollTo selector="{{AdminCreditMemoItemsSection.header}}" stepKey="scrollToItemsToRefund"/>
32+
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemOne)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemOne"/>
33+
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemTwo)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemTwo"/>
34+
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemThree)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemThree"/>
35+
<waitForLoadingMaskToDisappear stepKey="waitForActivateButton"/>
36+
<conditionalClick selector="{{AdminCreditMemoItemsSection.updateQty}}" dependentSelector="{{AdminCreditMemoItemsSection.disabledUpdateQty}}" visible="false" stepKey="clickUpdateButton"/>
37+
<waitForLoadingMaskToDisappear stepKey="waitForUpdate"/>
38+
<fillField userInput="{{shippingRefund}}" selector="{{AdminCreditMemoTotalSection.refundShipping}}" stepKey="fillShipping"/>
39+
<fillField userInput="{{adjustmentRefund}}" selector="{{AdminCreditMemoTotalSection.adjustmentRefund}}" stepKey="fillAdjustmentRefund"/>
40+
<fillField userInput="{{adjustmentFee}}" selector="{{AdminCreditMemoTotalSection.adjustmentFee}}" stepKey="fillAdjustmentFee"/>
41+
<waitForElementVisible selector="{{AdminCreditMemoTotalSection.updateTotals}}" stepKey="waitForUpdateTotalsButton"/>
42+
<click selector="{{AdminCreditMemoTotalSection.updateTotals}}" stepKey="clickUpdateTotals"/>
43+
<checkOption selector="{{AdminCreditMemoTotalSection.emailCopy}}" stepKey="checkSendEmailCopy"/>
44+
</actionGroup>
45+
</actionGroups>

app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@
2424
<element name="disabledUpdateQty" type="button" selector=".order-creditmemo-tables tfoot button[data-ui-id='order-items-update-button'].disabled" timeout="30"/>
2525
<element name="nameColumn" type="text" selector=".order-creditmemo-tables .product-title"/>
2626
<element name="skuColumn" type="text" selector=".order-creditmemo-tables .product-sku-block"/>
27+
<element name="childItemQtyToRefund" type="input" selector=".order-creditmemo-tables tr:nth-child({{row}}) td .qty-input" parameterized="true"/>
2728
</section>
2829
</sections>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="AdminCreateCreditmemoWithBundleProductTest">
12+
<annotations>
13+
<title value="Create Creditmemo in Admin with bundle product"/>
14+
<stories value="Github issue: #23440 fix Refund for bundle product without receiving product back"/>
15+
<description value="Create Creditmemo for bundle product with without receiving product back(all child item qty = 0)"/>
16+
<features value="Sales"/>
17+
<severity value="AVERAGE"/>
18+
<group value="Sales"/>
19+
</annotations>
20+
21+
<before>
22+
<createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/>
23+
<createData entity="Simple_US_Customer_CA" stepKey="simpleCustomer"/>
24+
<createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/>
25+
<createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/>
26+
<createData entity="ApiBundleProduct" stepKey="product"/>
27+
<createData entity="CheckboxOption" stepKey="checkboxBundleOption">
28+
<requiredEntity createDataKey="product"/>
29+
</createData>
30+
<createData entity="ApiBundleLink" stepKey="createBundleLink1">
31+
<requiredEntity createDataKey="product"/>
32+
<requiredEntity createDataKey="checkboxBundleOption"/>
33+
<requiredEntity createDataKey="simple1"/>
34+
<field key="qty">2</field>
35+
<field key="is_default">1</field>
36+
</createData>
37+
<createData entity="ApiBundleLink" stepKey="createBundleLink2">
38+
<requiredEntity createDataKey="product"/>
39+
<requiredEntity createDataKey="checkboxBundleOption"/>
40+
<requiredEntity createDataKey="simple2"/>
41+
<field key="qty">2</field>
42+
<field key="is_default">1</field>
43+
</createData>
44+
<createData entity="DropDownBundleOption" stepKey="dropDownBundleOption">
45+
<requiredEntity createDataKey="product"/>
46+
</createData>
47+
<createData entity="ApiBundleLink" stepKey="createBundleLink3">
48+
<requiredEntity createDataKey="product"/>
49+
<requiredEntity createDataKey="dropDownBundleOption"/>
50+
<requiredEntity createDataKey="simple1"/>
51+
<field key="qty">2</field>
52+
<field key="is_default">1</field>
53+
</createData>
54+
<createData entity="ApiBundleLink" stepKey="createBundleLink4">
55+
<requiredEntity createDataKey="product"/>
56+
<requiredEntity createDataKey="dropDownBundleOption"/>
57+
<requiredEntity createDataKey="simple2"/>
58+
<field key="qty">2</field>
59+
</createData>
60+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
61+
</before>
62+
<after>
63+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
64+
<deleteData createDataKey="product" stepKey="delete"/>
65+
<deleteData createDataKey="simpleCustomer" stepKey="deleteSimpleCustomer"/>
66+
<deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/>
67+
<deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/>
68+
</after>
69+
70+
<actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToNewOrderWithExistingCustomer">
71+
<argument name="customer" value="$$simpleCustomer$$"/>
72+
</actionGroup>
73+
<actionGroup ref="AddBundleProductToOrderAndCheckPriceInGridActionGroup" stepKey="addBundleProductToOrder">
74+
<argument name="product" value="$$product$$"/>
75+
<argument name="quantity" value="1"/>
76+
<argument name="price" value="$738.00"/>
77+
</actionGroup>
78+
<actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/>
79+
<actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/>
80+
<actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/>
81+
<actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/>
82+
<grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/>
83+
<actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder">
84+
<argument name="orderId" value="$grabOrderId"/>
85+
</actionGroup>
86+
<actionGroup ref="AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup" stepKey="fillCreditMemoRefund">
87+
<argument name="itemQtyToRefund" value="0"/>
88+
<argument name="rowNumberItemOne" value="3"/>
89+
<argument name="rowNumberItemTwo" value="5"/>
90+
<argument name="rowNumberItemThree" value="6"/>
91+
<argument name="adjustmentRefund" value="10"/>
92+
</actionGroup>
93+
<actionGroup ref="SubmitCreditMemoActionGroup" stepKey="submitCreditMemo" />
94+
95+
<actionGroup ref="AdminOpenCreditMemoFromOrderPageActionGroup" stepKey="openCreditMemo" />
96+
<scrollTo selector="{{AdminCreditMemoViewTotalSection.subtotal}}" stepKey="scrollToTotal"/>
97+
<actionGroup ref="AssertAdminCreditMemoViewPageTotalsActionGroup" stepKey="assertCreditMemoViewPageTotals">
98+
<argument name="subtotal" value="$0.00"/>
99+
<argument name="adjustmentRefund" value="$10.00"/>
100+
<argument name="adjustmentFee" value="$0.00"/>
101+
<argument name="grandTotal" value="$10.00"/>
102+
</actionGroup>
103+
</test>
104+
</tests>
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Test\Unit\Model\Order;
9+
10+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
11+
use Magento\Sales\Model\Order\CreditmemoFactory;
12+
use Magento\Sales\Model\Order\Item;
13+
use PHPUnit\Framework\MockObject\MockObject;
14+
use PHPUnit\Framework\TestCase;
15+
use ReflectionMethod;
16+
17+
/**
18+
* Unit test for creditmemo factory class.
19+
*/
20+
class CreditmemoFactoryTest extends TestCase
21+
{
22+
/**
23+
* @var CreditmemoFactory
24+
*/
25+
protected $subject;
26+
27+
/**
28+
* @var ReflectionMethod
29+
*/
30+
protected $testMethod;
31+
32+
/**
33+
* @var Item|MockObject
34+
*/
35+
protected $orderItemMock;
36+
37+
/**
38+
* @var Item|MockObject
39+
*/
40+
protected $orderChildItemOneMock;
41+
42+
/**
43+
* @var Item|MockObject
44+
*/
45+
protected $orderChildItemTwoMock;
46+
47+
/**
48+
* @inheritDoc
49+
*/
50+
protected function setUp(): void
51+
{
52+
$this->orderItemMock = $this->createPartialMock(
53+
Item::class,
54+
['getChildrenItems', 'isDummy', 'getHasChildren', 'getId', 'getParentItemId']
55+
);
56+
$this->orderChildItemOneMock = $this->createPartialMock(
57+
Item::class,
58+
['getQtyToRefund', 'getId']
59+
);
60+
$this->orderChildItemTwoMock = $this->createPartialMock(
61+
Item::class,
62+
['getQtyToRefund', 'getId']
63+
);
64+
$this->testMethod = new ReflectionMethod(CreditmemoFactory::class, 'canRefundItem');
65+
66+
$objectManagerHelper = new ObjectManagerHelper($this);
67+
$this->subject = $objectManagerHelper->getObject(CreditmemoFactory::class, []);
68+
}
69+
70+
/**
71+
* Check if order item can be refunded
72+
* @return void
73+
*/
74+
public function testCanRefundItem(): void
75+
{
76+
$orderItemQtys = [
77+
2 => 0,
78+
3 => 0
79+
];
80+
$invoiceQtysRefundLimits = [];
81+
82+
$this->orderItemMock->expects($this->any())
83+
->method('getId')
84+
->willReturn(1);
85+
$this->orderItemMock->expects($this->any())
86+
->method('getParentItemId')
87+
->willReturn(false);
88+
$this->orderItemMock->expects($this->any())
89+
->method('isDummy')
90+
->willReturn(true);
91+
$this->orderItemMock->expects($this->any())
92+
->method('getHasChildren')
93+
->willReturn(true);
94+
95+
$this->orderChildItemOneMock->expects($this->any())
96+
->method('getQtyToRefund')
97+
->willReturn(1);
98+
$this->orderChildItemOneMock->expects($this->any())
99+
->method('getId')
100+
->willReturn(2);
101+
102+
$this->orderChildItemTwoMock->expects($this->any())
103+
->method('getQtyToRefund')
104+
->willReturn(1);
105+
$this->orderChildItemTwoMock->expects($this->any())
106+
->method('getId')
107+
->willReturn(3);
108+
$this->orderItemMock->expects($this->any())
109+
->method('getChildrenItems')
110+
->willReturn([$this->orderChildItemOneMock, $this->orderChildItemTwoMock]);
111+
112+
$this->testMethod->setAccessible(true);
113+
114+
$this->assertTrue(
115+
$this->testMethod->invoke(
116+
$this->subject,
117+
$this->orderItemMock,
118+
$orderItemQtys,
119+
$invoiceQtysRefundLimits
120+
)
121+
);
122+
}
123+
}

0 commit comments

Comments
 (0)