Skip to content

Commit b5e99d2

Browse files
Merge pull request #10255 from magento-gl/spartans_pr_02122025
[Spartans] BugFixes Delivery
2 parents 9bcd880 + 251002a commit b5e99d2

File tree

12 files changed

+1508
-5
lines changed

12 files changed

+1508
-5
lines changed

app/code/Magento/ImportExport/Controller/Adminhtml/Import/Start.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public function execute()
124124

125125
$this->addErrorMessages($resultBlock, $errorAggregator);
126126
$resultBlock->addSuccess(__('Import successfully done'));
127+
$this->_eventManager->dispatch('log_admin_import');
127128
}
128129

129130
return $resultLayout;

app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function execute()
5151
$import = $this->getImport()->setData($data);
5252
try {
5353
$source = $import->uploadFileAndGetSource();
54+
$this->_eventManager->dispatch('log_admin_import');
5455
$this->processValidationResult($import->validateSource($source), $resultBlock);
5556
$ids = $import->getValidatedIds();
5657
if (count($ids) > 0) {

app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Import/ValidateTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Magento\ImportExport\Model\Import;
2828
use Magento\ImportExport\Model\Import\AbstractSource;
2929
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
30+
use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
3031

3132
/**
3233
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -83,6 +84,11 @@ class ValidateTest extends TestCase
8384
*/
8485
private $abstractSourceMock;
8586

87+
/**
88+
* @var EventManagerInterface|MockObject
89+
*/
90+
private $eventManagerMock;
91+
8692
protected function setUp(): void
8793
{
8894
$objectManagerHelper = new ObjectManagerHelper($this);
@@ -147,6 +153,13 @@ protected function setUp(): void
147153
->disableOriginalConstructor()
148154
->getMockForAbstractClass();
149155

156+
$this->eventManagerMock = $this->getMockBuilder(EventManagerInterface::class)
157+
->getMockForAbstractClass();
158+
159+
$this->contextMock->expects($this->any())
160+
->method('getEventManager')
161+
->willReturn($this->eventManagerMock);
162+
150163
$this->validate = new Validate(
151164
$this->contextMock,
152165
$this->reportProcessorMock,
@@ -337,6 +350,10 @@ public function testFileVerifiedWithImport()
337350
->method('getAllErrors')
338351
->willReturn($errorAggregatorMock);
339352

353+
$this->eventManagerMock->expects($this->once())
354+
->method('dispatch')
355+
->with('log_admin_import');
356+
340357
$this->resultFactoryMock->expects($this->any())
341358
->method('create')
342359
->with(ResultFactory::TYPE_LAYOUT)

app/code/Magento/Sales/Controller/Adminhtml/Order/Create.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ protected function _processData()
168168
* @SuppressWarnings(PHPMD.NPathComplexity)
169169
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
170170
*/
171+
// phpcs:disable Generic.Metrics.NestingLevel
171172
protected function _processActionData($action = null)
172173
{
173174
$eventData = [
@@ -253,7 +254,62 @@ protected function _processActionData($action = null)
253254
) {
254255
$items = $this->getRequest()->getPost('item');
255256
$items = $this->_processFiles($items);
256-
$this->_getOrderCreateModel()->addProducts($items);
257+
/**
258+
* Filter out products that are already in the quote with required options
259+
* when the current bulk payload does not contain any options for them.
260+
* This avoids accidental re-adding of previously configured products with qty=1.
261+
*/
262+
if (is_array($items)) {
263+
$filtered = [];
264+
foreach ($items as $productId => $config) {
265+
$config = is_array($config) ? $config : [];
266+
$hasOptionsInConfig = false;
267+
if (isset($config['options']) && is_array($config['options'])) {
268+
$opts = $config['options'];
269+
unset($opts['files_prefix']);
270+
$opts = array_filter(
271+
$opts,
272+
function ($v) {
273+
if (is_array($v)) {
274+
return !empty($v);
275+
}
276+
277+
return $v !== '' && $v !== null;
278+
}
279+
);
280+
$hasOptionsInConfig = !empty($opts);
281+
}
282+
if ($this->_getQuote()->hasProductId((int)$productId) && !$hasOptionsInConfig) {
283+
try {
284+
/** @var \Magento\Catalog\Model\Product $product */
285+
$product = $this
286+
->_objectManager
287+
->create(\Magento\Catalog\Model\Product::class)
288+
->load($productId);
289+
if ($product->getId() && $product->getHasOptions()) {
290+
$hasRequired = false;
291+
foreach ($product->getOptions() as $option) {
292+
if ($option->getIsRequire()) {
293+
$hasRequired = true;
294+
break;
295+
}
296+
}
297+
if ($hasRequired) {
298+
continue;
299+
}
300+
}
301+
//phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
302+
} catch (\Throwable $e) {
303+
// Intentionally swallow any exception during pre-check to allow normal add flow.
304+
}
305+
}
306+
$filtered[$productId] = $config;
307+
}
308+
$items = $filtered;
309+
}
310+
if (!empty($items)) {
311+
$this->_getOrderCreateModel()->addProducts($items);
312+
}
257313
}
258314

259315
/**
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Weee\Plugin\ConfigurableProduct\Block\Product\View\Type;
9+
10+
use Magento\ConfigurableProduct\Block\Product\View\Type\Configurable as ConfigurableBlock;
11+
use Magento\Framework\Json\DecoderInterface;
12+
use Magento\Framework\Json\EncoderInterface;
13+
use Magento\Weee\Helper\Data as WeeeHelper;
14+
use Magento\Catalog\Model\Product;
15+
16+
/**
17+
* Plugin to add FPT data to configurable product JSON config
18+
*
19+
* @SuppressWarnings(PHPMD.StaticAccess)
20+
* phpcs:disable Magento2.Functions.StaticFunction
21+
*/
22+
class Configurable
23+
{
24+
/**
25+
* @param WeeeHelper $weeeHelper
26+
* @param EncoderInterface $jsonEncoder
27+
* @param DecoderInterface $jsonDecoder
28+
*/
29+
public function __construct(
30+
private readonly WeeeHelper $weeeHelper,
31+
private readonly EncoderInterface $jsonEncoder,
32+
private readonly DecoderInterface $jsonDecoder
33+
) {
34+
}
35+
36+
/**
37+
* Add FPT/WEEE data to option prices
38+
*
39+
* @param ConfigurableBlock $subject
40+
* @param string $result
41+
* @return string
42+
*/
43+
public function afterGetJsonConfig(
44+
ConfigurableBlock $subject,
45+
string $result
46+
): string {
47+
$config = $this->jsonDecoder->decode($result);
48+
49+
if (!$this->shouldProcessWeee($config)) {
50+
return $result;
51+
}
52+
53+
foreach ($subject->getAllowProducts() as $product) {
54+
$productId = (string)$product->getId();
55+
56+
if (!isset($config['optionPrices'][$productId])) {
57+
continue;
58+
}
59+
60+
$this->injectWeeeData($config, $productId, $product);
61+
}
62+
63+
return $this->jsonEncoder->encode($config);
64+
}
65+
66+
/**
67+
* Check if WEEE should be processed
68+
*
69+
* @param array|null $config
70+
* @return bool
71+
*/
72+
private function shouldProcessWeee(?array $config): bool
73+
{
74+
return !empty($config['optionPrices']) && $this->weeeHelper->isEnabled();
75+
}
76+
77+
/**
78+
* Inject processed WEEE data into config
79+
*
80+
* @param array $config
81+
* @param string $productId
82+
* @param Product $product
83+
* @return void
84+
*/
85+
private function injectWeeeData(array &$config, string $productId, Product $product): void
86+
{
87+
$attributes = $this->weeeHelper->getProductWeeeAttributesForDisplay($product);
88+
89+
if (empty($attributes)) {
90+
return;
91+
}
92+
93+
$weeeData = $this->processWeeeAttributes($attributes);
94+
95+
$this->appendFormattedWeee(
96+
$config['optionPrices'][$productId]['finalPrice'],
97+
$config['priceFormat'],
98+
$weeeData
99+
);
100+
}
101+
102+
/**
103+
* Convert raw attribute objects into array data
104+
*
105+
* @param array $weeeAttributes
106+
* @return array
107+
*/
108+
private function processWeeeAttributes(array $weeeAttributes): array
109+
{
110+
$processed = [];
111+
$total = 0.0;
112+
113+
foreach ($weeeAttributes as $attribute) {
114+
$amount = (float)$attribute->getAmount();
115+
$name = (string)($attribute->getData('name') ?: 'FPT');
116+
117+
$processed[] = [
118+
'name' => $name,
119+
'amount' => $amount,
120+
'amount_excl_tax' => (float)$attribute->getAmountExclTax(),
121+
'tax_amount' => (float)$attribute->getTaxAmount(),
122+
];
123+
124+
$total += $amount;
125+
}
126+
127+
return ['attributes' => $processed, 'total' => $total];
128+
}
129+
130+
/**
131+
* Add formatted WEEE data to price array
132+
*
133+
* @param array $finalPrice
134+
* @param array $priceFormat
135+
* @param array $weeeData
136+
* @return void
137+
*/
138+
private function appendFormattedWeee(
139+
array &$finalPrice,
140+
array $priceFormat,
141+
array $weeeData
142+
): void {
143+
$finalAmount = (float)$finalPrice['amount'];
144+
$baseAmount = $finalAmount - $weeeData['total'];
145+
146+
// Format each attribute
147+
$formattedAttrs = array_map(
148+
fn($attr) => [
149+
'name' => $attr['name'],
150+
'amount' => $attr['amount'],
151+
'formatted' => $this->formatPrice($attr['amount'], $priceFormat)
152+
],
153+
$weeeData['attributes']
154+
);
155+
156+
$finalPrice = array_merge(
157+
$finalPrice,
158+
[
159+
'weeeAmount' => $weeeData['total'],
160+
'weeeAttributes' => $formattedAttrs,
161+
'amountWithoutWeee' => $baseAmount,
162+
'formattedWithoutWeee' => $this->formatPrice($baseAmount, $priceFormat),
163+
'formattedWithWeee' => $this->formatPrice($finalAmount, $priceFormat),
164+
]
165+
);
166+
}
167+
168+
/**
169+
* Format price using the store's price format
170+
*
171+
* @param float $amount
172+
* @param array $priceFormat
173+
* @return string
174+
*/
175+
private function formatPrice(float $amount, array $priceFormat): string
176+
{
177+
$pattern = $priceFormat['pattern'] ?? '%s';
178+
$precision = $priceFormat['precision'] ?? 2;
179+
$decimalSymbol = $priceFormat['decimalSymbol'] ?? '.';
180+
$groupSymbol = $priceFormat['groupSymbol'] ?? ',';
181+
182+
return str_replace(
183+
'%s',
184+
number_format($amount, $precision, $decimalSymbol, $groupSymbol),
185+
$pattern
186+
);
187+
}
188+
}

0 commit comments

Comments
 (0)