-
Notifications
You must be signed in to change notification settings - Fork 254
Introduce Stock quantity calculation #94
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
Changes from 3 commits
46c9efa
f37b38e
5c20080
a82fe04
c6a642d
2db99e4
8f2b014
2ecd5bc
eeb4545
7ae88aa
89209cc
284738f
555110a
13a74ee
328d920
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\Inventory\Model; | ||
|
||
use Magento\Inventory\Model\ResourceModel\Reservation\ReservationQuantity; | ||
use Magento\Inventory\Model\ResourceModel\Stock\StockItemQuantity; | ||
use Magento\InventoryApi\Api\GetProductQuantityInStockInterface; | ||
|
||
/** | ||
* Return Quantity of products available to be sold by Product SKU and Stock Id | ||
* | ||
* @see \Magento\InventoryApi\Api\GetProductQuantityInStockInterface | ||
* @api | ||
*/ | ||
class GetProductQuantityInStock implements GetProductQuantityInStockInterface | ||
{ | ||
/** | ||
* GetProductQuantityInStock constructor. | ||
* | ||
* @param StockItemQuantity $stockItemQty | ||
* @param ReservationQuantity $reservationQty | ||
*/ | ||
public function __construct( | ||
StockItemQuantity $stockItemQty, | ||
ReservationQuantity $reservationQty | ||
) { | ||
$this->stockItemQty = $stockItemQty; | ||
$this->reservationQty = $reservationQty; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function execute(string $sku, int $stockId): float | ||
{ | ||
$productQtyInStock = $this->stockItemQty->execute($sku, $stockId) - | ||
$this->reservationQty->execute($sku, $stockId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we are putting both positive and negative values to the Reservation. For example,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's because I intended a "reserved quantity" (positive number) as something I don't have to consider as available because it's "reserved", thus not available and thus something that has to be subtracted from total quantity. Your interpretation is opposite and it's ok to use it but that also means that there is space for interpretation that is error prone. I will fix as you suggest but probably we should find a naming that's less subject to interpretation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just re-read your spec here and you were perfectly clear on the meaning of adding (+) and subtracting (-) but for some reason, I inverted it in my mind |
||
return (float) $productQtyInStock; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,7 @@ public function setMetadata(string $metadata = null): ReservationBuilderInterfac | |
public function build(): ReservationInterface | ||
{ | ||
$data = [ | ||
ReservationInterface::RESERVATION_ID => null, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you add this string? By default reservation id is null There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason is that If we don't pass the reservation ID, the constructor (used by the object manager) will throw an exception because it's a constructor mandatory parameter |
||
ReservationInterface::STOCK_ID => $this->stockId, | ||
ReservationInterface::SKU => $this->sku, | ||
ReservationInterface::QUANTITY => $this->quantity, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\Inventory\Model\ResourceModel\Reservation; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on which line do you prefer to add this directive?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just find this tool: https://github.com/dypa/declare-strict-types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My choose is |
||
|
||
use Magento\Framework\App\ResourceConnection; | ||
use Magento\Inventory\Setup\Operation\CreateReservationTable; | ||
use Magento\InventoryApi\Api\Data\ReservationInterface; | ||
|
||
/** | ||
* The resource model responsible for retrieving Reservation Quantity. | ||
* Used by Service Contracts that are agnostic to the Data Access Layer. | ||
*/ | ||
class ReservationQuantity | ||
{ | ||
/** | ||
* @var ResourceConnection | ||
*/ | ||
private $resource; | ||
|
||
/** | ||
* ReservationQuantity constructor. | ||
* | ||
* @param ResourceConnection $resource | ||
*/ | ||
public function __construct( | ||
ResourceConnection $resource | ||
) { | ||
$this->resource = $resource; | ||
} | ||
|
||
/** | ||
* Given a product sku and a stock id, return reservation quantity. | ||
* | ||
* @param string $sku | ||
* @param int $stockId | ||
* @return float | ||
*/ | ||
public function execute(string $sku, int $stockId): float | ||
{ | ||
$connection = $this->resource->getConnection(); | ||
|
||
$reservationTableName = $connection->getTableName(CreateReservationTable::TABLE_NAME_RESERVATION); | ||
|
||
$select = $connection->select() | ||
->from($reservationTableName, [ReservationInterface::QUANTITY => 'sum(' . ReservationInterface::QUANTITY . ')']) | ||
->where(ReservationInterface::SKU . '=?', $sku) | ||
->where(ReservationInterface::STOCK_ID . '=?', $stockId) | ||
->limit(1); | ||
|
||
$reservationQty = $connection->fetchOne($select); | ||
if (false === $reservationQty) { | ||
$reservationQty = 0; | ||
} | ||
|
||
return (float) $reservationQty; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\Inventory\Model\ResourceModel\Stock; | ||
|
||
use Magento\Framework\App\ResourceConnection; | ||
use Magento\Inventory\Indexer\Alias; | ||
use Magento\Inventory\Indexer\IndexNameBuilder; | ||
use Magento\Inventory\Indexer\IndexNameResolverInterface; | ||
use Magento\Inventory\Indexer\StockItem\IndexStructure as StockItemIndex; | ||
use Magento\Inventory\Indexer\StockItemIndexerInterface; | ||
|
||
/** | ||
* The resource model responsible for retrieving StockItem Quantity. | ||
* Used by Service Contracts that are agnostic to the Data Access Layer. | ||
*/ | ||
class StockItemQuantity | ||
{ | ||
/** | ||
* @var ResourceConnection | ||
*/ | ||
private $resource; | ||
|
||
/** | ||
* @var IndexNameBuilder | ||
*/ | ||
private $indexNameBuilder; | ||
|
||
/** | ||
* @var IndexNameResolverInterface | ||
*/ | ||
private $indexNameResolver; | ||
|
||
/** | ||
* StockItemQuantity constructor. | ||
* | ||
* @param ResourceConnection $resource | ||
* @param IndexNameBuilder $indexNameBuilder | ||
* @param IndexNameResolverInterface $indexNameResolver | ||
*/ | ||
public function __construct( | ||
ResourceConnection $resource, | ||
IndexNameBuilder $indexNameBuilder, | ||
IndexNameResolverInterface $indexNameResolver | ||
) { | ||
$this->resource = $resource; | ||
$this->indexNameBuilder = $indexNameBuilder; | ||
$this->indexNameResolver = $indexNameResolver; | ||
} | ||
|
||
/** | ||
* Given a product sku and a stock id, return stock item quantity. | ||
* | ||
* @param string $sku | ||
* @param int $stockId | ||
* @return float | ||
*/ | ||
public function execute(string $sku, int $stockId): float | ||
{ | ||
$indexName = $this->indexNameBuilder | ||
->setIndexId(StockItemIndexerInterface::INDEXER_ID) | ||
->addDimension('stock_', $stockId) | ||
->setAlias(Alias::ALIAS_MAIN) | ||
->create(); | ||
|
||
|
||
$stockItemTableName = $this->indexNameResolver->resolveName($indexName); | ||
|
||
$connection = $this->resource->getConnection(); | ||
|
||
$select = $connection->select() | ||
->from($stockItemTableName, [StockItemIndex::QUANTITY]) | ||
->where(StockItemIndex::SKU . '=?', $sku) | ||
->limit(1); | ||
|
||
|
||
$stockItemQty = $connection->fetchOne($select); | ||
if (false === $stockItemQty) { | ||
$stockItemQty = 0; | ||
} | ||
|
||
return (float) $stockItemQty; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\InventoryApi\Api; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, the namespace is correct. |
||
|
||
/** | ||
* Service which returns Quantity of products available to be sold by Product SKU and Stock Id | ||
* | ||
* @api | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, we need to add this service to Web API webapi.xml in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, wondering if I can use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have open task for refactoring of our ACL rules But in this case need to discuss maybe need to relate on some product resource |
||
*/ | ||
interface GetProductQuantityInStockInterface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's a good question But, having "Stock" entity introduced by MSI. Currently, this service accepts Based on the above IMO current naming - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, here we have a term "stock" that violates the "ubiquitous language" DDD principle because in the same context it can mean both the "available quantity" (more high-level) and the "virtual aggregation" (more low-level); probably we should think about it; what's more, as I introduced during our last weekly meeting, in the future, when we will have the sales channel connected to our stock (virtual aggregation), maybe this service could be better named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aleron75 you made a very true statement regarding violation of "ubiquitous language". Regarding So, semantically it's incorrect to say - give me product quantity for current Sales Channel providing $stockId as the parameter. Does it make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
you're perfectly right, it should be instead: "give me product quantity for current Sales Channel providing some channel id as the parameter" where the by the way at the moment let's leave the service as is and just postpone this discussion when we'll work on the channel entity; thank you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aleron75 100% agree, let's see how our interfaces will evolve and later align naming accordingly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My opinion is to follow YAGNI principle. So we should not create service related to Sales Channels. We will do it when we will work on this task. |
||
{ | ||
/** | ||
* Get Product Quantity for given SKU in a given Stock | ||
* | ||
* @param string $sku | ||
* @param int $stockId | ||
* @return float | ||
*/ | ||
public function execute(string $sku, int $stockId): float; | ||
} |
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.
This service looks like a perfect candidate to be covered with both:
and
testing
Uh oh!
There was an error while loading. Please reload this page.
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 to have two separate interfaces for API and SPI is redundant
Implementation of API is only proxy to SPI
We have the same situation with SourceItemsSave operation
And we decided to have only one interface (which presents API and SPI in one interface)
Interface
https://github.com/magento-engcom/msi/blob/develop/app/code/Magento/InventoryApi/Api/SourceItemsSaveInterface.php
Implementation
https://github.com/magento-engcom/msi/blob/develop/app/code/Magento/Inventory/Model/SourceItem/Command/SourceItemsSave.php
For Repository we need to extract SPI parts in separate set of interfaces due Repository represents Facade pattern (accumulate some functionality together)
But for single command, it looks like overhead (after creating we need to maintain this code)
Thus now looks like we have some mess in our interfaces:
GetProductQuantityInStockInterface
GetProductQuantityInterface
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.
As proposed by @naydav and discussed with @maghamed we will simplify the implementation getting rid of the command layer;
thus we will delegate effective computation to resource model, injected into the services in place of the command.