Skip to content
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

Merged
merged 46 commits into from
Dec 11, 2017

Conversation

bartekszymanski
Copy link
Contributor

@bartekszymanski bartekszymanski commented Nov 9, 2017

Description

Fixed Issues (if relevant)

  1. Migrate Single Stock Data From CatalogInventory to MSI SourceItems #178: Migrate Single Stock Data From CatalogInventory to MSI SourceItems
  2. Need to add "@magentoAppArea adminhtml" to all tests which are used with InventoryApi product fixtures #263: Need to add "@magentoAppArea adminhtml" to all tests which are used with InventoryApi product fixtures

Manual testing scenarios

Contribution checklist

  • Add data to default source (Data Install) when merchant migrates from Magento 2.0.X, 2.1.X, 2.2.X -> Magento 2.3 with MSI
  • Plugin when saving via StockItem resource model.
  • Test save Stock Data in the scope of Product. And check new storage
  • Test to save Old Stock Item via StockItem Repository and check new storage
  • Test to save Old Stock Item via Resource Model and check data in New storage

@bartekszymanski bartekszymanski self-assigned this Nov 9, 2017
* @throws AlreadyExistsException;
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function aroundSave(Interceptor $subject, callable $proceed, Item $stockItem): AbstractDb
Copy link

@naydav naydav Nov 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why do we use around plugin instead of after plugin?
  2. 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. with around plugin we could achieve stock deduction for MSI in the same DB transaction that stock deduction for CatalogInventory StockItem happened
  2. 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
Copy link

@naydav naydav Nov 23, 2017

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)

Copy link
Contributor

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:

  1. 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
  2. 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)
Copy link

@naydav naydav Nov 23, 2017

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

Copy link

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)
Copy link

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

Copy link

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:

  1. In this plugin
  2. 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
Copy link

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)

Copy link
Contributor

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]);
Copy link

@naydav naydav Nov 23, 2017

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. with around plugin we could achieve stock deduction for MSI in the same DB transaction that stock deduction for CatalogInventory StockItem happened
  2. 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
Copy link
Contributor

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:

  1. 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
  2. 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());
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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"/>
Copy link
Contributor

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"/>
Copy link
Contributor

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"/>
Copy link
Contributor

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

Copy link

@naydav naydav Dec 9, 2017

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);
Copy link
Contributor

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;
}
Copy link
Contributor

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()]));
Copy link
Contributor

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
Copy link

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
Copy link

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())
Copy link

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)
Copy link

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())
Copy link

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());
Copy link

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);
Copy link

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())
Copy link

@naydav naydav Dec 8, 2017

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

@@ -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"/>
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants