Magento2.3 多庫存工作流程

Magento 2.3 CE is coming with new inventory feature MSI or Multi Source Inventory. In this post we will explain how it works and where are modifications in code and database. If you take a look on vendor/magento folder you will see more then 30 new modules with prefix module-inventory. These all modules are in charge for new inventory logic.

So far Magento catalog-inventory didn’t have major changes in logic, but from now in Magento 2.3 inventory management has new term and workflow. This term is reservation. In shortly on every order place reservation will be created. Reservation are stored in database table: inventory_reservation. In screen shot below you can see structure of inventory_reservation table.

In this table first record is reservation, quantity value is -1 and second row is compensation of reservation for the same product. Second row is inserted after shipment is created. Second record has quantity value 1 and our reservation is compensated (-1+1=0).

The core logic how new inventory work is in module: module-inventory-sales. There are two important plugins. First is Magento\InventorySales\Plugin\CatalogInventory\ StockManagement\ProcessRegisterProductsSalePlugin and method aroundRegisterProductsSale. In method aroundRegisterProductSales there is logic for checking is specific product salable for requested qty.

Chunk of logic from method aroundRegisterProductsSale.

 
1
2
3
//stock_id is int value
//itemsBySkue is array with sku => qty
$ this -> checkItemsQuantity -> execute ( $ itemsBySku , $ stockId ) ;

Method aroundRegisterProductsSale overrides old method registerProductsSale in class Magento\CatalogInventory\Model\StockManagement  and always return  empty array.

With empty array, plugin prevent reindex process in observer class Magento\CatalogInventory\Observer\SubtractQuoteInventoryObserver:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function execute ( EventObserver $observer )
     {
         /** @var \Magento\Quote\Model\Quote $quote */
         $quote = $observer -> getEvent ( ) -> getQuote ( ) ;
 
         // Maybe we've already processed this quote in some event during order placement
         // e.g. call in event 'sales_model_service_quote_submit_before' and later in 'checkout_submit_all_after'
         if ( $quote -> getInventoryProcessed ( ) ) {
             return $this ;
         }
         $items = $this -> productQty -> getProductQty ( $quote -> getAllItems ( ) ) ;
 
         /**
         * Remember items
         */
         $itemsForReindex = $this -> stockManagement -> registerProductsSale (
             $items ,
             $quote -> getStore ( ) -> getWebsiteId ( )
         ) ;
        
         //$itemsForReindex is always empty array and reindex is not happening!!!!!!!!!!!  
 
         if ( count ( $itemsForReindex ) ) {
             $this -> itemsForReindex -> setItems ( $itemsForReindex ) ;
         }
         $quote -> setInventoryProcessed ( true ) ;
         return $this ;
     }

Second plugin is in charge for creating reservation is: Magento\InventorySales\Plugin\Sales\OrderManagement\AppendReservationsAfterOrderPlacementPlugin and method afterPlace.

This method is called after method place in class Magento\Sales\Model\Service\OrderService. Below you can see method place:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function place ( \ Magento \ Sales \ Api \ Data \ OrderInterface $order )
     {
         // transaction will be here
         //begin transaction
         try {
            
             $order -> place ( ) ;
             //afterPlace method is called after place method and after mysql commit, this is big problem.
             return $this -> orderRepository -> save ( $order ) ;
             //commit
         } catch ( \ Exception $e ) {
             if ( $e instanceof CommandException ) {
                 $this -> paymentFailures -> handle ( ( int ) $order -> getQuoteId ( ) , __ ( $e -> getMessage ( ) ) ) ;
             }
             throw $e ;
             //rollback;
         }
     }

Mehod afterPlace from class Magento\InventorySales\Plugin\Sales\OrderManagement\AppendReservationsAfterOrderPlacementPlugin  is in charge for inserting reservation in db table inventory_reservation. 

From my experience there is possible problem, because reservation is created after order place (after mysql commit). With this inventory logic on web site with big traffic there is possibility that order will be placed, but qty (stock items) are already reserved, or product is not salable for specific qty. It means that although you placed order, maybe there is no enough items on stock (items are already reserved) and merchant will have to refund order.

And last step is decreasing qty from physical stock

If merchant ship order with non virtual products then qty from stock will be decreased when shipment is created, but if order contains only virtual products then qty will be decrease after invoice is created.

My conclusion is that this workflow will be problem for most of merchant, because from my experience merchant don’t like to ship from magento.  It means that we will have to modify this process.

