Skip to content

Commit

Permalink
ENGCOM-7499: #23440 fix Refund for bundle product without receiving p…
Browse files Browse the repository at this point in the history
…roduct 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
  • Loading branch information
magento-engcom-team committed May 14, 2020
2 parents 3f1099e + a4d0b79 commit f7fafca
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
Expand Up @@ -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;
}
Expand Down
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
<actionGroup name="AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup">
<annotations>
<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>
</annotations>
<arguments>
<argument name="itemQtyToRefund" type="string" defaultValue="0"/>
<argument name="shippingRefund" type="string" defaultValue="0"/>
<argument name="adjustmentRefund" type="string" defaultValue="0"/>
<argument name="adjustmentFee" type="string" defaultValue="0"/>
<argument name="rowNumberItemOne" type="string" defaultValue="3"/>
<argument name="rowNumberItemTwo" type="string" defaultValue="5"/>
<argument name="rowNumberItemThree" type="string" defaultValue="6"/>
</arguments>

<!-- Click 'Credit Memo' button -->
<click selector="{{AdminOrderDetailsMainActionsSection.creditMemo}}" stepKey="clickCreateCreditMemo"/>
<seeInCurrentUrl url="{{AdminCreditMemoNewPage.url}}" stepKey="seeNewCreditMemoPage"/>
<see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Memo" stepKey="seeNewMemoInPageTitle"/>

<!-- Fill data from dataset: refund -->
<scrollTo selector="{{AdminCreditMemoItemsSection.header}}" stepKey="scrollToItemsToRefund"/>
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemOne)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemOne"/>
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemTwo)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemTwo"/>
<fillField selector="{{AdminCreditMemoItemsSection.childItemQtyToRefund(rowNumberItemThree)}}" userInput="{{itemQtyToRefund}}" stepKey="fillQtyToRefundItemThree"/>
<waitForLoadingMaskToDisappear stepKey="waitForActivateButton"/>
<conditionalClick selector="{{AdminCreditMemoItemsSection.updateQty}}" dependentSelector="{{AdminCreditMemoItemsSection.disabledUpdateQty}}" visible="false" stepKey="clickUpdateButton"/>
<waitForLoadingMaskToDisappear stepKey="waitForUpdate"/>
<fillField userInput="{{shippingRefund}}" selector="{{AdminCreditMemoTotalSection.refundShipping}}" stepKey="fillShipping"/>
<fillField userInput="{{adjustmentRefund}}" selector="{{AdminCreditMemoTotalSection.adjustmentRefund}}" stepKey="fillAdjustmentRefund"/>
<fillField userInput="{{adjustmentFee}}" selector="{{AdminCreditMemoTotalSection.adjustmentFee}}" stepKey="fillAdjustmentFee"/>
<waitForElementVisible selector="{{AdminCreditMemoTotalSection.updateTotals}}" stepKey="waitForUpdateTotalsButton"/>
<click selector="{{AdminCreditMemoTotalSection.updateTotals}}" stepKey="clickUpdateTotals"/>
<checkOption selector="{{AdminCreditMemoTotalSection.emailCopy}}" stepKey="checkSendEmailCopy"/>
</actionGroup>
</actionGroups>
Expand Up @@ -24,5 +24,6 @@
<element name="disabledUpdateQty" type="button" selector=".order-creditmemo-tables tfoot button[data-ui-id='order-items-update-button'].disabled" timeout="30"/>
<element name="nameColumn" type="text" selector=".order-creditmemo-tables .product-title"/>
<element name="skuColumn" type="text" selector=".order-creditmemo-tables .product-sku-block"/>
<element name="childItemQtyToRefund" type="input" selector=".order-creditmemo-tables tr:nth-child({{row}}) td .qty-input" parameterized="true"/>
</section>
</sections>
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="AdminCreateCreditmemoWithBundleProductTest">
<annotations>
<title value="Create Creditmemo in Admin with bundle product"/>
<stories value="Github issue: #23440 fix Refund for bundle product without receiving product back"/>
<description value="Create Creditmemo for bundle product with without receiving product back(all child item qty = 0)"/>
<features value="Sales"/>
<severity value="AVERAGE"/>
<group value="Sales"/>
</annotations>

