-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate Single Stock Data From CatalogInventory to MSI SourceItems #198
Conversation
…Add data to default source (Data Install), split InstallData to processors
# Conflicts: # app/code/Magento/InventoryCatalog/Setup/InstallData.php
* @throws AlreadyExistsException; | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Why do we use
around
plugin instead ofafter
plugin? - Maybe it is better to use observer event? In this case, all operations will be in scope of one transaction (now our code will be performed in out of transaction)
@maghamed pls provide your opinion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- with
around
plugin we could achieve stock deduction for MSI in the same DB transaction that stock deduction for CatalogInventory StockItem happened - with around plugin we could either achieve transactional consistency as we control state before the subject execution and after
/** | ||
* Class provide Around Plugin on Stock::save to migrate single stock data | ||
*/ | ||
class StockItemPlugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, Provide more clear name (what is this plugin responsible for)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, better to sync up with existing plugin we have for Reservation placement.
We could give them similar names and more to the single location.
For example,
1. UpdateLegacyCatalogInventoryAtStockDeductionTime
2. UpdateLegacyCatalogInventoryAtStockSettingTime
Because we have two different business operations with stock:
- We set stock data and overwrite existing data in StockItem (despite the data stored there before) , this happens for example during the Import or synchronization with ERP
- Stock Deduction, which happens during the order placement, when we need to subtract given quantity from the data we have in Stock
…ck-data # Conflicts: # app/code/Magento/InventoryCatalog/etc/di.xml
public function execute() | ||
{ | ||
$defaultSourceId = $this->defaultSourceProvider->getId(); | ||
$sql = "INSERT INTO inventory_source_item (source_id, sku, quantity, status) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to resolve table name via setup object
example $sourceTable = $setup->getTable(SourceResourceModel::TABLE_NAME_SOURCE);
Now solution is not work if table prefixes are used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, need to use constants for fields
public function execute() | ||
{ | ||
$defaultSourceId = $this->defaultSourceProvider->getId(); | ||
$sql = "INSERT INTO inventory_source_item (source_id, sku, quantity, status) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add dependency on Inventory
in module sequence configuration in module.xml
Because we operate inventory_source_item
table which is created in Inventory module
So we need to guarantee that install scheme from Inventory
is finished before InventoryCatalog
installation running
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we have double saving of SourceItem
For example, I feel both fields in UI (old qty field and new in our grid) with different qty values
So we have save in:
- In this plugin
- In \Magento\InventoryCatalog\Observer\ProcessSourceItemsObserver::execute
And as result, we will have in old system(legacy stockItem) one value, and in new system(source item) different value
Maybe need to improve ProcessSourceItemsObserver (need to work only for multi stock system)
But looks like it will be better to do this in separate task
@maghamed
* @throws AlreadyExistsException; | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first parameter is \Magento\Framework\Model\ResourceModel\Db\AbstractDb
(in type hinting)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first parameter should have type Magento\CatalogInventory\Model\ResourceModel\Stock\Item
$this->sourceItem->setSku($product->getSku()); | ||
$this->sourceItem->setQuantity($stockItem->getQty()); | ||
$this->sourceItem->setStatus((int)$stockItem->getIsInStock()); | ||
$this->sourceItemsSave->execute([$this->sourceItem]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add type casting to
$this->sourceItem->setQuantity($stockItem->getQty());
The case with creating product in admin panel:
If original field (old field in UI) is not filled then we have NULL value
And after that we will have fail in \Magento\Inventory\Model\SourceItem\Validator\QuantityValidator::validate
use Magento\Framework\Exception\AlreadyExistsException; | ||
|
||
/** | ||
* Class provide Around Plugin on Stock::save to migrate single stock data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
provideS
not Stock::save but Magento\CatalogInventory\Model\ResourceModel\Stock\Item::save
* @throws AlreadyExistsException; | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first parameter should have type Magento\CatalogInventory\Model\ResourceModel\Stock\Item
* @throws AlreadyExistsException; | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- with
around
plugin we could achieve stock deduction for MSI in the same DB transaction that stock deduction for CatalogInventory StockItem happened - with around plugin we could either achieve transactional consistency as we control state before the subject execution and after
/** | ||
* Class provide Around Plugin on Stock::save to migrate single stock data | ||
*/ | ||
class StockItemPlugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, better to sync up with existing plugin we have for Reservation placement.
We could give them similar names and more to the single location.
For example,
1. UpdateLegacyCatalogInventoryAtStockDeductionTime
2. UpdateLegacyCatalogInventoryAtStockSettingTime
Because we have two different business operations with stock:
- We set stock data and overwrite existing data in StockItem (despite the data stored there before) , this happens for example during the Import or synchronization with ERP
- Stock Deduction, which happens during the order placement, when we need to subtract given quantity from the data we have in Stock
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb | ||
{ | ||
$product = $this->productRepository->getById($stockItem->getProductId()); | ||
$this->sourceItem->setSourceId($this->defaultSourceProvider->getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, you should use SourceItemFactory here, because currently you have the dependency in class on SourceItemInterface $sourceItem
, which could be given to you in a dirty state.
But what you need is to construct new SourceItemInterface here - that's the responsibility of factories.
$defaultSourceId = $this->defaultSourceProvider->getId(); | ||
$sql = "INSERT INTO inventory_source_item (source_id, sku, quantity, status) | ||
SELECT $defaultSourceId AS source_id, product.sku, stock_item.qty, stock_item.is_in_stock | ||
FROM cataloginventory_stock_item AS stock_item |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also where
clause with website_id = 0
should be added.
Because some customizations duplicate data in our CatalogInventory cataloginventory_stock_item table and store Stock data per Website
$defaultSourceId = $this->defaultSourceProvider->getId(); | ||
$sql = "INSERT INTO inventory_source_item (source_id, sku, quantity, status) | ||
SELECT $defaultSourceId AS source_id, product.sku, stock_item.qty, stock_item.is_in_stock | ||
FROM cataloginventory_stock_item AS stock_item |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name of the table cataloginventory_stock_item
should also be resolved
$this->sourceItem->setStatus((int)$stockItem->getIsInStock()); | ||
$this->sourceItemsSave->execute([$this->sourceItem]); | ||
|
||
return $proceed($stockItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's wrap this code into the DB transaction
<plugin name="update_legacy_catalog_inventory_plugin" type="Magento\InventoryCatalog\Plugin\InventoryApi\Api\UpdateLegacyCatalogInventoryAtStockDeductionTime"/> | ||
</type> | ||
<type name="Magento\CatalogInventory\Model\ResourceModel\Stock\Item"> | ||
<plugin name="migrate_single_stock_data" type="Magento\InventoryCatalog\Plugin\CatalogInventory\Model\ResourceModel\Stock\UpdateSourceItemAtLegacyCatalogInventoryStockSettingTime" sortOrder="1"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remove sortOrder="1"
<plugin name="migrate_single_stock_data" type="Magento\InventoryCatalog\Plugin\CatalogInventory\Model\ResourceModel\Stock\UpdateSourceItemAtLegacyCatalogInventoryStockSettingTime" sortOrder="1"/> | ||
</type> | ||
<type name="Magento\CatalogInventory\Model\ResourceModel\QtyCounterInterface"> | ||
<plugin name="migrate_single_stock_data_at_qty_counter" type="Magento\InventoryCatalog\Plugin\CatalogInventory\Model\ResourceModel\UpdateSourceItemAtLegacyCatalogInventoryQtyCounter" sortOrder="1"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remove sortOrder="1"
as it's usage is a bad practice for plugin design
@@ -8,7 +8,7 @@ | |||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> | |||
<module name="Magento_InventoryCatalog" setup_version="2.0.0"> | |||
<sequence> | |||
<module name="InventorySales"/> | |||
<module name="Magento_Inventory"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should have a dependency on Magento_InventoryAPI
but not on Inventory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not dependency
But it is sequence, so we need already installed tables which are described in Magento_Inventory
So now declaration is correct
*/ | ||
public function aroundCorrectItemsQty($subject, callable $proceed, array $items, $websiteId, $operator) | ||
{ | ||
$proceed($items, $websiteId, $operator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to wrap with transaction initial method as well
so,
$connection = $this->resourceConnection->getConnection();
+ $connection->beginTransaction();
should be before $proceed
|
||
if ($websiteId !== 0) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we return if websiteId <> 0?
Current implementation re-uses the same stock for all websites.
$sourceItems = $this->sourceItemRepository->getList($searchCriteria)->getItems(); | ||
$sourceItems = array_map( | ||
function (SourceItemInterface $item) use ($productsData, $operator) { | ||
$item->setQuantity($item->getQuantity() + (int)($operator . $productsData[$item->getSku()])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we make casting to (int) but not to (float) ?
looks like we have unneeded rounding
* | ||
* @return int | ||
*/ | ||
private function getInventorySourceItemId(string $sku, int $sourceId): int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we can extract this code in separate object (in test namespace) and reuse this object in the other cases
After that we can remove $connection->query('ALTER TABLE ' . $resourceConnection->getTableName('inventory_source_item') . ' AUTO_INCREMENT 1;');
from fixtures
@@ -13,6 +13,9 @@ | |||
use Magento\TestFramework\Helper\Bootstrap; | |||
use PHPUnit\Framework\TestCase; | |||
|
|||
/** | |||
* @magentoAppArea adminhtml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indexer test must work in any areas
If need you could use trick with is secure area
$connection->getTableName('cataloginventory_stock_item'), | ||
[ | ||
StockItemInterface::QTY => new \Zend_Db_Expr( | ||
sprintf('%s%s', StockItemInterface::QTY, $reservation->getQuantity()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like need only concatenation
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function execute(ReservationInterface $reservation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to unify interface
Method should work with sku
and qty
In this case we could reuse this code when source item is updated
$connection->getTableName('cataloginventory_stock_status'), | ||
[ | ||
StockStatusInterface::QTY => new \Zend_Db_Expr( | ||
sprintf('%s%s', StockStatusInterface::QTY, $reservation->getQuantity()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like logic is not related to Interface
We need to update status
|
||
$product = $this->productRepository->getById($stockItem->getProductId()); | ||
$sourceItem = $this->sourceItemFactory->create(); | ||
$sourceItem->setSourceId($this->defaultSourceProvider->getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to think about case if product is not assigned to Default Source
$selectForInsert = $this->resourceConnection->getConnection()->select()->from( | ||
$stockItemTable, | ||
['source_id' => new \Zend_Db_Expr($defaultSourceId), 'qty', 'is_in_stock'] | ||
)->join($productTable, 'entity_id = product_id', 'sku')->where('website_id = ?', 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check behaviour with staging
$this->resourceConnection->getTableName('cataloginventory_stock_item'), | ||
[ | ||
StockItemInterface::QTY => new \Zend_Db_Expr( | ||
sprintf('%s%s', StockItemInterface::QTY, $reservation->getQuantity()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we have logic error here
Maybe wright code is %s + %s
-- slight refactoring
-- slight refactoring
@@ -8,7 +8,7 @@ | |||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> | |||
<module name="Magento_InventoryCatalog" setup_version="2.0.0"> | |||
<sequence> | |||
<module name="InventorySales"/> | |||
<module name="Magento_InventoryAPI"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like is mistake
In this config we need to describe sequence but not dependency
-- slight refactoring
-- slight refactoring
-- slight refactoring
-- slight refactoring
-- slight refactoring
-- slight refactoring
-- slight refactoring
Description
Fixed Issues (if relevant)
Manual testing scenarios
Contribution checklist