Skip to content

Commit 36c5f34

Browse files
authored
Merge branch 'magento-commerce:2.4-develop' into ACP2E-1541
2 parents 183d7c9 + 09384df commit 36c5f34

File tree

74 files changed

+2692
-242
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+2692
-242
lines changed

app/code/Magento/Backend/Block/Dashboard/Totals.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Reports\Model\ResourceModel\Order\Collection;
1414
use Magento\Reports\Model\ResourceModel\Order\CollectionFactory;
1515
use Magento\Store\Model\Store;
16+
use Magento\Framework\App\ObjectManager;
1617

1718
/**
1819
* Adminhtml dashboard totals bar
@@ -31,19 +32,27 @@ class Totals extends Bar
3132
*/
3233
protected $_moduleManager;
3334

35+
/**
36+
* @var Period
37+
*/
38+
private $period;
39+
3440
/**
3541
* @param Context $context
3642
* @param CollectionFactory $collectionFactory
3743
* @param Manager $moduleManager
3844
* @param array $data
45+
* @param Period|null $period
3946
*/
4047
public function __construct(
4148
Context $context,
4249
CollectionFactory $collectionFactory,
4350
Manager $moduleManager,
44-
array $data = []
51+
array $data = [],
52+
?Period $period = null
4553
) {
4654
$this->_moduleManager = $moduleManager;
55+
$this->period = $period ?? ObjectManager::getInstance()->get(Period::class);
4756
parent::__construct($context, $collectionFactory, $data);
4857
}
4958

@@ -63,7 +72,8 @@ protected function _prepareLayout()
6372
) || $this->getRequest()->getParam(
6473
'group'
6574
);
66-
$period = $this->getRequest()->getParam('period', Period::PERIOD_24_HOURS);
75+
$firstPeriod = array_key_first($this->period->getDatePeriods());
76+
$period = $this->getRequest()->getParam('period', $firstPeriod);
6777

6878
/* @var $collection Collection */
6979
$collection = $this->_collectionFactory->create()->addCreateAtPeriodFilter(

app/code/Magento/Backend/Model/Dashboard/Chart/Date.php

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Backend\Model\Dashboard\Chart;
99

10+
use DateTimeZone;
1011
use Magento\Backend\Model\Dashboard\Period;
1112
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
1213
use Magento\Reports\Model\ResourceModel\Order\CollectionFactory;
@@ -54,40 +55,32 @@ public function getByPeriod(string $period): array
5455
'',
5556
true
5657
);
57-
5858
$timezoneLocal = $this->localeDate->getConfigTimezone();
59-
$localStartDate = new \DateTime($dateStart->format('Y-m-d H:i:s'), new \DateTimeZone($timezoneLocal));
60-
$localEndDate = new \DateTime($dateEnd->format('Y-m-d H:i:s'), new \DateTimeZone($timezoneLocal));
59+
60+
$dateStart->setTimezone(new DateTimeZone($timezoneLocal));
61+
$dateEnd->setTimezone(new DateTimeZone($timezoneLocal));
6162

6263
if ($period === Period::PERIOD_24_HOURS) {
63-
$localEndDate = new \DateTime('now', new \DateTimeZone($timezoneLocal));
64-
$localStartDate = clone $localEndDate;
65-
$localStartDate->modify('-1 day');
66-
$localStartDate->modify('+1 hour');
67-
} elseif ($period === Period::PERIOD_TODAY) {
68-
$localEndDate->modify('now');
69-
} else {
70-
$localEndDate->setTime(23, 59, 59);
71-
$localStartDate->setTime(0, 0, 0);
64+
$dateEnd->modify('-1 hour');
7265
}
7366

7467
$dates = [];
7568

