Skip to content

Commit 51f753d

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4157' into PR_2025_09_15_muntianu
2 parents 566132e + 3264e68 commit 51f753d

File tree

2 files changed

+84
-4
lines changed
  • app/code/Magento/Sales
    • Controller/Adminhtml/Order/Creditmemo
    • Test/Unit/Controller/Adminhtml/Order/Creditmemo

2 files changed

+84
-4
lines changed

app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2014 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo;
77

@@ -10,7 +10,13 @@
1010
use Magento\Sales\Helper\Data as SalesData;
1111
use Magento\Sales\Model\Order\Creditmemo;
1212
use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender;
13+
use Magento\Catalog\Model\Product\Type\AbstractType;
14+
use Magento\Sales\Model\Order\Creditmemo\Item;
15+
use Magento\Catalog\Model\Product\Type;
1316

17+
/**
18+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
19+
*/
1420
class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface
1521
{
1622
/**
@@ -155,6 +161,9 @@ private function adjustCreditMemoItemQuantities(Creditmemo $creditMemo): void
155161
$parentQuantities = [];
156162
foreach ($items as $item) {
157163
if ($parentId = $item->getOrderItem()->getParentItemId()) {
164+
if ($this->shouldSkipQuantityAccumulation($item)) {
165+
continue;
166+
}
158167
if (empty($parentQuantities[$parentId])) {
159168
$parentQuantities[$parentId] = $item->getQty();
160169
} else {
@@ -171,4 +180,21 @@ private function adjustCreditMemoItemQuantities(Creditmemo $creditMemo): void
171180
}
172181
}
173182
}
183+
184+
/**
185+
* Check if the quantity adjustment should be skipped
186+
*
187+
* @param Item $item
188+
* @return bool
189+
*/
190+
private function shouldSkipQuantityAccumulation(Item $item): bool
191+
{
192+
$parentOrderItem = $item->getOrderItem()->getParentItem();
193+
if (!$parentOrderItem || $parentOrderItem->getProductType() !== Type::TYPE_BUNDLE) {
194+
return false;
195+
}
196+
$parentOptions = $parentOrderItem->getProductOptions();
197+
return isset($parentOptions['product_calculations']) &&
198+
$parentOptions['product_calculations'] === AbstractType::CALCULATE_PARENT;
199+
}
174200
}

app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2014 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -29,6 +29,7 @@
2929
use Magento\Sales\Model\Order\Creditmemo;
3030
use Magento\Sales\Model\Order\Creditmemo\Item;
3131
use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender;
32+
use Magento\Catalog\Model\Product\Type\AbstractType;
3233
use PHPUnit\Framework\MockObject\MockObject;
3334
use PHPUnit\Framework\TestCase;
3435

@@ -413,4 +414,57 @@ public function testExecuteEmails(
413414
}
414415
$this->assertEquals($this->resultRedirectMock, $this->_controller->execute());
415416
}
417+
418+
/**
419+
* Test execute method with bundle products
420+
*/
421+
public function testExecuteWithBundleProductCreditMemo()
422+
{
423+
$orderId = 1;
424+
$creditmemoId = 2;
425+
$invoiceId = 3;
426+
$creditmemoData = ['items' => [], 'comment_text' => ''];
427+
$this->_requestMock->expects($this->any())
428+
->method('getParam')
429+
->willReturnMap([
430+
['order_id', null, $orderId],
431+
['creditmemo_id', null, $creditmemoId],
432+
['creditmemo', null, $creditmemoData],
433+
['invoice_id', null, $invoiceId]
434+
]);
435+
436+
$this->_requestMock->expects($this->once())
437+
->method('getPost')
438+
->with('creditmemo')
439+
->willReturn($creditmemoData);
440+
$orderMock = $this->createMock(Order::class);
441+
$parentOrderItemMock = $this->createMock(Order\Item::class);
442+
$parentOrderItemMock->expects($this->any())->method('getProductType')->willReturn('bundle');
443+
$parentOrderItemMock->expects($this->any())
444+
->method('getProductOptions')
445+
->willReturn([
446+
'product_calculations' => AbstractType::CALCULATE_PARENT
447+
]);
448+
$childOrderItemMock = $this->createMock(Order\Item::class);
449+
$childOrderItemMock->expects($this->any())->method('getParentItemId')->willReturn(1);
450+
$childOrderItemMock->expects($this->any())->method('getParentItem')->willReturn($parentOrderItemMock);
451+
$creditMemoItemMock = $this->createMock(Item::class);
452+
$creditMemoItemMock->expects($this->any())->method('getOrderItem')->willReturn($childOrderItemMock);
453+
$creditMemoItemMock->expects($this->never())->method('setQty');
454+
$creditmemoMock = $this->createMock(Creditmemo::class);
455+
$creditmemoMock->expects($this->once())->method('isValidGrandTotal')->willReturn(true);
456+
$creditmemoMock->expects($this->once())->method('getOrder')->willReturn($orderMock);
457+
$creditmemoMock->expects($this->once())->method('getOrderId')->willReturn($orderId);
458+
$creditmemoMock->expects($this->once())->method('getAllItems')->willReturn([$creditMemoItemMock]);
459+
$this->memoLoaderMock->expects($this->once())->method('load')->willReturn($creditmemoMock);
460+
$this->resultRedirectFactoryMock->expects($this->once())
461+
->method('create')
462+
->willReturn($this->resultRedirectMock);
463+
$this->resultRedirectMock->expects($this->once())
464+
->method('setPath')
465+
->with('sales/order/view', ['order_id' => $orderId])
466+
->willReturnSelf();
467+
$result = $this->_controller->execute();
468+
$this->assertEquals($this->resultRedirectMock, $result);
469+
}
416470
}

0 commit comments

Comments
 (0)