<before>
<createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/>
<createData entity="Simple_US_Customer_CA" stepKey="simpleCustomer"/>
<createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/>
<createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/>
<createData entity="ApiBundleProduct" stepKey="product"/>
<createData entity="CheckboxOption" stepKey="checkboxBundleOption">
<requiredEntity createDataKey="product"/>
</createData>
<createData entity="ApiBundleLink" stepKey="createBundleLink1">
<requiredEntity createDataKey="product"/>
<requiredEntity createDataKey="checkboxBundleOption"/>
<requiredEntity createDataKey="simple1"/>
<field key="qty">2</field>
<field key="is_default">1</field>
</createData>
<createData entity="ApiBundleLink" stepKey="createBundleLink2">
<requiredEntity createDataKey="product"/>
<requiredEntity createDataKey="checkboxBundleOption"/>
<requiredEntity createDataKey="simple2"/>
<field key="qty">2</field>
<field key="is_default">1</field>
</createData>
<createData entity="DropDownBundleOption" stepKey="dropDownBundleOption">
<requiredEntity createDataKey="product"/>
</createData>
<createData entity="ApiBundleLink" stepKey="createBundleLink3">
<requiredEntity createDataKey="product"/>
<requiredEntity createDataKey="dropDownBundleOption"/>
<requiredEntity createDataKey="simple1"/>
<field key="qty">2</field>
<field key="is_default">1</field>
</createData>
<createData entity="ApiBundleLink" stepKey="createBundleLink4">
<requiredEntity createDataKey="product"/>
<requiredEntity createDataKey="dropDownBundleOption"/>
<requiredEntity createDataKey="simple2"/>
<field key="qty">2</field>
</createData>
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
</before>
<after>
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
<deleteData createDataKey="product" stepKey="delete"/>
<deleteData createDataKey="simpleCustomer" stepKey="deleteSimpleCustomer"/>
<deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/>
<deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/>
</after>

<actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToNewOrderWithExistingCustomer">
<argument name="customer" value="$$simpleCustomer$$"/>
</actionGroup>
<actionGroup ref="AddBundleProductToOrderAndCheckPriceInGridActionGroup" stepKey="addBundleProductToOrder">
<argument name="product" value="$$product$$"/>
<argument name="quantity" value="1"/>
<argument name="price" value="$738.00"/>
</actionGroup>
<actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/>
<actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/>
<actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startInvoice"/>
<actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/>
<grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/>
<actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder">
<argument name="orderId" value="$grabOrderId"/>
</actionGroup>
<actionGroup ref="AdminOpenAndFillCreditMemoRefundBundleWithQtyActionGroup" stepKey="fillCreditMemoRefund">
<argument name="itemQtyToRefund" value="0"/>
<argument name="rowNumberItemOne" value="3"/>
<argument name="rowNumberItemTwo" value="5"/>
<argument name="rowNumberItemThree" value="6"/>
<argument name="adjustmentRefund" value="10"/>
</actionGroup>
<actionGroup ref="SubmitCreditMemoActionGroup" stepKey="submitCreditMemo" />

<actionGroup ref="AdminOpenCreditMemoFromOrderPageActionGroup" stepKey="openCreditMemo" />
<scrollTo selector="{{AdminCreditMemoViewTotalSection.subtotal}}" stepKey="scrollToTotal"/>
<actionGroup ref="AssertAdminCreditMemoViewPageTotalsActionGroup" stepKey="assertCreditMemoViewPageTotals">
<argument name="subtotal" value="$0.00"/>
<argument name="adjustmentRefund" value="$10.00"/>
<argument name="adjustmentFee" value="$0.00"/>
<argument name="grandTotal" value="$10.00"/>
</actionGroup>
</test>
</tests>
123 changes: 123 additions & 0 deletions app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoFactoryTest.php
@@ -0,0 +1,123 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Sales\Test\Unit\Model\Order;

use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\Sales\Model\Order\CreditmemoFactory;
use Magento\Sales\Model\Order\Item;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;

/**
* Unit test for creditmemo factory class.
*/
class CreditmemoFactoryTest extends TestCase
{
/**
* @var CreditmemoFactory
*/
protected $subject;

/**
* @var ReflectionMethod
*/
protected $testMethod;

/**
* @var Item|MockObject
*/
protected $orderItemMock;

/**
* @var Item|MockObject
*/
protected $orderChildItemOneMock;

/**
* @var Item|MockObject
*/
protected $orderChildItemTwoMock;

/**
* @inheritDoc
*/
protected function setUp(): void
{
$this->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
)
);
}
}

0 comments on commit f7fafca

Please sign in to comment.