76-
while ($localStartDate <= $localEndDate) {
69+
while ($dateStart <= $dateEnd) {
7770
switch ($period) {
7871
case Period::PERIOD_7_DAYS:
7972
case Period::PERIOD_1_MONTH:
80-
$d = $localStartDate->format('Y-m-d');
81-
$localStartDate->modify('+1 day');
73+
$d = $dateStart->format('Y-m-d');
74+
$dateStart->modify('+1 day');
8275
break;
8376
case Period::PERIOD_1_YEAR:
8477
case Period::PERIOD_2_YEARS:
85-
$d = $localStartDate->format('Y-m');
86-
$localStartDate->modify('first day of next month');
78+
$d = $dateStart->format('Y-m');
79+
$dateStart->modify('first day of next month');
8780
break;
8881
default:
89-
$d = $localStartDate->format('Y-m-d H:00');
90-
$localStartDate->modify('+1 hour');
82+
$d = $dateStart->format('Y-m-d H:00');
83+
$dateStart->modify('+1 hour');
9184
}
9285

9386
$dates[] = $d;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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="AdminDashboardTotalsBlockTest" extends="AdminCheckDashboardWithChartsTest">
12+
<annotations>
13+
<features value="Backend"/>
14+
<stories value="Order Totals on Magento dashboard"/>
15+
<title value="Dashboard First Shows Wrong Information about Revenue"/>
16+
<description value="Revenue on Magento dashboard page is displaying properly"/>
17+
<severity value="AVERAGE"/>
18+
<testCaseId value="ACP2E-1294"/>
19+
<useCaseId value="ACSD-46523"/>
20+
<group value="backend"/>
21+
</annotations>
22+
<remove keyForRemoval="checkQuantityWasChanged"/>
23+
<waitForElementVisible selector="{{AdminDashboardSection.dashboardTotals('Revenue')}}" stepKey="waitForRevenueAfter"/>
24+
<grabTextFrom selector="{{AdminDashboardSection.dashboardTotals('Revenue')}}" stepKey="grabRevenueAfter"/>
25+
<selectOption userInput="1m" selector="select#dashboard_chart_period" stepKey="selectOneMonthPeriod"/>
26+
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/>
27+
<selectOption userInput="today" selector="select#dashboard_chart_period" stepKey="selectTodayPeriod"/>
28+
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearAfterSelectTodayPeriod"/>
29+
<waitForElementVisible selector="{{AdminDashboardSection.dashboardTotals('Revenue')}}" stepKey="waitForRevenueAfterSelectTodayPeriod"/>
30+
<waitForElementVisible selector="{{AdminDashboardSection.dashboardTotals('Quantity')}}" stepKey="waitForQuantityAfterSelectTodayPeriod"/>
31+
<grabTextFrom selector="{{AdminDashboardSection.dashboardTotals('Revenue')}}" stepKey="grabRevenueAfterSelectTodayPeriod"/>
32+
<grabTextFrom selector="{{AdminDashboardSection.dashboardTotals('Quantity')}}" stepKey="grabQuantityAfterSelectTodayPeriod"/>
33+
<assertEquals stepKey="checkTodayRevenue">
34+
<actualResult type="const">$grabRevenueAfter</actualResult>
35+
<expectedResult type="const">$grabRevenueAfterSelectTodayPeriod</expectedResult>
36+
</assertEquals>
37+
<assertEquals stepKey="checkTodayQuantity">
38+
<actualResult type="const">$grabQuantityAfter</actualResult>
39+
<expectedResult type="const">$grabQuantityAfterSelectTodayPeriod</expectedResult>
40+
</assertEquals>
41+
</test>
42+
</tests>

app/code/Magento/Bundle/Model/ResourceModel/Selection.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
*/
66
namespace Magento\Bundle\Model\ResourceModel;
77

8-
use Magento\Framework\App\ObjectManager;
98
use Magento\Catalog\Api\Data\ProductInterface;
10-
use Magento\Framework\EntityManager\MetadataPool;
9+
use Magento\Framework\App\ObjectManager;
1110
use Magento\Framework\EntityManager\EntityManager;
11+
use Magento\Framework\EntityManager\MetadataPool;
1212
use Magento\Framework\Model\ResourceModel\Db\Context;
1313

1414
/**
@@ -141,7 +141,7 @@ public function getParentIdsByChild($childId)
141141
''
142142
)->join(
143143
['e' => $this->metadataPool->getMetadata(ProductInterface::class)->getEntityTable()],
144-
'e.' . $metadata->getLinkField() . ' = ' . $this->getMainTable() . '.parent_product_id',
144+
'e.' . $metadata->getLinkField() . ' = ' . $this->getMainTable() . '.parent_product_id',
145145
['e.entity_id as parent_product_id']
146146
)->where(
147147
$this->getMainTable() . '.product_id IN(?)',
@@ -174,10 +174,11 @@ public function saveSelectionPrice($item)
174174
$values = [
175175
'selection_id' => $item->getSelectionId(),
176176
'website_id' => $item->getWebsiteId(),
177-
'selection_price_type' => $item->getSelectionPriceType(),
178-
'selection_price_value' => $item->getSelectionPriceValue(),
177+
'selection_price_type' => $item->getSelectionPriceType() ?? 0,
178+
'selection_price_value' => $item->getSelectionPriceValue() ?? 0,
179179
'parent_product_id' => $item->getParentProductId(),
180180
];
181+
181182
$connection->insertOnDuplicate(
182183
$this->getTable('catalog_product_bundle_selection_price'),
183184
$values,
@@ -187,7 +188,8 @@ public function saveSelectionPrice($item)
187188
}
188189

189190
/**
190-
* {@inheritdoc}
191+
* @inheritdoc
192+
*
191193
* @since 100.2.0
192194
*/
193195
public function save(\Magento\Framework\Model\AbstractModel $object)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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\Bundle\Test\Unit\Model\ResourceModel;
9+
10+
use Codeception\PHPUnit\TestCase;
11+
use Magento\Bundle\Model\ResourceModel\Selection as ResourceSelection;
12+
use Magento\Bundle\Model\Selection;
13+
use Magento\Framework\App\ResourceConnection;
14+
use Magento\Framework\DB\Adapter\AdapterInterface;
15+
use Magento\Framework\EntityManager\MetadataPool;
16+
use Magento\Framework\Model\ResourceModel\Db\Context;
17+
18+
class SelectionTest extends TestCase
19+
{
20+
/**
21+
* @var Context|Context&\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject
22+
*/
23+
private Context $context;
24+
25+
/**
26+
* @var MetadataPool|MetadataPool&\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject
27+
*/
28+
private MetadataPool $metadataPool;
29+
30+
/**
31+
* @inheritdoc
32+
*/
33+
protected function setUp(): void
34+
{
35+
parent::setUp();
36+
37+
$this->context = $this->createMock(Context::class);
38+
$this->metadataPool = $this->createMock(MetadataPool::class);
39+
}
40+
41+
public function testSaveSelectionPrice()
42+
{
43+
$item = $this->getMockBuilder(Selection::class)
44+
->disableOriginalConstructor()
45+
->addMethods([
46+
'getSelectionId',
47+
'getWebsiteId',
48+
'getSelectionPriceType',
49+
'getSelectionPriceValue',
50+
'getParentProductId',
51+
'getDefaultPriceScope'])
52+
->getMock();
53+
$values = [
54+
'selection_id' => 1,
55+
'website_id' => 1,
56+
'selection_price_type' => null,
57+
'selection_price_value' => null,
58+
'parent_product_id' => 1,
59+
];
60+
$item->expects($this->once())->method('getDefaultPriceScope')->willReturn(false);
61+
$item->expects($this->once())->method('getSelectionId')->willReturn($values['selection_id']);
62+
$item->expects($this->once())->method('getWebsiteId')->willReturn($values['website_id']);
63+
$item->expects($this->once())->method('getSelectionPriceType')->willReturn($values['selection_price_type']);
64+
$item->expects($this->once())->method('getSelectionPriceValue')->willReturn($values['selection_price_value']);
65+
$item->expects($this->once())->method('getParentProductId')->willReturn($values['parent_product_id']);
66+
67+
$connection = $this->createMock(AdapterInterface::class);
68+
$connection->expects($this->once())
69+
->method('insertOnDuplicate')
70+
->with(
71+
'catalog_product_bundle_selection_price',
72+
$this->callback(function ($insertValues) {
73+
return $insertValues['selection_price_type'] === 0 && $insertValues['selection_price_value'] === 0;
74+
}),
75+
['selection_price_type', 'selection_price_value']
76+
);
77+
78+
$parentResources = $this->createMock(ResourceConnection::class);
79+
$parentResources->expects($this->once())->method('getConnection')->willReturn($connection);
80+
$parentResources->expects($this->once())->method('getTableName')
81+
->with('catalog_product_bundle_selection_price', 'test_connection_name')
82+
->willReturn('catalog_product_bundle_selection_price');
83+
$this->context->expects($this->once())->method('getResources')->willReturn($parentResources);
84+
85+
$selection = new ResourceSelection($this->context, $this->metadataPool, 'test_connection_name');
86+
$selection->saveSelectionPrice($item);
87+
}
88+
}

app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ protected function parseSelections($rowData, $entityId)
201201
$this->_cachedOptions[$entityId][$option['name']] = $option;
202202
$this->_cachedOptions[$entityId][$option['name']]['selections'] = [];
203203
}
204-
$this->_cachedOptions[$entityId][$option['name']]['selections'][] = $option;
204+
$this->_cachedOptions[$entityId][$option['name']]['selections'][$option['sku']] = $option;
205205
$this->_cachedOptionSelectQuery[] = [(int)$entityId, $option['name']];
206206
}
207207
}

