StockIndex.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CatalogInventory\Model;
  7. use Magento\Catalog\Model\Product\Type as ProductType;
  8. use Magento\Catalog\Model\Product\Website as ProductWebsite;
  9. use Magento\Catalog\Model\ProductFactory;
  10. use Magento\CatalogInventory\Api\StockIndexInterface;
  11. use Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface;
  12. use Magento\Catalog\Api\ProductRepositoryInterface;
  13. use Magento\Catalog\Model\Product\Attribute\Source\Status;
  14. /**
  15. * Class StockIndex
  16. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  17. */
  18. class StockIndex implements StockIndexInterface
  19. {
  20. /**
  21. * @var StockRegistryProviderInterface
  22. */
  23. protected $stockRegistryProvider;
  24. /**
  25. * @var ProductRepositoryInterface
  26. */
  27. protected $productRepository;
  28. /**
  29. * @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Status
  30. */
  31. protected $stockStatusResource;
  32. /**
  33. * @var ProductType
  34. */
  35. protected $productType;
  36. /**
  37. * Retrieve website models
  38. *
  39. * @var array
  40. */
  41. protected $websites;
  42. /**
  43. * Product Type Instances cache
  44. *
  45. * @var array
  46. */
  47. protected $productTypes = [];
  48. /**
  49. * @param StockRegistryProviderInterface $stockRegistryProvider
  50. * @param ProductRepositoryInterface $productRepository
  51. * @param ProductWebsite $productWebsite
  52. * @param ProductType $productType
  53. */
  54. public function __construct(
  55. StockRegistryProviderInterface $stockRegistryProvider,
  56. ProductRepositoryInterface $productRepository,
  57. ProductWebsite $productWebsite,
  58. ProductType $productType
  59. ) {
  60. $this->stockRegistryProvider = $stockRegistryProvider;
  61. $this->productRepository = $productRepository;
  62. $this->productWebsite = $productWebsite;
  63. $this->productType = $productType;
  64. }
  65. /**
  66. * Rebuild stock index of the given website
  67. *
  68. * @param int $productId
  69. * @param int $scopeId
  70. * @deprecated 100.1.0
  71. * @return true
  72. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  73. */
  74. public function rebuild($productId = null, $scopeId = null)
  75. {
  76. if ($productId !== null) {
  77. $this->updateProductStockStatus($productId, $scopeId);
  78. } else {
  79. $lastProductId = 0;
  80. while (true) {
  81. /** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Status $resource */
  82. $resource = $this->getStockStatusResource();
  83. $productCollection = $resource->getProductCollection($lastProductId);
  84. if (!$productCollection) {
  85. break;
  86. }
  87. foreach ($productCollection as $productId => $productType) {
  88. $lastProductId = $productId;
  89. $this->updateProductStockStatus($productId, $scopeId);
  90. }
  91. }
  92. }
  93. return true;
  94. }
  95. /**
  96. * Update product status from stock item
  97. *
  98. * @param int $productId
  99. * @param int $websiteId
  100. * @deprecated 100.1.0
  101. * @return void
  102. */
  103. public function updateProductStockStatus($productId, $websiteId)
  104. {
  105. $item = $this->stockRegistryProvider->getStockItem($productId, $websiteId);
  106. $status = \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK;
  107. $qty = 0;
  108. if ($item->getItemId()) {
  109. $status = $item->getIsInStock();
  110. $qty = $item->getQty();
  111. }
  112. $this->processChildren($productId, $item->getWebsiteId(), $qty, $status);
  113. $this->processParents($productId, $item->getWebsiteId());
  114. }
  115. /**
  116. * Process children stock status
  117. *
  118. * @param int $productId
  119. * @param int $websiteId
  120. * @param int $qty
  121. * @param int $status
  122. * @return $this
  123. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  124. */
  125. protected function processChildren(
  126. $productId,
  127. $websiteId,
  128. $qty = 0,
  129. $status = \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK
  130. ) {
  131. if ($status == \Magento\CatalogInventory\Model\Stock\Status::STATUS_OUT_OF_STOCK) {
  132. $this->getStockStatusResource()->saveProductStatus($productId, $status, $qty, $websiteId);
  133. return;
  134. }
  135. $statuses = [];
  136. $websitesWithStores = $this->getWebsitesWithDefaultStores($websiteId);
  137. foreach (array_keys($websitesWithStores) as $websiteId) {
  138. /* @var $website \Magento\Store\Model\Website */
  139. $statuses[$websiteId] = $status;
  140. }
  141. /** @var \Magento\Catalog\Model\Product $product */
  142. $product = $this->productRepository->getById($productId);
  143. $typeInstance = $product->getTypeInstance();
  144. $requiredChildrenIds = $typeInstance->getChildrenIds($productId, true);
  145. if ($requiredChildrenIds) {
  146. $childrenIds = [];
  147. foreach ($requiredChildrenIds as $groupedChildrenIds) {
  148. $childrenIds = array_merge($childrenIds, $groupedChildrenIds);
  149. }
  150. $childrenWebsites = $this->productWebsite->getWebsites($childrenIds);
  151. foreach ($websitesWithStores as $websiteId => $storeId) {
  152. $childrenStatus = $this->getStockStatusResource()->getProductStatus($childrenIds, $storeId);
  153. $childrenStock = $this->getStockStatusResource()->getProductsStockStatuses($childrenIds, $websiteId);
  154. $websiteStatus = $statuses[$websiteId];
  155. foreach ($requiredChildrenIds as $groupedChildrenIds) {
  156. $optionStatus = false;
  157. foreach ($groupedChildrenIds as $childId) {
  158. if (isset($childrenStatus[$childId])
  159. && isset($childrenWebsites[$childId])
  160. && in_array($websiteId, $childrenWebsites[$childId])
  161. && $childrenStatus[$childId] == Status::STATUS_ENABLED
  162. && isset($childrenStock[$childId])
  163. && $childrenStock[$childId] == \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK
  164. ) {
  165. $optionStatus = true;
  166. }
  167. }
  168. $websiteStatus = $websiteStatus && $optionStatus;
  169. }
  170. $statuses[$websiteId] = (int)$websiteStatus;
  171. }
  172. }
  173. foreach ($statuses as $websiteId => $websiteStatus) {
  174. $this->getStockStatusResource()->saveProductStatus($productId, $websiteStatus, $qty, $websiteId);
  175. }
  176. }
  177. /**
  178. * Retrieve website models
  179. *
  180. * @param int|null $websiteId
  181. * @return array
  182. */
  183. protected function getWebsitesWithDefaultStores($websiteId = null)
  184. {
  185. if ($this->websites === null) {
  186. /** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Status $resource */
  187. $resource = $this->getStockStatusResource();
  188. $this->websites = $resource->getWebsiteStores();
  189. }
  190. $websites = $this->websites;
  191. if ($websiteId !== null && isset($this->websites[$websiteId])) {
  192. $websites = [$websiteId => $this->websites[$websiteId]];
  193. }
  194. return $websites;
  195. }
  196. /**
  197. * Process Parents by child
  198. *
  199. * @param int $productId
  200. * @param int $websiteId
  201. * @return $this
  202. */
  203. protected function processParents($productId, $websiteId)
  204. {
  205. $parentIds = [];
  206. foreach ($this->getProductTypeInstances() as $typeInstance) {
  207. /* @var $typeInstance AbstractType */
  208. $parentIds = array_merge($parentIds, $typeInstance->getParentIdsByChild($productId));
  209. }
  210. if (!$parentIds) {
  211. return $this;
  212. }
  213. foreach ($parentIds as $parentId) {
  214. $item = $this->stockRegistryProvider->getStockItem($parentId, $websiteId);
  215. $status = \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK;
  216. $qty = 0;
  217. if ($item->getItemId()) {
  218. $status = $item->getIsInStock();
  219. $qty = $item->getQty();
  220. }
  221. $this->processChildren($parentId, $websiteId, $qty, $status);
  222. }
  223. }
  224. /**
  225. * Retrieve Product Type Instances
  226. * as key - type code, value - instance model
  227. *
  228. * @return array
  229. */
  230. protected function getProductTypeInstances()
  231. {
  232. if (empty($this->productTypes)) {
  233. $productEmulator = new \Magento\Framework\DataObject();
  234. foreach (array_keys($this->productType->getTypes()) as $typeId) {
  235. $productEmulator->setTypeId($typeId);
  236. $this->productTypes[$typeId] = $this->productType->factory($productEmulator);
  237. }
  238. }
  239. return $this->productTypes;
  240. }
  241. /**
  242. * @return \Magento\CatalogInventory\Model\ResourceModel\Stock\Status
  243. */
  244. protected function getStockStatusResource()
  245. {
  246. if (empty($this->stockStatusResource)) {
  247. $this->stockStatusResource = \Magento\Framework\App\ObjectManager::getInstance()->get(
  248. \Magento\CatalogInventory\Model\ResourceModel\Stock\Status::class
  249. );
  250. }
  251. return $this->stockStatusResource;
  252. }
  253. }