55 */
66namespace Magento \Sales \Model \ResourceModel \Report ;
77
8+ use Magento \Catalog \Model \ResourceModel \Product ;
9+ use Magento \Framework \App \ObjectManager ;
10+ use Magento \Framework \DB \Select ;
11+ use Magento \Framework \Exception \LocalizedException ;
12+ use Magento \Framework \Model \ResourceModel \Db \Context ;
13+ use Magento \Framework \Stdlib \DateTime \DateTime ;
14+ use Magento \Framework \Stdlib \DateTime \Timezone \Validator ;
15+ use Magento \Framework \Stdlib \DateTime \TimezoneInterface ;
16+ use Magento \Reports \Model \FlagFactory ;
17+ use Magento \Sales \Model \ResourceModel \Helper ;
18+ use Magento \Store \Model \Store ;
19+ use Magento \Store \Model \StoreManagerInterface ;
20+ use Psr \Log \LoggerInterface ;
21+
822/**
923 * Bestsellers report resource model
1024 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1125 */
1226class Bestsellers extends AbstractReport
1327{
14- const AGGREGATION_DAILY = 'daily ' ;
28+ public const AGGREGATION_DAILY = 'daily ' ;
1529
16- const AGGREGATION_MONTHLY = 'monthly ' ;
30+ public const AGGREGATION_MONTHLY = 'monthly ' ;
1731
18- const AGGREGATION_YEARLY = 'yearly ' ;
32+ public const AGGREGATION_YEARLY = 'yearly ' ;
1933
2034 /**
21- * @var \Magento\Catalog\Model\ResourceModel\ Product
35+ * @var Product
2236 */
2337 protected $ _productResource ;
2438
2539 /**
26- * @var \Magento\Sales\Model\ResourceModel\ Helper
40+ * @var Helper
2741 */
2842 protected $ _salesResourceHelper ;
2943
@@ -37,29 +51,36 @@ class Bestsellers extends AbstractReport
3751 ];
3852
3953 /**
40- * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
41- * @param \Psr\Log\LoggerInterface $logger
42- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
43- * @param \Magento\Reports\Model\FlagFactory $reportsFlagFactory
44- * @param \Magento\Framework\Stdlib\DateTime\Timezone\Validator $timezoneValidator
45- * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime
46- * @param \Magento\Catalog\Model\ResourceModel\Product $productResource
47- * @param \Magento\Sales\Model\ResourceModel\Helper $salesResourceHelper
54+ * @var StoreManagerInterface
55+ */
56+ protected $ storeManager ;
57+
58+ /**
59+ * @param Context $context
60+ * @param LoggerInterface $logger
61+ * @param TimezoneInterface $localeDate
62+ * @param FlagFactory $reportsFlagFactory
63+ * @param Validator $timezoneValidator
64+ * @param DateTime $dateTime
65+ * @param Product $productResource
66+ * @param Helper $salesResourceHelper
67+ * @param string|null $connectionName
4868 * @param array $ignoredProductTypes
49- * @param string $connectionName
69+ * @param StoreManagerInterface|null $storeManager
5070 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
5171 */
5272 public function __construct (
53- \Magento \Framework \Model \ResourceModel \Db \Context $ context ,
54- \Psr \Log \LoggerInterface $ logger ,
55- \Magento \Framework \Stdlib \DateTime \TimezoneInterface $ localeDate ,
56- \Magento \Reports \Model \FlagFactory $ reportsFlagFactory ,
57- \Magento \Framework \Stdlib \DateTime \Timezone \Validator $ timezoneValidator ,
58- \Magento \Framework \Stdlib \DateTime \DateTime $ dateTime ,
59- \Magento \Catalog \Model \ResourceModel \Product $ productResource ,
60- \Magento \Sales \Model \ResourceModel \Helper $ salesResourceHelper ,
61- $ connectionName = null ,
62- array $ ignoredProductTypes = []
73+ Context $ context ,
74+ LoggerInterface $ logger ,
75+ TimezoneInterface $ localeDate ,
76+ FlagFactory $ reportsFlagFactory ,
77+ Validator $ timezoneValidator ,
78+ DateTime $ dateTime ,
79+ Product $ productResource ,
80+ Helper $ salesResourceHelper ,
81+ ?string $ connectionName = null ,
82+ array $ ignoredProductTypes = [],
83+ ?StoreManagerInterface $ storeManager = null
6384 ) {
6485 parent ::__construct (
6586 $ context ,
@@ -73,6 +94,7 @@ public function __construct(
7394 $ this ->_productResource = $ productResource ;
7495 $ this ->_salesResourceHelper = $ salesResourceHelper ;
7596 $ this ->ignoredProductTypes = array_merge ($ this ->ignoredProductTypes , $ ignoredProductTypes );
97+ $ this ->storeManager = $ storeManager ?: ObjectManager::getInstance ()->get (StoreManagerInterface::class);
7698 }
7799
78100 /**
@@ -92,123 +114,161 @@ protected function _construct()
92114 * @param string|int|\DateTime|array|null $to
93115 * @return $this
94116 * @throws \Exception
95- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
96117 */
97118 public function aggregate ($ from = null , $ to = null )
98119 {
99120 $ connection = $ this ->getConnection ();
100- //$this->getConnection()->beginTransaction();
101-
102- try {
103- if ($ from !== null || $ to !== null ) {
104- $ subSelect = $ this ->_getTableDateRangeSelect (
105- $ this ->getTable ('sales_order ' ),
106- 'created_at ' ,
107- 'updated_at ' ,
108- $ from ,
109- $ to
110- );
111- } else {
112- $ subSelect = null ;
113- }
114-
115- $ this ->_clearTableByDateRange ($ this ->getMainTable (), $ from , $ to , $ subSelect );
116- // convert dates to current admin timezone
117- $ periodExpr = $ connection ->getDatePartSql (
118- $ this ->getStoreTZOffsetQuery (
119- ['source_table ' => $ this ->getTable ('sales_order ' )],
120- 'source_table.created_at ' ,
121- $ from ,
122- $ to
123- )
124- );
125- $ select = $ connection ->select ();
126-
127- $ select ->group ([$ periodExpr , 'source_table.store_id ' , 'order_item.product_id ' ]);
128-
129- $ columns = [
130- 'period ' => $ periodExpr ,
131- 'store_id ' => 'source_table.store_id ' ,
132- 'product_id ' => 'order_item.product_id ' ,
133- 'product_name ' => new \Zend_Db_Expr ('MIN(order_item.name) ' ),
134- 'product_price ' => new \Zend_Db_Expr (
135- 'MIN(IF(order_item_parent.base_price, order_item_parent.base_price, order_item.base_price)) ' .
136- '* MIN(source_table.base_to_global_rate) '
137- ),
138- 'qty_ordered ' => new \Zend_Db_Expr ('SUM(order_item.qty_ordered) ' ),
139- ];
140-
141- $ select ->from (
142- ['source_table ' => $ this ->getTable ('sales_order ' )],
143- $ columns
144- )->joinInner (
145- ['order_item ' => $ this ->getTable ('sales_order_item ' )],
146- 'order_item.order_id = source_table.entity_id ' ,
147- []
148- )->joinLeft (
149- ['order_item_parent ' => $ this ->getTable ('sales_order_item ' )],
150- 'order_item.parent_item_id = order_item_parent.item_id ' ,
151- []
152- )->where (
153- 'source_table.state != ? ' ,
154- \Magento \Sales \Model \Order::STATE_CANCELED
155- )->where (
156- 'order_item.product_type NOT IN(?) ' ,
157- $ this ->ignoredProductTypes
158- );
121+ $ this ->clearByDateRange ($ from , $ to );
122+ foreach ($ this ->storeManager ->getStores (true ) as $ store ) {
123+ $ this ->processStoreAggregate ($ store ->getId (), $ from , $ to );
124+ }
159125
160- if ($ subSelect !== null ) {
161- $ select ->having ($ this ->_makeConditionFromDateRangeSelect ($ subSelect , 'period ' ));
162- }
163-
164- $ select ->useStraightJoin ();
165- // important!
166- $ insertQuery = $ select ->insertFromSelect ($ this ->getMainTable (), array_keys ($ columns ));
167- $ connection ->query ($ insertQuery );
168-
169- $ columns = [
170- 'period ' => 'period ' ,
171- 'store_id ' => new \Zend_Db_Expr (\Magento \Store \Model \Store::DEFAULT_STORE_ID ),
172- 'product_id ' => 'product_id ' ,
173- 'product_name ' => new \Zend_Db_Expr ('MIN(product_name) ' ),
174- 'product_price ' => new \Zend_Db_Expr ('MIN(product_price) ' ),
175- 'qty_ordered ' => new \Zend_Db_Expr ('SUM(qty_ordered) ' ),
176- ];
177-
178- $ select ->reset ();
179- $ select ->from (
180- $ this ->getMainTable (),
181- $ columns
182- )->where (
183- 'store_id <> ? ' ,
184- \Magento \Store \Model \Store::DEFAULT_STORE_ID
185- );
126+ $ columns = [
127+ 'period ' => 'period ' ,
128+ 'store_id ' => new \Zend_Db_Expr (Store::DEFAULT_STORE_ID ),
129+ 'product_id ' => 'product_id ' ,
130+ 'product_name ' => new \Zend_Db_Expr ('MIN(product_name) ' ),
131+ 'product_price ' => new \Zend_Db_Expr ('MIN(product_price) ' ),
132+ 'qty_ordered ' => new \Zend_Db_Expr ('SUM(qty_ordered) ' ),
133+ ];
186134
187- if ($ subSelect !== null ) {
188- $ select ->where ($ this ->_makeConditionFromDateRangeSelect ($ subSelect , 'period ' ));
189- }
190-
191- $ select ->group (['period ' , 'product_id ' ]);
192- $ insertQuery = $ select ->insertFromSelect ($ this ->getMainTable (), array_keys ($ columns ));
193- $ connection ->query ($ insertQuery );
194-
195- // update rating
196- $ this ->_updateRatingPos (self ::AGGREGATION_DAILY );
197- $ this ->_updateRatingPos (self ::AGGREGATION_MONTHLY );
198- $ this ->_updateRatingPos (self ::AGGREGATION_YEARLY );
199- $ this ->_setFlagData (\Magento \Reports \Model \Flag::REPORT_BESTSELLERS_FLAG_CODE );
200- } catch (\Exception $ e ) {
201- throw $ e ;
135+ $ select = $ connection ->select ();
136+ $ select ->reset ();
137+ $ select ->from (
138+ $ this ->getMainTable (),
139+ $ columns
140+ )->where (
141+ 'store_id <> ? ' ,
142+ Store::DEFAULT_STORE_ID
143+ );
144+ $ subSelect = $ this ->getRangeSubSelect ($ from , $ to );
145+ if ($ subSelect !== null ) {
146+ $ select ->where ($ this ->_makeConditionFromDateRangeSelect ($ subSelect , 'period ' ));
202147 }
203148
149+ $ select ->group (['period ' , 'product_id ' ]);
150+ $ insertQuery = $ select ->insertFromSelect ($ this ->getMainTable (), array_keys ($ columns ));
151+ $ connection ->query ($ insertQuery );
152+
153+ $ this ->_updateRatingPos (self ::AGGREGATION_DAILY );
154+ $ this ->_updateRatingPos (self ::AGGREGATION_MONTHLY );
155+ $ this ->_updateRatingPos (self ::AGGREGATION_YEARLY );
156+ $ this ->_setFlagData (\Magento \Reports \Model \Flag::REPORT_BESTSELLERS_FLAG_CODE );
157+
204158 return $ this ;
205159 }
206160
161+ /**
162+ * Clear aggregate existing data by range
163+ *
164+ * @param string|int|\DateTime|array|null $from
165+ * @param string|int|\DateTime|array|null $to
166+ * @return void
167+ * @throws LocalizedException
168+ */
169+ private function clearByDateRange ($ from = null , $ to = null ): void
170+ {
171+ $ subSelect = $ this ->getRangeSubSelect ($ from , $ to );
172+ $ this ->_clearTableByDateRange ($ this ->getMainTable (), $ from , $ to , $ subSelect );
173+ }
174+
175+ /**
176+ * Get report range sub-select
177+ *
178+ * @param string|int|\DateTime|array|null $from
179+ * @param string|int|\DateTime|array|null $to
180+ * @return Select|null
181+ */
182+ private function getRangeSubSelect ($ from = null , $ to = null ): ?Select
183+ {
184+ $ subSelect = null ;
185+ if ($ from !== null || $ to !== null ) {
186+ $ subSelect = $ this ->_getTableDateRangeSelect (
187+ $ this ->getTable ('sales_order ' ),
188+ 'created_at ' ,
189+ 'updated_at ' ,
190+ $ from ,
191+ $ to
192+ );
193+ }
194+
195+ return $ subSelect ;
196+ }
197+
198+ /**
199+ * Calculate report aggregate per store
200+ *
201+ * @param int|null $storeId
202+ * @param string|int|\DateTime|array|null $from
203+ * @param string|int|\DateTime|array|null $to
204+ * @return void
205+ * @throws LocalizedException
206+ */
207+ private function processStoreAggregate (?int $ storeId , $ from = null , $ to = null ): void
208+ {
209+ $ connection = $ this ->getConnection ();
210+
211+ // convert dates to current admin timezone
212+ $ periodExpr = $ connection ->getDatePartSql (
213+ $ this ->getStoreTZOffsetQuery (
214+ ['source_table ' => $ this ->getTable ('sales_order ' )],
215+ 'source_table.created_at ' ,
216+ $ from ,
217+ $ to
218+ )
219+ );
220+ $ select = $ connection ->select ();
221+ $ subSelect = $ this ->getRangeSubSelect ($ from , $ to );
222+
223+ $ select ->group ([$ periodExpr , 'source_table.store_id ' , 'order_item.product_id ' ]);
224+
225+ $ columns = [
226+ 'period ' => $ periodExpr ,
227+ 'store_id ' => 'source_table.store_id ' ,
228+ 'product_id ' => 'order_item.product_id ' ,
229+ 'product_name ' => new \Zend_Db_Expr ('MIN(order_item.name) ' ),
230+ 'product_price ' => new \Zend_Db_Expr (
231+ 'MIN(IF(order_item_parent.base_price, order_item_parent.base_price, order_item.base_price)) ' .
232+ '* MIN(source_table.base_to_global_rate) '
233+ ),
234+ 'qty_ordered ' => new \Zend_Db_Expr ('SUM(order_item.qty_ordered) ' ),
235+ ];
236+
237+ $ select ->from (
238+ ['source_table ' => $ this ->getTable ('sales_order ' )],
239+ $ columns
240+ )->joinInner (
241+ ['order_item ' => $ this ->getTable ('sales_order_item ' )],
242+ 'order_item.order_id = source_table.entity_id ' ,
243+ []
244+ )->joinLeft (
245+ ['order_item_parent ' => $ this ->getTable ('sales_order_item ' )],
246+ 'order_item.parent_item_id = order_item_parent.item_id ' ,
247+ []
248+ )->where (
249+ "source_table.entity_id IN (SELECT entity_id FROM " . $ this ->getTable ('sales_order ' ) .
250+ " WHERE store_id = " . $ storeId .
251+ " AND state != ' " . \Magento \Sales \Model \Order::STATE_CANCELED . "' " .
252+ ($ subSelect !== null ?
253+ " AND " . $ this ->_makeConditionFromDateRangeSelect ($ subSelect , $ periodExpr ) :
254+ '' ) . ") "
255+ )->where (
256+ 'order_item.product_type NOT IN(?) ' ,
257+ $ this ->ignoredProductTypes
258+ );
259+
260+ $ select ->useStraightJoin ();
261+ // important!
262+ $ insertQuery = $ select ->insertFromSelect ($ this ->getMainTable (), array_keys ($ columns ));
263+ $ connection ->query ($ insertQuery );
264+ }
265+
207266 /**
208267 * Update rating position
209268 *
210269 * @param string $aggregation
211270 * @return $this
271+ * @throws LocalizedException
212272 */
213273 protected function _updateRatingPos ($ aggregation )
214274 {
0 commit comments