app/code/Magento/BundleImportExport/Test/Mftf/Data/ImportData.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,17 @@
127127
<data key="bundleOption2Required">false</data>
128128
<data key="bundleOption2NumberOfProducts">1</data>
129129
</entity>
130+
<entity name="ImportProduct_Bundle2" type="product">
131+
<data key="fileName">catalog_import_duplicate_bundle_products.csv</data>
132+
<data key="name">import-product-bundle-with-duplicates</data>
133+
<data key="sku">import-product-bundle2</data>
134+
<data key="type_id">bundle</data>
135+
<data key="attribute_set_id">4</data>
136+
<data key="attributeSetText">Default</data>
137+
<data key="urlKey">import-product-bundle2</data>
138+
<data key="bundleOption1Title">Bundle Option A</data>
139+
<data key="bundleOption1InputType">radio</data>
140+
<data key="bundleOption1Required">true</data>
141+
<data key="bundleOption1NumberOfProducts">2</data>
142+
</entity>
130143
</entities>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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="AdminImportDuplicateBundleProductsWithoutImagesTest">
12+
<annotations>
13+
<title value="Bundle product import issue"/>
14+
<stories value="Asserting bundle product import functionality and verify data in product option "/>
15+
<description value="The merchant is having issues with importing Bundled Products via CSV. When they import a CSV where the same SKU is duplicated, duplicated records are created for the product option."/>
16+
<testCaseId value="AC-7646"/>
17+
<useCaseId value="ACP2E-1478"/>
18+
<features value="Sales"/>
19+
<severity value="AVERAGE"/>
20+
<group value="importExport"/>
21+
<group value="Bundle"/>
22+
</annotations>
23+
<before>
24+
<!-- Create Simple Product1 -->
25+
<createData entity="_defaultCategory" stepKey="createCategory"/>
26+
<createData entity="SimpleProduct" stepKey="createSimpleProduct1">
27+
<field key="name">SimpleProduct1</field>
28+
<field key="sku">SimpleProduct1</field>
29+
<requiredEntity createDataKey="createCategory"/>
30+
</createData>
31+
<!-- Create Simple Product2 -->
32+
<createData entity="SimpleProduct" stepKey="createSimpleProduct2">
33+
<field key="name">SimpleProduct2</field>
34+
<field key="sku">SimpleProduct2</field>
35+
<requiredEntity createDataKey="createCategory"/>
36+
</createData>
37+
<!-- Login as Admin -->
38+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
39+
</before>
40+
<after>
41+
<deleteData createDataKey="createCategory" stepKey="deleteCategory"/>
42+
<deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/>
43+
<deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/>
44+
<deleteData url="/V1/products/{{ImportProduct_Bundle2.urlKey}}" stepKey="deleteImportedBundleProduct"/>
45+
<actionGroup ref="NavigateToAndResetProductGridToDefaultViewActionGroup" stepKey="navigateToAndResetProductGridToDefaultView"/>
46+
<!-- Logout -->
47+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/>
48+
</after>
49+
<!-- Import Bundle Product & Assert No Errors -->
50+
<actionGroup ref="AdminNavigateToImportPageActionGroup" stepKey="navigateToImportPage"/>
51+
<actionGroup ref="AdminFillImportFormActionGroup" stepKey="fillImportForm">
52+
<argument name="importFile" value="{{ImportProduct_Bundle2.fileName}}"/>
53+
</actionGroup>
54+
<actionGroup ref="AdminClickCheckDataImportActionGroup" stepKey="clickCheckData"/>
55+
<see selector="{{AdminImportValidationMessagesSection.success}}" userInput="{{ImportCommonMessages.validFile}}" stepKey="seeCheckDataResultMessage"/>
56+
<dontSeeElementInDOM selector="{{AdminImportValidationMessagesSection.importErrorList}}" stepKey="dontSeeErrorMessage"/>
57+
<actionGroup ref="AdminClickImportActionGroup" stepKey="clickImport"/>
58+
<see selector="{{AdminImportValidationMessagesSection.messageByType('success')}}" userInput="{{ImportCommonMessages.success}}" stepKey="seeImportMessage"/>
59+
<dontSeeElementInDOM selector="{{AdminImportValidationMessagesSection.importErrorList}}" stepKey="dontSeeErrorMessage2"/>
60+
<!-- Reindex -->
61+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
62+
<argument name="indices" value=""/>
63+
</actionGroup>
64+
<!-- Admin: Verify Bundle Product Options Data on Edit Product Page -->
65+
<actionGroup ref="NavigateToCreatedProductEditPageActionGroup" stepKey="goToBundleProductEditPage">
66+
<argument name="product" value="ImportProduct_Bundle2"/>
67+
</actionGroup>
68+
<conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="conditionallyOpenSectionBundleItems"/>
69+
<scrollTo selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" stepKey="scrollUpABit"/>
70+
<actionGroup ref="AdminVerifyBundleProductOptionActionGroup" stepKey="verifyBundleProductOption1">
71+
<argument name="optionTitle" value="{{ImportProduct_Bundle.bundleOption1Title}}"/>
72+
<argument name="inputType" value="{{ImportProduct_Bundle.bundleOption1InputType}}"/>
73+
<argument name="required" value="{{ImportProduct_Bundle.bundleOption1Required}}"/>
74+
<argument name="numberOfProducts" value="{{ImportProduct_Bundle.bundleOption1NumberOfProducts}}"/>
75+
<argument name="index" value="1"/>
76+
</actionGroup>
77+
</test>
78+
</tests>

0 commit comments

Comments
 (0)