Skip to content

Commit 2a30574

Browse files
authored
Release/2.3.0 (#32)
* Add PushApi service and plugin to push out of stock status on order placement per sales channel. * Add missing class property. * Bump version to 2.3.0 * Add config to allow stock push notifications at global or sales channel level (or disabled). * Add configuration to enable/disable tracking on checkout page. * Improve logic for sending stock push norification (dont send for already out of stock products). * Add exception handler for invalid product types when getting salable qty from default stock. * Fix casting of product status to reflect correct value. * Rename block functions to better reflect their purpose. Split out tracking and dataLayer logic. * Refactor function call to improve readability. * Add comment to make return type clear.
1 parent 7c82073 commit 2a30574

File tree

15 files changed

+400
-43
lines changed

15 files changed

+400
-43
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0"?>
22
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
3-
<module name="Ometria_AbandonedCarts" setup_version="2.2.4"/>
3+
<module name="Ometria_AbandonedCarts" setup_version="2.3.0"/>
44
</config>

app/code/Ometria/Api/Controller/V2/Products.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class Products extends Action
8585
/** @var AppEmulation */
8686
private $appEmulation;
8787

88+
/** @var InventoryService */
89+
private $inventoryService;
90+
8891
/** @var array */
8992
private $productCollections = [];
9093

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0"?>
22
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
3-
<module name="Ometria_Api" setup_version="2.2.4"/>
3+
<module name="Ometria_Api" setup_version="2.3.0"/>
44
</config>

app/code/Ometria/Core/Block/Head.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,32 @@ public function getAPIKey()
101101
/**
102102
* @return bool
103103
*/
104-
public function isUnivarEnabled()
104+
public function isDatalayerEnabled()
105105
{
106106
return $this->scopeConfig->isSetFlag(
107107
'ometria/advanced/univar',
108108
ScopeInterface::SCOPE_STORE
109109
);
110110
}
111111

112+
/**
113+
* @return bool
114+
*/
115+
public function isTrackingEnabled()
116+
{
117+
$enabled = true;
118+
119+
// Check config for tracking allowed if on checkout page
120+
if ($this->_isCheckout()) {
121+
$enabled = $this->scopeConfig->isSetFlag(
122+
'ometria/advanced/checkout_tracking_enabled',
123+
ScopeInterface::SCOPE_STORE
124+
);
125+
}
126+
127+
return $enabled;
128+
}
129+
112130
/**
113131
* @return bool
114132
*/

app/code/Ometria/Core/Helper/Config.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use Magento\Framework\App\Helper\AbstractHelper;
55
use Magento\Framework\App\Helper\Context;
6+
use Magento\Store\Model\ScopeInterface;
67
use Ometria\Core\Helper\MageConfig;
78

89
class Config extends AbstractHelper
@@ -56,6 +57,17 @@ public function getAPIKey($store_id = null)
5657
}
5758
}
5859

60+
/**
61+
* @return string
62+
*/
63+
public function getPushAPIKey()
64+
{
65+
return (string) $this->scopeConfig->getValue(
66+
'ometria/general/pushapikey',
67+
ScopeInterface::SCOPE_STORE
68+
);
69+
}
70+
5971
public function isConfigured()
6072
{
6173
return $this->isEnabled() && $this->getAPIKey() != "";
@@ -66,8 +78,9 @@ public function log($message, $level = \Psr\Log\LogLevel::DEBUG)
6678
$this->logger->log($level, $message);
6779
}
6880