All logic for decreasing stock is in module “module-inventory-shipping”. This module has two observers:

  • Magento\InventoryShipping\Observer\SourceDeductionProcessor event “sales_order_shipment_save_after”
  • Magento\InventoryShipping\Observer\VirtualSourceDeductionProcessor event “sales_order_invoice_save_after”


Last thing which we have to mention is two classes from module module-inventory-sales which is in charge for regulation is product salable and is product salable for requested qty condition.

First class Magento\InventorySales\Model\IsProductSalableCondition\IsSalableWithReservationsCondition checks current stock qty and current number of reservation from db table inventory_reservation and if curent qtyWithreservation is grether then $stockItemConfiguration->getMinQty() then product is salable. Logic you can see below:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function execute ( string $sku , int $stockId ) : bool
     {
         $stockItemData = $this -> getStockItemData -> execute ( $sku , $stockId ) ;
         if ( null === $stockItemData ) {
             // Sku is not assigned to Stock
             return false ;
         }
 
         $productType = $this -> getProductTypesBySkus -> execute ( [ $sku ] ) [ $sku ] ;
         if ( false === $this -> isSourceItemManagementAllowedForProductType -> execute ( $productType ) ) {
             return ( bool ) $stockItemData [ GetStockItemDataInterface:: IS_SALABLE ] ;
         }
 
         /** @var StockItemConfigurationInterface $stockItemConfiguration */
         $stockItemConfiguration = $this -> getStockItemConfiguration -> execute ( $sku , $stockId ) ;
         $qtyWithReservation = $stockItemData [ GetStockItemDataInterface:: QUANTITY ] +
             $this -> getReservationsQuantity -> execute ( $sku , $stockId ) ;
 
         //!!!!check is salable and check qtywithreservation !!!!!!!!!!!
         return ( bool ) $stockItemData [ GetStockItemDataInterface:: IS_SALABLE ] &&
             $qtyWithReservation > $stockItemConfiguration -> getMinQty ( ) ;
     }

Second class checks is product salable for requested qty condition Magento\InventorySales\Model\IsProductSalableForRequestedQtyCondition\IsSalableWithReservationsCondition this class has method execute and take a look below on methods code.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function execute ( string $sku , int $stockId , float $requestedQty ) : ProductSalableResultInterface
     {
         $stockItemData = $this -> getStockItemData -> execute ( $sku , $stockId ) ;
         if ( null === $stockItemData ) {
             $errors = [
                 $this -> productSalabilityErrorFactory -> create ( [
                     'code' = > 'is_salable_with_reservations-no_data' ,
                     'message' = > __ ( 'The requested sku is not assigned to given stock' )
                 ] )
             ] ;
             return $this -> productSalableResultFactory -> create ( [ 'errors' = > $errors ] ) ;
         }
 
         /** @var StockItemConfigurationInterface $stockItemConfiguration */
         $stockItemConfiguration = $this -> getStockItemConfiguration -> execute ( $sku , $stockId ) ;
 
         $qtyWithReservation = $stockItemData [ GetStockItemDataInterface:: QUANTITY ] +
             $this -> getReservationsQuantity -> execute ( $sku , $stockId ) ;
         $qtyLeftInStock = $qtyWithReservation - $stockItemConfiguration -> getMinQty ( ) - $requestedQty ;
         $isEnoughQty = ( bool ) $stockItemData [ GetStockItemDataInterface:: IS_SALABLE ] && $qtyLeftInStock >= 0 ;
         if ( ! $isEnoughQty ) {
             $errors = [
                 $this -> productSalabilityErrorFactory -> create ( [
                     'code' = > 'is_salable_with_reservations-not_enough_qty' ,
                     'message' = > __ ( 'The requested qty is not available' )
                 ] )
             ] ;
             return $this -> productSalableResultFactory -> create ( [ 'errors' = > $errors ] ) ;
         }
         return $this -> productSalableResultFactory -> create ( [ 'errors' = > [ ] ] ) ;
     }

This method checks for requested qty is product available. Method calculated total available qty with reservation, check variable $qtyWithReservation.

This is short explanation how new MSI inventory works and my conclusion is that we will have big problem if you upgrade Magento 2.2 to Magento 2.3. Merchants will have to change own process. They have to ship order, but if you don’t need to use multi source inventory then my suggestion is that disable it.

In next post we will explain how to configure Multi Source Inventory in Magento 2.3, what is stock and what is source, until then if you have any questions feel free to send comment 

 

來源: https://potkoc.com/2018/12/23/multi-source-inventory-msi-in-magento-2-3-reservation-logic/

更多資料: https://www.rakeshjesadiya.com/check-is-product-assigned-to-stock-or-not-using-multi-store-inventory-msi-magento-2/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章