diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59c0cc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Created by .ignore support plugin (hsz.mobi) +coverage/ +vendor/ +composer.lock diff --git a/Api/Data/ProductInterface.php b/Api/Data/ProductInterface.php new file mode 100644 index 0000000..2ae4262 --- /dev/null +++ b/Api/Data/ProductInterface.php @@ -0,0 +1,13 @@ +bestsellers = $bestsellers; + $this->products = $products; + $this->storeManager = $storeManager; + $this->rating = $rating; + $this->ratingAggregated = $ratingAggregated; + } + + /** + * Get items from report + * + * @api + * + * @param string $type Type of source + * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria + * + * @return \OxCom\MagentoTopProducts\Api\Data\ProductSearchResultsInterface + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getList($type, ProductSearchCriteriaInterface $searchCriteria = null) + { + $allowed = [static::FILTER_TYPE_TOP_SELLING, static::FILTER_TYPE_TOP_FREE, static::FILTER_TYPE_TOP_RATED]; + $type = mb_strtolower($type); + + if (empty($searchCriteria)) { + $searchCriteria = new ProductSearchCriteria(); + } + + switch ($type) { + case static::FILTER_TYPE_TOP_SELLING: + $result = $this->getBestsellers('gt', $searchCriteria); + break; + + case static::FILTER_TYPE_TOP_FREE: + $result = $this->getBestsellers('eq', $searchCriteria); + break; + + case static::FILTER_TYPE_TOP_RATED: + $result = $this->getRatedProducts($searchCriteria); + break; + + default: + $allowed = implode(', ', $allowed); + $phrase = __('Requested type "%s" doesn\'t exist. Allowed: %s', $type, $allowed); + throw new \Magento\Framework\Exception\InputException($phrase); + } + + return $result; + } + + /** + * @param string $condition - price filter + * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria + * + * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults + */ + protected function getBestsellers($condition, ProductSearchCriteriaInterface $searchCriteria) + { + $pageSize = (int)$searchCriteria->getPageSize(); + $page = (int)$searchCriteria->getCurrentPage(); + + $this->bestsellers + ->clear() + ->distinct(true) + ->setPeriod($searchCriteria->getPeriod()) + ->setPageSize($pageSize) + ->setCurPage($page) + ->addStoreRestrictions($this->storeManager->getStore()->getId()); + + $this->bestsellers->addFieldToFilter('product_price', [$condition => 0]); + + /** @var \Magento\Reports\Model\Item[] $items */ + $items = $this->bestsellers->walk(function ($item) { + /** @var \Magento\Reports\Model\Item $item */ + $productId = $item->getData('product_id'); + $product = $this->products->getById($productId); + + return $product; + }); + + $result = $this->process($items, $searchCriteria, $this->bestsellers->getSize()); + + return $result; + } + + /** + * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria + * + * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults + */ + public function getRatedProducts(ProductSearchCriteriaInterface $searchCriteria) + { + $pageSize = (int)$searchCriteria->getPageSize(); + $page = (int)$searchCriteria->getCurrentPage(); + $storeId = (int)$this->storeManager->getStore()->getId(); + + $this->ratingAggregated + ->clear() + ->setPageSize($pageSize) + ->setCurPage($page) + ->addFieldToFilter('store_id', ['eq' => $storeId]); + + $code = $searchCriteria->getRatingCode(); + if (!empty($code)) { + $rating = $this->rating->getItemByColumnValue('rating_code', $code); + + if (!empty($rating)) { + // there is something like we are searching + $id = $rating->getData('rating_id'); + $this->ratingAggregated->addFieldToFilter('rating_id', ['eq' => $id]); + } + } + + $this->ratingAggregated + ->addOrder('percent', Collection::SORT_ORDER_DESC) + ->addOrder('vote_value_sum', Collection::SORT_ORDER_DESC); + + /** @var \Magento\Reports\Model\Item[] $items */ + $items = $this->ratingAggregated->walk(function ($item) { + /** @var \Magento\Reports\Model\Item $item */ + $productId = $item->getData('entity_pk_value'); + $product = $this->products->getById($productId); + + return $product; + }); + + $result = $this->process($items, $searchCriteria, $this->ratingAggregated->getSize()); + + return $result; + } + + /** + * Prepare response + * + * @param $items + * @param \OxCom\MagentoTopProducts\Api\ProductSearchCriteriaInterface $searchCriteria + * @param $size + * + * @return \OxCom\MagentoTopProducts\Model\ProductSearchResults + */ + protected function process($items, ProductSearchCriteriaInterface $searchCriteria, $size) + { + $result = new ProductSearchResults(); + $result->setItems($items) + ->setSearchCriteria($searchCriteria) + ->setTotalCount($size); + + return $result; + } +} diff --git a/Model/ProductSearchCriteria.php b/Model/ProductSearchCriteria.php new file mode 100644 index 0000000..e038c65 --- /dev/null +++ b/Model/ProductSearchCriteria.php @@ -0,0 +1,83 @@ +period = $period; + + return $this; + } + + /** + * @return string + */ + public function getPeriod() + { + return $this->period; + } + + /** + * @param string $code Raging code filter + * + * @return $this + */ + public function setRatingCode($code = null) + { + $this->ratingCode = $code; + + return $this; + } + + /** + * @return string + */ + public function getRatingCode() + { + return $this->ratingCode; + } +} diff --git a/Model/ProductSearchResults.php b/Model/ProductSearchResults.php new file mode 100644 index 0000000..1188520 --- /dev/null +++ b/Model/ProductSearchResults.php @@ -0,0 +1,84 @@ +_get(self::KEY_ITEMS) === null ? [] : $this->_get(self::KEY_ITEMS); + } + + /** + * Set items + * + * @param \OxCom\MagentoTopProducts\Api\Data\ProductInterface[] $items + * + * @return $this + */ + public function setItems(array $items) + { + return $this->setData(self::KEY_ITEMS, $items); + } + + /** + * Get search criteria + * + * @return \OxCom\MagentoTopProducts\Model\ProductSearchCriteria + */ + public function getSearchCriteria() + { + return $this->_get(self::KEY_SEARCH_CRITERIA); + } + + /** + * Set search criteria + * + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * + * @return $this + */ + public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) + { + return $this->setData(self::KEY_SEARCH_CRITERIA, $searchCriteria); + } + + /** + * Get total count + * + * @return int + */ + public function getTotalCount() + { + return $this->_get(self::KEY_TOTAL_COUNT); + } + + /** + * Set total count + * + * @param int $count + * + * @return $this + */ + public function setTotalCount($count) + { + return $this->setData(self::KEY_TOTAL_COUNT, $count); + } +} diff --git a/Model/Rating/Option/Aggregated.php b/Model/Rating/Option/Aggregated.php new file mode 100644 index 0000000..e770c69 --- /dev/null +++ b/Model/Rating/Option/Aggregated.php @@ -0,0 +1,23 @@ +_init(AggregatedResourceModel::class); + } +} diff --git a/Model/ResourceModel/Rating/Option/Aggregated.php b/Model/ResourceModel/Rating/Option/Aggregated.php new file mode 100644 index 0000000..1e36225 --- /dev/null +++ b/Model/ResourceModel/Rating/Option/Aggregated.php @@ -0,0 +1,21 @@ +_init('rating_option_vote_aggregated', 'primary_id'); + } +} diff --git a/Model/ResourceModel/Rating/Option/Aggregated/Collection.php b/Model/ResourceModel/Rating/Option/Aggregated/Collection.php new file mode 100644 index 0000000..64c82ab --- /dev/null +++ b/Model/ResourceModel/Rating/Option/Aggregated/Collection.php @@ -0,0 +1,24 @@ +_init( + \OxCom\MagentoTopProducts\Model\Rating\Option\Aggregated::class, + \OxCom\MagentoTopProducts\Model\ResourceModel\Rating\Option\Aggregated::class + ); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b54c52 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Magento2 Top Products API + +This is a module that extends Magento2 API to get list of TOP products by next filters: + - TOP selling products + - TOP Free products + - TOP Rated products + +Top selling and free products are fetching from Magento2 reports. + +Top rated products are fetching from Rates module and results are based on aggregated data. + +## Install +```bash +$ composer require oxcom/magento2-top-products +$ bin/magento module:enable OxCom_MagentoTopProducts +$ bin/magento setup:upgrade +$ bin/magento setup:di:compile +``` + +## API requests +```GET /V1/products/top/{type}``` - Get list of top products by type. +Where ```type``` can be: +- **selling** - TOP selling products +- **free** - TOP Free products +- **rated** - TOP Rated products + +###### Search criteria params +**pageSize** - Page size + +**currentPage** - Current page + +**period** - filter by period. This options is related only for ```selling``` or ```free``` type. Possible values are: +- yearly - annual report +- monthly - monthly report +- daily - daily report + +**ratingCode** - filter by rating type. This options is related ony for ```rated``` type. Possible values can be found in ```rating``` table. + +## Dependencies +This module is using exists functionality of next modules: +- **magento/module-catalog** +- **magento/module-review** +- **magento/module-sales** + +## Bugs and Issues +Please, if You found a bug or something, that is not working properly, contact me and tell what's wrong. It's nice to have an example how to reproduce a bug, or any idea how to fix it in Your request. I'll take care about it ASAP. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ae54d37 --- /dev/null +++ b/composer.json @@ -0,0 +1,51 @@ +{ + "name": "oxcom/magento2-top-products", + "description": "This is a module that extends Magento2 API to get list of TOP products.", + "type": "magento2-module", + "license": "LGPL-3.0", + "version": "1.0.0", + "keywords": [ + "magento2", + "top products", + "api" + ], + "homepage": "https://github.com/OxCom/magento2-top-products", + "require": { + "php": ">=5.6", + "magento/module-config": "100.0.*|100.1.*|101.0.*", + "magento/module-store": "100.0.*|100.1.*|100.2.*", + "magento/module-backend": "100.0.*|100.1.*|100.2.*", + "magento/module-directory": "100.0.*|100.1.*|100.2.*", + "magento/framework": "100.0.*|100.1.*|101.0.*" + }, + "require-dev": { + "magento/zendframework1": "1.12.*|1.13.*", + "phpunit/phpunit": "5.3.5", + "squizlabs/php_codesniffer": "^3.1", + "sebastian/phpcpd": "^3.0" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "OxCom\\MagentoTopProducts\\": "" + } + }, + "autoload-dev": { + "classmap": [ + ] + }, + "authors": [ + { + "name": "OxCom", + "email": "lancer.oxcom@gmail.com" + } + ], + "repositories": [ + { + "type": "composer", + "url": "https://repo.magento.com/" + } + ] +} diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..90e9928 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..37ce748 --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/etc/webapi.xml b/etc/webapi.xml new file mode 100644 index 0000000..206e2a0 --- /dev/null +++ b/etc/webapi.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..877499d --- /dev/null +++ b/registration.php @@ -0,0 +1,6 @@ +