69-
public function isSkuMode(){
70-
return $this->coreHelperMageConfig->get('ometria/advanced/productmode')=='sku';
81+
public function isSkuMode()
82+
{
83+
return $this->coreHelperMageConfig->get('ometria/advanced/productmode') == 'sku';
7184
}
7285

7386
/**
@@ -85,4 +98,12 @@ public function getPreferredProductAttribute()
8598
{
8699
return (string) $this->coreHelperMageConfig->get('ometria/advanced/preferred_product_attribute');
87100
}
101+
102+
/**
103+
* @return string
104+
*/
105+
public function getStockPushScope()
106+
{
107+
return (string) $this->coreHelperMageConfig->get('ometria/advanced/stock_push_scope');
108+
}
88109
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
namespace Ometria\Core\Model\Config\Source;
3+
4+
use Magento\Framework\Option\ArrayInterface;
5+
6+
class StockPushScope implements ArrayInterface
7+
{
8+
const SCOPE_DISABLED = 0;
9+
const SCOPE_GLOBAL = 1;
10+
const SCOPE_CHANNEL = 2;
11+
12+
13+
/**
14+
* @return array
15+
*/
16+
public function toOptionArray()
17+
{
18+
return [
19+
['value' => self::SCOPE_DISABLED, 'label' => __('Disabled')],
20+
['value' => self::SCOPE_GLOBAL, 'label' => __('Global')],
21+
['value' => self::SCOPE_CHANNEL, 'label' => __('Sales Channel')]
22+
];
23+
}
24+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
namespace Ometria\Core\Plugin;
3+
4+
use Magento\Catalog\Api\Data\ProductInterface;
5+
use Magento\Sales\Api\Data\OrderInterface;
6+
use Magento\Sales\Api\OrderManagementInterface;
7+
use Ometria\Core\Helper\Config;
8+
use Ometria\Core\Model\Config\Source\StockPushScope;
9+
use Ometria\Core\Service\Product\Inventory as InventoryService;
10+
use Ometria\Core\Service\PushApi as PushApiService;
11+
12+
class OrderManagementPlugin
13+
{
14+
/** @var InventoryService */
15+
private $inventoryService;
16+
17+
/** @var PushApiService */
18+
private $pushApiService;
19+
20+
/** @var Config */
21+
private $helperConfig;
22+
23+
/**
24+
* @param InventoryService $inventoryService
25+
* @param PushApiService $pushApiService
26+
* @param Config $helperConfig
27+
*/
28+
public function __construct(
29+
InventoryService $inventoryService,
30+
PushApiService $pushApiService,
31+
Config $helperConfig
32+
) {
33+
$this->inventoryService = $inventoryService;
34+
$this->pushApiService = $pushApiService;
35+
$this->helperConfig = $helperConfig;
36+
}
37+
38+
/**
39+
* Plugin to trigger webhook to Ometria where stock value reaches 0 for
40+
* an MSI sales channel after an order is placed.
41+
*
42+
* @param OrderManagementInterface $subject
43+
* @param $result
44+
* @param OrderInterface $order
45+
* @return mixed
46+
*/
47+
public function afterPlace(OrderManagementInterface $subject, $result, OrderInterface $order)
48+
{
49+
try {
50+
$this->sendPushNotifications($order);
51+
} catch (\Exception $e) {
52+
// Catch all errors to ensure this does not affect order placement
53+
}
54+
55+
return $result;
56+
}
57+
58+
/**
59+
* @param OrderInterface $order
60+
*/
61+
private function sendPushNotifications(OrderInterface $order)
62+
{
63+
$stockPushScope = $this->helperConfig->getStockPushScope();
64+
65+
if ($stockPushScope == StockPushScope::SCOPE_DISABLED) {
66+
// Return early if stock push disabled
67+
return;
68+
}
69+
70+
foreach ($order->getItems() as $orderItem) {
71+
// Retrieve the salable qty of the product based on configured scope
72+
$salableQty = $this->getSalableQty(
73+
$orderItem->getProduct(),
74+
$stockPushScope
75+
);
76+
77+
// if salable qty is set and not already 0, check if push is required (null infers manage stock is disabled)
78+
if ($salableQty !== null && $salableQty > 0) {
79+
// Calculate new salabale quantity (after order placement)
80+
$salableQtyAfterOrder = $salableQty - $orderItem->getQtyOrdered();
81+
if ($salableQtyAfterOrder <= 0) {
82+
$stockData = $this->inventoryService->getPushApiStockData(
83+
(int)$orderItem->getProductId(),
84+
false
85+
);
86+
87+
$this->pushApiService->pushRequest($stockData);
88+
}
89+
}
90+
}
91+
}
92+
93+
/**
94+
* @param ProductInterface $product
95+
* @param int $stockPushScope
96+
* @return float|null
97+
*/
98+
private function getSalableQty(ProductInterface $product, int $stockPushScope)
99+
{
100+
$salableQty = null;
101+
102+
if ($stockPushScope == StockPushScope::SCOPE_GLOBAL) {
103+
// Get current salabale quantity (before order placement)
104+
$salableQty = $this->inventoryService->getGlobalSalableQuantity($product);
105+
} else if ($stockPushScope == StockPushScope::SCOPE_CHANNEL) {
106+
// Get current salabale quantity (before order placement)
107+
$salableQty = $this->inventoryService->getSalableQuantity($product);
108+
}
109+
110+
return $salableQty;
111+
}
112+
}

app/code/Ometria/Core/Service/Product/Inventory.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
use Magento\CatalogInventory\Api\StockRegistryInterface;
88
use Magento\CatalogInventory\Helper\Stock as StockHelper;
99
use Magento\Framework\App\ObjectManager;
10+
use Magento\Framework\Exception\InputException;
1011
use Magento\Framework\Exception\LocalizedException;
1112
use Magento\Framework\Exception\NoSuchEntityException;
1213
use Magento\Framework\Module\Manager as ModuleManager;
14+
use Magento\InventoryConfigurationApi\Api\GetStockItemConfigurationInterface;
15+
use Magento\InventoryConfigurationApi\Exception\SkuIsNotAssignedToStockException;
16+
use Magento\InventorySalesAdminUi\Model\ResourceModel\GetAssignedStockIdsBySku;
1317
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
1418
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
1519
use Magento\InventorySalesApi\Api\IsProductSalableInterface;
@@ -67,6 +71,8 @@ public function getStockStatus(ProductInterface $product)
6771
}
6872

6973
/**
74+
* Get the salable quantity of a product for current sales channel
75+
*
7076
* @param ProductInterface $product
7177
* @return float
7278
*/
@@ -79,6 +85,41 @@ public function getSalableQuantity(ProductInterface $product)
7985
return $this->getLegacySalableQuantity($product);
8086
}
8187

