Collection.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Sales\Model\ResourceModel\Report\Bestsellers;
  7. /**
  8. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  9. */
  10. class Collection extends \Magento\Sales\Model\ResourceModel\Report\Collection\AbstractCollection
  11. {
  12. /**
  13. * Rating limit
  14. *
  15. * @var int
  16. */
  17. protected $_ratingLimit = 5;
  18. /**
  19. * Selected columns
  20. *
  21. * @var array
  22. */
  23. protected $_selectedColumns = [];
  24. /**
  25. * Tables per period
  26. *
  27. * @var array
  28. */
  29. protected $tableForPeriod = [
  30. 'daily' => 'sales_bestsellers_aggregated_daily',
  31. 'monthly' => 'sales_bestsellers_aggregated_monthly',
  32. 'yearly' => 'sales_bestsellers_aggregated_yearly',
  33. ];
  34. /**
  35. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
  36. * @param \Psr\Log\LoggerInterface $logger
  37. * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
  38. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  39. * @param \Magento\Sales\Model\ResourceModel\Report $resource
  40. * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
  41. */
  42. public function __construct(
  43. \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
  44. \Psr\Log\LoggerInterface $logger,
  45. \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
  46. \Magento\Framework\Event\ManagerInterface $eventManager,
  47. \Magento\Sales\Model\ResourceModel\Report $resource,
  48. \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
  49. ) {
  50. $resource->init($this->getTableByAggregationPeriod('daily'));
  51. parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $resource, $connection);
  52. }
  53. /**
  54. * Return ordered filed
  55. *
  56. * @return string
  57. */
  58. protected function getOrderedField()
  59. {
  60. return 'qty_ordered';
  61. }
  62. /**
  63. * Return table per period
  64. *
  65. * @param string $period
  66. * @return mixed
  67. */
  68. public function getTableByAggregationPeriod($period)
  69. {
  70. return $this->tableForPeriod[$period];
  71. }
  72. /**
  73. * Retrieve selected columns
  74. *
  75. * @return array
  76. */
  77. protected function _getSelectedColumns()
  78. {
  79. $connection = $this->getConnection();
  80. if (!$this->_selectedColumns) {
  81. if ($this->isTotals()) {
  82. $this->_selectedColumns = $this->getAggregatedColumns();
  83. } else {
  84. $this->_selectedColumns = [
  85. 'period' => sprintf('MAX(%s)', $connection->getDateFormatSql('period', '%Y-%m-%d')),
  86. $this->getOrderedField() => 'SUM(' . $this->getOrderedField() . ')',
  87. 'product_id' => 'product_id',
  88. 'product_name' => 'MAX(product_name)',
  89. 'product_price' => 'MAX(product_price)',
  90. ];
  91. if ('year' == $this->_period) {
  92. $this->_selectedColumns['period'] = $connection->getDateFormatSql('period', '%Y');
  93. } elseif ('month' == $this->_period) {
  94. $this->_selectedColumns['period'] = $connection->getDateFormatSql('period', '%Y-%m');
  95. }
  96. }
  97. }
  98. return $this->_selectedColumns;
  99. }
  100. /**
  101. * Make select object for date boundary
  102. *
  103. * @param string $from
  104. * @param string $to
  105. * @return \Magento\Framework\DB\Select
  106. */
  107. protected function _makeBoundarySelect($from, $to)
  108. {
  109. $connection = $this->getConnection();
  110. $cols = $this->_getSelectedColumns();
  111. $cols[$this->getOrderedField()] = 'SUM(' . $this->getOrderedField() . ')';
  112. $select = $connection->select()->from(
  113. $this->getResource()->getMainTable(),
  114. $cols
  115. )->where(
  116. 'period >= ?',
  117. $from
  118. )->where(
  119. 'period <= ?',
  120. $to
  121. )->group(
  122. 'product_id'
  123. )->order(
  124. $this->getOrderedField() . ' DESC'
  125. )->limit(
  126. $this->_ratingLimit
  127. );
  128. $this->_applyStoresFilterToSelect($select);
  129. return $select;
  130. }
  131. /**
  132. * Init collection select
  133. *
  134. * @return $this
  135. */
  136. protected function _applyAggregatedTable()
  137. {
  138. $select = $this->getSelect();
  139. //if grouping by product, not by period
  140. if (!$this->_period) {
  141. $cols = $this->_getSelectedColumns();
  142. $cols[$this->getOrderedField()] = 'SUM(' . $this->getOrderedField() . ')';
  143. if ($this->_from || $this->_to) {
  144. $mainTable = $this->getTable($this->getTableByAggregationPeriod('daily'));
  145. $select->from($mainTable, $cols);
  146. } else {
  147. $mainTable = $this->getTable($this->getTableByAggregationPeriod('yearly'));
  148. $select->from($mainTable, $cols);
  149. }
  150. //exclude removed products
  151. $select->where(new \Zend_Db_Expr($mainTable . '.product_id IS NOT NULL'))->group(
  152. 'product_id'
  153. )->order(
  154. $this->getOrderedField() . ' ' . \Magento\Framework\DB\Select::SQL_DESC
  155. )->limit(
  156. $this->_ratingLimit
  157. );
  158. return $this;
  159. }
  160. if ('year' == $this->_period) {
  161. $mainTable = $this->getTable($this->getTableByAggregationPeriod('yearly'));
  162. $select->from($mainTable, $this->_getSelectedColumns());
  163. } elseif ('month' == $this->_period) {
  164. $mainTable = $this->getTable($this->getTableByAggregationPeriod('monthly'));
  165. $select->from($mainTable, $this->_getSelectedColumns());
  166. } else {
  167. $mainTable = $this->getTable($this->getTableByAggregationPeriod('daily'));
  168. $select->from($mainTable, $this->_getSelectedColumns());
  169. }
  170. if (!$this->isTotals()) {
  171. $select->group(['period', 'product_id']);
  172. }
  173. $select->where('rating_pos <= ?', $this->_ratingLimit);
  174. return $this;
  175. }
  176. /**
  177. * Get SQL for get record count
  178. *
  179. * @return \Magento\Framework\DB\Select
  180. */
  181. public function getSelectCountSql()
  182. {
  183. $this->_renderFilters();
  184. $select = clone $this->getSelect();
  185. $select->reset(\Magento\Framework\DB\Select::ORDER);
  186. return $this->getConnection()->select()->from($select, 'COUNT(*)');
  187. }
  188. /**
  189. * Set ids for store restrictions
  190. *
  191. * @param int|int[] $storeIds
  192. * @return $this
  193. */
  194. public function addStoreRestrictions($storeIds)
  195. {
  196. if (!is_array($storeIds)) {
  197. $storeIds = [$storeIds];
  198. }
  199. $currentStoreIds = $this->_storesIds;
  200. if (isset(
  201. $currentStoreIds
  202. ) && $currentStoreIds != \Magento\Store\Model\Store::DEFAULT_STORE_ID && $currentStoreIds != [
  203. \Magento\Store\Model\Store::DEFAULT_STORE_ID
  204. ]
  205. ) {
  206. if (!is_array($currentStoreIds)) {
  207. $currentStoreIds = [$currentStoreIds];
  208. }
  209. $this->_storesIds = array_intersect($currentStoreIds, $storeIds);
  210. } else {
  211. $this->_storesIds = $storeIds;
  212. }
  213. return $this;
  214. }
  215. /**
  216. * Redeclare parent method for applying filters after parent method
  217. * but before adding unions and calculating totals
  218. *
  219. * @return $this|\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
  220. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  221. * @SuppressWarnings(PHPMD.NPathComplexity)
  222. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  223. */
  224. protected function _beforeLoad()
  225. {
  226. parent::_beforeLoad();
  227. $this->_applyStoresFilter();
  228. if ($this->_period) {
  229. $selectUnions = [];
  230. // apply date boundaries (before calling $this->_applyDateRangeFilter())
  231. $periodFrom = $this->_from !== null ? new \DateTime($this->_from) : null;
  232. $periodTo = $this->_to !== null ? new \DateTime($this->_to) : null;
  233. if ('year' == $this->_period) {
  234. if ($periodFrom) {
  235. // not the first day of the year
  236. if ($periodFrom->format('m') != 1 || $periodFrom->format('d') != 1) {
  237. $dtFrom = clone $periodFrom;
  238. // last day of the year
  239. $dtTo = clone $periodFrom;
  240. $dtTo->setDate($dtTo->format('Y'), 12, 31);
  241. if (!$periodTo || $dtTo < $periodTo) {
  242. $selectUnions[] = $this->_makeBoundarySelect(
  243. $dtFrom->format('Y-m-d'),
  244. $dtTo->format('Y-m-d')
  245. );
  246. // first day of the next year
  247. $this->_from = clone $periodFrom;
  248. $this->_from->modify('+1 year');
  249. $this->_from->setDate($this->_from->format('Y'), 1, 1);
  250. $this->_from = $this->_from->format('Y-m-d');
  251. }
  252. }
  253. }
  254. if ($periodTo) {
  255. // not the last day of the year
  256. if ($periodTo->format('m') != 12 || $periodTo->format('d') != 31) {
  257. $dtFrom = clone $periodTo;
  258. $dtFrom->setDate($dtFrom->format('Y'), 1, 1);
  259. // first day of the year
  260. $dtTo = clone $periodTo;
  261. if (!$periodFrom || $dtFrom > $periodFrom) {
  262. $selectUnions[] = $this->_makeBoundarySelect(
  263. $dtFrom->format('Y-m-d'),
  264. $dtTo->format('Y-m-d')
  265. );
  266. // last day of the previous year
  267. $this->_to = clone $periodTo;
  268. $this->_to->modify('-1 year');
  269. $this->_to->setDate($this->_to->format('Y'), 12, 31);
  270. $this->_to = $this->_to->format('Y-m-d');
  271. }
  272. }
  273. }
  274. if ($periodFrom && $periodTo) {
  275. // the same year
  276. if ($periodTo->format('Y') == $periodFrom->format('Y')) {
  277. $dtFrom = clone $periodFrom;
  278. $dtTo = clone $periodTo;
  279. $selectUnions[] = $this->_makeBoundarySelect(
  280. $dtFrom->format('Y-m-d'),
  281. $dtTo->format('Y-m-d')
  282. );
  283. $this->getSelect()->where('1<>1');
  284. }
  285. }
  286. } elseif ('month' == $this->_period) {
  287. if ($periodFrom) {
  288. // not the first day of the month
  289. if ($periodFrom->format('d') != 1) {
  290. $dtFrom = clone $periodFrom;
  291. // last day of the month
  292. $dtTo = clone $periodFrom;
  293. $dtTo->modify('+1 month');
  294. $dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
  295. $dtTo->modify('-1 day');
  296. if (!$periodTo || $dtTo < $periodTo) {
  297. $selectUnions[] = $this->_makeBoundarySelect(
  298. $dtFrom->format('Y-m-d'),
  299. $dtTo->format('Y-m-d')
  300. );
  301. // first day of the next month
  302. $this->_from = clone $periodFrom;
  303. $this->_from->modify('+1 month');
  304. $this->_from->setDate($this->_from->format('Y'), $this->_from->format('m'), 1);
  305. $this->_from = $this->_from->format('Y-m-d');
  306. }
  307. }
  308. }
  309. if ($periodTo) {
  310. // not the last day of the month
  311. if ($periodTo->format('d') != $periodTo->format('t')) {
  312. $dtFrom = clone $periodTo;
  313. $dtFrom->setDate($dtFrom->format('Y'), $dtFrom->format('m'), 1);
  314. // first day of the month
  315. $dtTo = clone $periodTo;
  316. if (!$periodFrom || $dtFrom > $periodFrom) {
  317. $selectUnions[] = $this->_makeBoundarySelect(
  318. $dtFrom->format('Y-m-d'),
  319. $dtTo->format('Y-m-d')
  320. );
  321. // last day of the previous month
  322. $this->_to = clone $periodTo;
  323. $this->_to->setDate($this->_to->format('Y'), $this->_to->format('m'), 1);
  324. $this->_to->modify('-1 day');
  325. $this->_to = $this->_to->format('Y-m-d');
  326. }
  327. }
  328. }
  329. if ($periodFrom && $periodTo) {
  330. // the same month
  331. if ($periodTo->format('Y') == $periodFrom->format('Y') &&
  332. $periodTo->format('m') == $periodFrom->format('m')
  333. ) {
  334. $dtFrom = clone $periodFrom;
  335. $dtTo = clone $periodTo;
  336. $selectUnions[] = $this->_makeBoundarySelect(
  337. $dtFrom->format('Y-m-d'),
  338. $dtTo->format('Y-m-d')
  339. );
  340. $this->getSelect()->where('1<>1');
  341. }
  342. }
  343. }
  344. $this->_applyDateRangeFilter();
  345. // add unions to select
  346. if ($selectUnions) {
  347. $unionParts = [];
  348. $cloneSelect = clone $this->getSelect();
  349. $unionParts[] = '(' . $cloneSelect . ')';
  350. foreach ($selectUnions as $union) {
  351. $unionParts[] = '(' . $union . ')';
  352. }
  353. $this->getSelect()->reset()->union($unionParts, \Magento\Framework\DB\Select::SQL_UNION_ALL);
  354. }
  355. if ($this->isTotals()) {
  356. // calculate total
  357. $cloneSelect = clone $this->getSelect();
  358. $this->getSelect()->reset()->from($cloneSelect, $this->getAggregatedColumns());
  359. } else {
  360. // add sorting
  361. $this->getSelect()->order(['period ASC', $this->getOrderedField() . ' DESC']);
  362. }
  363. }
  364. return $this;
  365. }
  366. }