Skip to content

Commit 0f767ef

Browse files
committed
Merge branch 'ACP2E-3189' of https://github.com/adobe-commerce-tier-4/magento2ce into Tier4-09-12-2024
2 parents c4e2a58 + 8d43eb1 commit 0f767ef

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed

app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers.php

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ class Bestsellers extends AbstractReport
5555
*/
5656
protected $storeManager;
5757

58+
/**
59+
* @var array
60+
*/
61+
private array $rangesByQuery = [];
62+
5863
/**
5964
* @param Context $context
6065
* @param LoggerInterface $logger
@@ -169,7 +174,66 @@ public function aggregate($from = null, $to = null)
169174
private function clearByDateRange($from = null, $to = null): void
170175
{
171176
$subSelect = $this->getRangeSubSelect($from, $to);
172-
$this->_clearTableByDateRange($this->getMainTable(), $from, $to, $subSelect);
177+
$this->clearTableRanges($this->getMainTable(), $from, $to, $subSelect);
178+
}
179+
180+
/**
181+
* Clear table by date range
182+
*
183+
* @param string $table
184+
* @param ?string $from
185+
* @param ?string $to
186+
* @param null|Select|string $subSelect
187+
* @return void
188+
*/
189+
private function clearTableRanges($table, $from = null, $to = null, $subSelect = null): void
190+
{
191+
if ($from === null && $to === null) {
192+
$this->_truncateTable($table);
193+
return;
194+
}
195+
196+
if ($subSelect !== null) {
197+
$dataRange = $this->getRange($subSelect);
198+
$deleteCondition = $this->getConnection()->prepareSqlCondition('period', ['in' => $dataRange]);
199+
$this->getConnection()->delete($table, $deleteCondition);
200+
return;
201+
} else {
202+
$condition = [];
203+
if ($from !== null) {
204+
$condition[] = $this->getConnection()->quoteInto('period >= ?', $from);
205+
}
206+
207+
if ($to !== null) {
208+
$condition[] = $this->getConnection()->quoteInto('period <= ?', $to);
209+
}
210+
$deleteCondition = implode(' AND ', $condition);
211+
}
212+
$this->getConnection()->delete($table, $deleteCondition);
213+
}
214+
215+
/**
216+
* Get dates range to clear the table
217+
*
218+
* @param Select $select
219+
* @return array
220+
*/
221+
private function getRange(Select $select): array
222+
{
223+
$queryHash = sha1($select->__toString());
224+
if (!isset($this->rangesByQuery[$queryHash])) {
225+
226+
$connection = $this->getConnection();
227+
try {
228+
$query = $connection->query($select);
229+
$range = $query->fetchAll(\Zend_Db::FETCH_COLUMN);
230+
} catch (\Exception) {
231+
$range = [];
232+
}
233+
234+
$this->rangesByQuery[$queryHash] = $range;
235+
}
236+
return $this->rangesByQuery[$queryHash];
173237
}
174238

175239
/**
@@ -217,9 +281,14 @@ private function processStoreAggregate(?int $storeId, $from = null, $to = null):
217281
$to
218282
)
219283
);
220-
$select = $connection->select();
284+
221285
$subSelect = $this->getRangeSubSelect($from, $to);
286+
if ($subSelect) {
287+
$dataRange = $this->getRange($subSelect);
288+
$whereCondition = $connection->prepareSqlCondition($periodExpr, ['in' => $dataRange]);
289+
}
222290

291+
$select = $connection->select();
223292
$select->group([$periodExpr, 'source_table.store_id', 'order_item.product_id']);
224293

225294
$columns = [
@@ -250,7 +319,7 @@ private function processStoreAggregate(?int $storeId, $from = null, $to = null):
250319
" WHERE store_id = " . $storeId .
251320
" AND state != '" . \Magento\Sales\Model\Order::STATE_CANCELED . "'" .
252321
($subSelect !== null ?
253-
" AND " . $this->_makeConditionFromDateRangeSelect($subSelect, $periodExpr) :
322+
" AND " . $whereCondition :
254323
'') . ")"
255324
)->where(
256325
'order_item.product_type NOT IN(?)',

app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Report/BestsellersTest.php

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
use Magento\Sales\Model\ResourceModel\Report\Bestsellers;
2222
use Magento\Store\Api\Data\StoreInterface;
2323
use Magento\Store\Model\StoreManagerInterface;
24+
use PHPUnit\Framework\Attributes\DataProvider;
25+
use PHPUnit\Framework\MockObject\Exception;
2426
use PHPUnit\Framework\MockObject\MockObject;
2527
use PHPUnit\Framework\TestCase;
2628
use Psr\Log\LoggerInterface;
@@ -95,6 +97,24 @@ class BestsellersTest extends TestCase
9597
*/
9698
protected string $connectionName = 'connection_name';
9799