88+
/**
89+
* Get global salable quantity of a product (all sales channels)
90+
*
91+
* @param ProductInterface $product
92+
* @return float|null
93+
* @throws InputException
94+
* @throws LocalizedException
95+
* @throws SkuIsNotAssignedToStockException
96+
*/
97+
public function getGlobalSalableQuantity(ProductInterface $product)
98+
{
99+
if ($this->isMSIAvailable()) {
100+
return $this->getMSIGlobalSalableQuantity($product);
101+
}
102+
103+
return $this->getLegacySalableQuantity($product);
104+
}
105+
106+
/**
107+
* @param int $id
108+
* @param bool $isInStock
109+
* @return array
110+
*/
111+
public function getPushApiStockData(int $id, bool $isInStock)
112+
{
113+
return [
114+
[
115+
"@type" => "product",
116+
"id" => $id,
117+
"is_in_stock" => $isInStock,
118+
"@merge" => true
119+
]
120+
];
121+
}
122+
82123
/**
83124
* @param ProductCollection $collection
84125
*/
@@ -122,6 +163,50 @@ private function getMSISalableQuantity(ProductInterface $product)
122163
return $qty;
123164
}
124165

166+
/**
167+
* @param ProductInterface $product
168+
* @return float|null
169+
* @throws InputException
170+
* @throws LocalizedException
171+
* @throws SkuIsNotAssignedToStockException
172+
*/
173+
private function getMSIGlobalSalableQuantity(ProductInterface $product)
174+
{
175+
/** @var GetAssignedStockIdsBySku $getSalableQtyData */
176+
$getAssignedStockIdsBySku = ObjectManager::getInstance()->get(GetAssignedStockIdsBySku::class);
177+
178+
/** @var GetStockItemConfigurationInterface $getStockItemConfiguration */
179+
$getStockItemConfiguration = ObjectManager::getInstance()->get(GetStockItemConfigurationInterface::class);
180+
181+
/** @var GetProductSalableQtyInterface $getProductSalableQtyInterface */
182+
$getProductSalableQty = ObjectManager::getInstance()->get(GetProductSalableQtyInterface::class);
183+
184+
$qty = 0.;
185+
$stockIds = $getAssignedStockIdsBySku->execute($product->getSku());
186+
187+
foreach ($stockIds as $stockId) {
188+
$stockItemConfiguration = $getStockItemConfiguration->execute($product->getSku(), $stockId);
189+
$isManageStock = $stockItemConfiguration->isManageStock();
190+
191+
if (!$isManageStock) {
192+
// Explicitly return null here to show 'manage stock' disabled rather than zero stock
193+
return null;
194+
}
195+
196+
try {
197+
// Try to get qty for current stock ID
198+
$qtyForStockId = $getProductSalableQty->execute($product->getSku(), $stockId);
199+
} catch (InputException $e) {
200+
// Catch exception for invalid product types
201+
$qtyForStockId = 0;
202+
}
203+
204+
$qty += $qtyForStockId;
205+
}
206+
207+
return $qty;
208+
}
209+
125210
/**
126211
* @return int|null
127212
* @throws LocalizedException

0 commit comments

Comments
 (0)