diff --git a/Model/Indexer/Product.php b/Model/Indexer/Product.php
new file mode 100644
index 0000000..940ea75
--- /dev/null
+++ b/Model/Indexer/Product.php
@@ -0,0 +1,69 @@
+productIndexerRows = $productIndexerRows;
+ }
+
+ /**
+ * Execute materialization on ids entities
+ *
+ * @param int[] $ids
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ public function execute($ids)
+ {
+ $this->productIndexerRows->execute($ids);
+ }
+
+ /**
+ * Execute full indexation
+ *
+ * @return void
+ */
+ public function executeFull()
+ {
+ // not implemented
+ }
+
+ /**
+ * Execute partial indexation by ID list
+ *
+ * @param int[] $ids
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ public function executeList(array $ids)
+ {
+ $this->productIndexerRows->execute($ids);
+ }
+
+ /**
+ * Execute partial indexation by ID
+ *
+ * @param int $id
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ public function executeRow($id)
+ {
+ $this->productIndexerRows->execute([$id]);
+ }
+}
diff --git a/Model/Indexer/Product/Action/Rows.php b/Model/Indexer/Product/Action/Rows.php
new file mode 100644
index 0000000..28428a1
--- /dev/null
+++ b/Model/Indexer/Product/Action/Rows.php
@@ -0,0 +1,206 @@
+objectManager = $objectManager;
+ $this->scopeConfig = $scopeConfig;
+ $this->eventManager = $eventManager;
+ $this->productRepository = $productRepository;
+ $this->productType = $productType;
+ $this->api = $api;
+ }
+
+ /**
+ * Execute action for given ids
+ *
+ * @param array|int $ids
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @return void
+ */
+ public function execute($ids = null)
+ {
+ if (!$this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_REAL_TIME_ENABLED)) {
+ return;
+ }
+
+ if (!isset($ids) || empty($ids)) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('We can\'t rebuild the index for an undefined product.')
+ );
+ }
+
+ if (!is_array($ids)) {
+ $ids = [$ids];
+ }
+
+ foreach ($ids as $id) {
+ $this->reindexRow($id);
+ $this->updateParentProducts($id);
+ }
+ }
+
+ /**
+ * Refresh entity index
+ *
+ * @param int $productId
+ * @return void
+ */
+ protected function reindexRow($productId)
+ {
+ try {
+ $product = $this->productRepository->getById($productId);
+ } catch (NoSuchEntityException $e) {
+ $this->api->removeProduct($productId);
+ return;
+ }
+
+ //Cancel if product visibility is not as defined
+ if ($product->getVisibility() != $this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_VISIBILITY)) {
+ return;
+ }
+
+ //Cancel if product is not saleable
+ if ($this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_SALABLE_ONLY)) {
+ if (!$product->isSalable()) {
+ return;
+ }
+ }
+
+ $store = $this->objectManager->get('Magento\Store\Model\StoreManagerInterface')->getStore();
+ $imageUrl = $store->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) . 'catalog/product' . $product->getImage();
+
+ $productItem = [
+ 'id' => $product->getId(),
+ 'name' => $product->getName(),
+ 'description' => $product->getDescription(),
+ 'price' => $product->getFinalPrice(),
+ 'list_price' => $product->getPrice(),
+ 'image' => $imageUrl,
+ 'url' => $product->getUrlModel()->getUrl($product),
+ 'categories' => $product->getCategoryIds(),
+ 'sku' => $product->getSku(),
+ 'on_sale' => ($product->getFinalPrice() < $product->getPrice()),
+ ];
+
+ /**
+ * @todo Refactor to use fieldhandlers or similar
+ */
+ $configFields = $this->scopeConfig->getValue(
+ Config::XML_PATH_PRODUCT_SYNCHRONIZATION_ADDITIONAL_FIELDS
+ );
+
+ $fields = explode(',', $configFields);
+
+ foreach ($fields as $field) {
+ if (! isset($productItem[$field])) {
+ $productItem[$field] = $product->getData($field);
+ }
+ }
+
+ $productObject = new \Magento\Framework\DataObject();
+ $productObject->setData($productItem);
+
+ $this->eventManager->dispatch('clerk_product_sync_before', ['product' => $productObject]);
+
+ $this->api->addProduct($productObject->toArray());
+ }
+
+ /**
+ * Reindex parent configurable/bundle/grouped products
+ *
+ * @param int $productId
+ * @return void
+ * @see \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction::_getProductTypeInstances
+ */
+ public function updateParentProducts($productId)
+ {
+ $parentIds = [];
+
+ foreach ($this->getCompositeTypes() as $typeInstance) {
+ $parentIds = array_merge($parentIds, $typeInstance->getParentIdsByChild($productId));
+ }
+
+ foreach ($parentIds as $parentId) {
+ $this->reindexRow($parentId);
+ }
+ }
+
+ /**
+ * @return AbstractType[]
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function getCompositeTypes()
+ {
+ if (null === $this->compositeTypes) {
+ $productEmulator = new \Magento\Framework\DataObject();
+ foreach ($this->productType->getCompositeTypes() as $typeId) {
+ $productEmulator->setTypeId($typeId);
+ $this->compositeTypes[$typeId] = $this->productType->factory($productEmulator);
+ }
+ }
+
+ return $this->compositeTypes;
+ }
+}
diff --git a/Observer/ProductDeleteAfterDoneObserver.php b/Observer/ProductDeleteAfterDoneObserver.php
deleted file mode 100644
index c6be70f..0000000
--- a/Observer/ProductDeleteAfterDoneObserver.php
+++ /dev/null
@@ -1,45 +0,0 @@
-scopeConfig = $scopeConfig;
- $this->api = $api;
- }
-
- /**
- * Remove product from Clerk
- *
- * @param Observer $observer
- * @return void
- */
- public function execute(\Magento\Framework\Event\Observer $observer)
- {
- if ($this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_REAL_TIME_ENABLED)) {
- $product = $observer->getProduct();
-
- if ($product && $product->getId()) {
- $this->api->removeProduct($product->getId());
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Observer/ProductSaveEntityAfterObserver.php b/Observer/ProductSaveEntityAfterObserver.php
deleted file mode 100644
index 377c2e5..0000000
--- a/Observer/ProductSaveEntityAfterObserver.php
+++ /dev/null
@@ -1,115 +0,0 @@
-objectManager = $objectManager;
- $this->scopeConfig = $scopeConfig;
- $this->eventManager = $eventManager;
- $this->api = $api;
- }
-
- /**
- * Add product to Clerk
- *
- * @param Observer $observer
- * @return void
- */
- public function execute(\Magento\Framework\Event\Observer $observer)
- {
- if ($this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_REAL_TIME_ENABLED)) {
- /** @var Product $product */
- $product = $observer->getProduct();
-
- if ($product && $product->getId()) {
-
- //Cancel if product visibility is not as defined
- if ($product->getVisibility() != $this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_VISIBILITY)) {
- return;
- }
-
- //Cancel if product is not saleable
- if ($this->scopeConfig->getValue(Config::XML_PATH_PRODUCT_SYNCHRONIZATION_SALABLE_ONLY)) {
- if (!$product->isSalable()) {
- return;
- }
- }
-
- $store = $this->objectManager->get('Magento\Store\Model\StoreManagerInterface')->getStore();
- $imageUrl = $store->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) . 'catalog/product' . $product->getImage();
-
- $productItem = [
- 'id' => $product->getId(),
- 'name' => $product->getName(),
- 'description' => $product->getDescription(),
- 'price' => $product->getFinalPrice(),
- 'list_price' => $product->getPrice(),
- 'image' => $imageUrl,
- 'url' => $product->getUrlModel()->getUrl($product),
- 'categories' => $product->getCategoryIds(),
- 'sku' => $product->getSku(),
- 'on_sale' => ($product->getFinalPrice() < $product->getPrice()),
- ];
-
- /**
- * @todo Refactor to use fieldhandlers or similar
- */
- $configFields = $this->scopeConfig->getValue(
- Config::XML_PATH_PRODUCT_SYNCHRONIZATION_ADDITIONAL_FIELDS
- );
-
- $fields = explode(',', $configFields);
-
- foreach ($fields as $field) {
- if (! isset($productItem[$field])) {
- $productItem[$field] = $product->getData($field);
- }
- }
-
- $productObject = new \Magento\Framework\DataObject();
- $productObject->setData($productItem);
-
- $this->eventManager->dispatch('clerk_product_sync_before', ['product' => $productObject]);
-
- $this->api->addProduct($productObject->toArray());
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Plugin/Catalog/Product.php b/Plugin/Catalog/Product.php
new file mode 100644
index 0000000..5c83029
--- /dev/null
+++ b/Plugin/Catalog/Product.php
@@ -0,0 +1,61 @@
+indexer = $indexerRegistry->get('clerk_products');
+ }
+
+ /**
+ * @param \Magento\Catalog\Model\ResourceModel\Product $productResource
+ * @param \Closure $proceed
+ * @param \Magento\Framework\Model\AbstractModel $product
+ * @return mixed
+ */
+ public function aroundSave(
+ \Magento\Catalog\Model\ResourceModel\Product $productResource,
+ \Closure $proceed,
+ \Magento\Framework\Model\AbstractModel $product
+ ) {
+ $productResource->addCommitCallback(function () use ($product) {
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexRow($product->getId());
+ }
+ });
+
+ return $proceed($product);
+ }
+
+ /**
+ * @param \Magento\Catalog\Model\ResourceModel\Product $productResource
+ * @param \Closure $proceed
+ * @param \Magento\Framework\Model\AbstractModel $product
+ * @return mixed
+ */
+ public function aroundDelete(
+ \Magento\Catalog\Model\ResourceModel\Product $productResource,
+ \Closure $proceed,
+ \Magento\Framework\Model\AbstractModel $product
+ ) {
+ $productResource->addCommitCallback(function () use ($product) {
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexRow($product->getId());
+ }
+ });
+
+ return $proceed($product);
+ }
+}
diff --git a/Plugin/Catalog/Product/Action.php b/Plugin/Catalog/Product/Action.php
new file mode 100644
index 0000000..1b1118e
--- /dev/null
+++ b/Plugin/Catalog/Product/Action.php
@@ -0,0 +1,67 @@
+indexer = $indexerRegistry->get('clerk_products');
+ }
+
+ /**
+ * @param \Magento\Catalog\Model\Product\Action $subject
+ * @param \Closure $closure
+ * @param array $productIds
+ * @param array $attrData
+ * @param $storeId
+ * @return mixed
+ */
+ public function aroundUpdateAttributes(
+ \Magento\Catalog\Model\Product\Action $subject,
+ \Closure $closure,
+ array $productIds,
+ array $attrData,
+ $storeId
+ ) {
+ $result = $closure($productIds, $attrData, $storeId);
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexList(array_unique($productIds));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param \Magento\Catalog\Model\Product\Action $subject
+ * @param \Closure $closure
+ * @param array $productIds
+ * @param array $websiteIds
+ * @param $type
+ * @return mixed
+ */
+ public function aroundUpdateWebsites(
+ \Magento\Catalog\Model\Product\Action $subject,
+ \Closure $closure,
+ array $productIds,
+ array $websiteIds,
+ $type
+ ) {
+ $result = $closure($productIds, $websiteIds, $type);
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexList(array_unique($productIds));
+ }
+
+ return $result;
+ }
+}
diff --git a/Plugin/CatalogInventory/Stock/Item.php b/Plugin/CatalogInventory/Stock/Item.php
new file mode 100644
index 0000000..9ec6467
--- /dev/null
+++ b/Plugin/CatalogInventory/Stock/Item.php
@@ -0,0 +1,49 @@
+indexer = $indexerRegistry->get('clerk_products');
+ }
+
+ public function aroundSave(
+ \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $stockItemModel,
+ \Closure $proceed,
+ \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
+ ) {
+ $stockItemModel->addCommitCallback(function () use ($stockItem) {
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexRow($stockItem->getProductId());
+ }
+ });
+
+ return $proceed($stockItem);
+ }
+
+ public function aroundDelete(
+ \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $stockItemResource,
+ \Closure $proceed,
+ \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
+ ) {
+ $stockItemResource->addCommitCallback(function () use ($stockItem) {
+ if (!$this->indexer->isScheduled()) {
+ $this->indexer->reindexRow($stockItem->getProductId());
+ }
+ });
+
+ return $proceed($stockItem);
+ }
+}
diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml
deleted file mode 100644
index 12f70ef..0000000
--- a/etc/adminhtml/events.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/etc/di.xml b/etc/di.xml
index 86588cb..9ae4063 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -1,4 +1,13 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/indexer.xml b/etc/indexer.xml
new file mode 100644
index 0000000..952b6c8
--- /dev/null
+++ b/etc/indexer.xml
@@ -0,0 +1,7 @@
+
+
+
+ Clerk Products
+ Clerk Products reindex when real time synchronization is enabled
+
+
diff --git a/etc/mview.xml b/etc/mview.xml
new file mode 100644
index 0000000..883dc77
--- /dev/null
+++ b/etc/mview.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+