100+
/**
101+
* Data provider for testAggregateWithMultipleOrderDatesAndNoDates
102+
*
103+
* @return array
104+
*/
105+
public static function datesDataProvider(): array
106+
{
107+
$randomDates = [];
108+
for ($i = 0; $i < 10000; $i++) {
109+
$randomDates[] = date('Y-m-d', rand(0, time()));
110+
}
111+
return [
112+
'from-to interval' => [new \DateTime('yesterday'), new \DateTime(), $randomDates],
113+
'from interval' => [new \DateTime('yesterday'), null, $randomDates],
114+
'from interval no dates' => [new \DateTime('yesterday'), null, []]
115+
];
116+
}
117+
98118
/**
99119
* @inheritDoc
100120
*/
@@ -139,7 +159,8 @@ public function testAggregatePerStoreCalculationWithInterval(): void
139159
->willReturn($periodExpr);
140160
$connection->expects($this->any())->method('select')->willReturn($select);
141161
$query = $this->createMock(\Zend_Db_Statement_Interface::class);
142-
$connection->expects($this->exactly(3))->method('query')->willReturn($query);
162+
$query->expects($this->once())->method('fetchAll')->willReturn(['date1', 'date2']);
163+
$connection->expects($this->exactly(4))->method('query')->willReturn($query);
143164
$resource = $this->createMock(ResourceConnection::class);
144165
$resource->expects($this->any())
145166
->method('getConnection')
@@ -180,6 +201,68 @@ public function testAggregatePerStoreCalculationWithInterval(): void
180201
$this->report->aggregate($from, $to);
181202
}
182203

204+
public function testClearByDateRange()
205+
{
206+
$from = new \DateTime('yesterday');
207+
$to = new \DateTime();
208+
$periodExpr = 'DATE(DATE_ADD(`source_table`.`created_at`, INTERVAL -25200 SECOND))';
209+
210+
$connection = $this->createMock(AdapterInterface::class);
211+
$resource = $this->createMock(ResourceConnection::class);
212+
$resource->expects($this->any())
213+
->method('getConnection')
214+
->with($this->connectionName)
215+
->willReturn($connection);
216+
$this->context->expects($this->any())->method('getResources')->willReturn($resource);
217+
218+
$store = $this->createMock(StoreInterface::class);
219+
$store->expects($this->once())->method('getId')->willReturn(1);
220+
$this->storeManager->expects($this->once())->method('getStores')->with(true)->willReturn([$store]);
221+
222+
$select = $this->getMockBuilder(Select::class)
223+
->disableOriginalConstructor()
224+
->getMock();
225+
$select->expects($this->atLeastOnce())->method('from')->willReturnSelf();
226+
$select->expects($this->atLeastOnce())->method('joinInner')->willReturnSelf();
227+
$select->expects($this->atLeastOnce())->method('joinLeft')->willReturnSelf();
228+
$select->expects($this->atLeastOnce())->method('where')->willReturnSelf();
229+
$select->expects($this->atLeastOnce())->method('distinct')->willReturnSelf();
230+
$connection->expects($this->any())->method('select')->willReturn($select);
231+
232+
$date = $this->createMock(\DateTime::class);
233+
$date->expects($this->atLeastOnce())->method('format')->with('e');
234+
$this->time->expects($this->atLeastOnce())->method('scopeDate')->willReturn($date);
235+
236+
$flag = $this->createMock(Flag::class);
237+
$flag->expects($this->atLeastOnce())->method('setReportFlagCode')->willReturnSelf();
238+
$flag->expects($this->atLeastOnce())->method('unsetData')->willReturnSelf();
239+
$flag->expects($this->atLeastOnce())->method('loadSelf');
240+
$this->flagFactory->expects($this->atLeastOnce())->method('create')->willReturn($flag);
241+
242+
$query = $this->createMock(\Zend_Db_Statement_Interface::class);
243+
$query->method('fetchAll')->willReturn(['date1', 'date2']);
244+
$connection->expects($this->atLeastOnce())->method('query')->willReturn($query);
245+
$connection->expects($this->atLeastOnce())->method('getDatePartSql')->willReturn($periodExpr);
246+
247+
$connection->expects($this->once())->method('delete');
248+
249+
$this->report = new Bestsellers(
250+
$this->context,
251+
$this->logger,
252+
$this->time,
253+
$this->flagFactory,
254+
$this->validator,
255+
$this->date,
256+
$this->product,
257+
$this->helper,
258+
$this->connectionName,
259+
[],
260+
$this->storeManager
261+
);
262+
263+
$this->report->aggregate($from, $to);
264+
}
265+
183266
/**
184267
* @return void
185268
* @throws \Exception
@@ -242,4 +325,77 @@ public function testAggregatePerStoreCalculationNoInterval(): void
242325

243326
$this->report->aggregate();
244327
}
328+
329+
/**
330+
* @param \DateTime|null $from
331+
* @param \DateTime|null $to
332+
* @param array $randomDates
333+
* @return void
334+
* @throws Exception
335+
*/
336+
#[DataProvider('datesDataProvider')]
337+
public function testAggregateWithMultipleOrderDatesAndNoDates(
338+
?\DateTime $from,
339+
?\DateTime $to,
340+
array $randomDates
341+
): void {
342+
$periodExpr = 'DATE(DATE_ADD(`source_table`.`created_at`, INTERVAL -25200 SECOND))';
343+
$select = $this->getMockBuilder(Select::class)
344+
->disableOriginalConstructor()
345+
->getMock();
346+
$select->expects($this->exactly(2))->method('group');
347+
$select->expects($this->exactly(5))->method('from')->willReturn($select);
348+
$select->expects($this->exactly(3))->method('distinct')->willReturn($select);
349+
$select->expects($this->once())->method('joinInner')->willReturn($select);
350+
$select->expects($this->once())->method('joinLeft')->willReturn($select);
351+
$select->expects($this->any())->method('where')->willReturn($select);
352+
$select->expects($this->once())->method('useStraightJoin');
353+
$select->expects($this->exactly(2))->method('insertFromSelect');
354+
$connection = $this->createMock(AdapterInterface::class);
355+
$connection->expects($this->exactly(4))
356+
->method('getDatePartSql')
357+
->willReturn($periodExpr);
358+
$connection->expects($this->any())->method('select')->willReturn($select);
359+
$query = $this->createMock(\Zend_Db_Statement_Interface::class);
360+
$query->expects($this->once())->method('fetchAll')->willReturn($randomDates);
361+
$connection->expects($this->exactly(3))->method('query')->willReturn($query);
362+
$resource = $this->createMock(ResourceConnection::class);
363+
$resource->expects($this->any())
364+
->method('getConnection')
365+
->with($this->connectionName)
366+
->willReturn($connection);
367+
$this->context->expects($this->any())->method('getResources')->willReturn($resource);
368+
369+
$store = $this->createMock(StoreInterface::class);
370+
$store->expects($this->once())->method('getId')->willReturn(1);
371+
$this->storeManager->expects($this->once())->method('getStores')->with(true)->willReturn([$store]);
372+
373+
$this->helper->expects($this->exactly(3))->method('getBestsellersReportUpdateRatingPos');
374+
375+
$flag = $this->createMock(Flag::class);
376+
$flag->expects($this->once())->method('setReportFlagCode')->willReturn($flag);
377+
$flag->expects($this->once())->method('unsetData')->willReturn($flag);
378+
$flag->expects($this->once())->method('loadSelf');
379+
$this->flagFactory->expects($this->once())->method('create')->willReturn($flag);
380+
381+
$date = $this->createMock(\DateTime::class);
382+
$date->expects($this->exactly(4))->method('format')->with('e');
383+
$this->time->expects($this->exactly(4))->method('scopeDate')->willReturn($date);
384+
385+
$this->report = new Bestsellers(
386+
$this->context,
387+
$this->logger,
388+
$this->time,
389+
$this->flagFactory,
390+
$this->validator,
391+
$this->date,
392+
$this->product,
393+
$this->helper,
394+
$this->connectionName,
395+
[],
396+
$this->storeManager
397+
);
398+
399+
$this->report->aggregate($from, $to);
400+
}
245401
}

0 commit comments

Comments
 (0)