-
Notifications
You must be signed in to change notification settings - Fork 253
Add IsProductSalableForRequestedQty service #629
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
Add IsProductSalableForRequestedQty service #629
Conversation
- Add IsProductSalableForRequestedQtyConditionChain - Add definition and implementation of conditions for the IsProductSalableForRequestedQtyConditionChain - Add conditions tests (except for IsCorrectQtyCondition)
*/ | ||
public function execute(string $sku, int $stockId, float $requestedQty): bool | ||
{ | ||
return $this->isSalableWithReservationsCondition->execute($sku, $stockId); |
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 condition looks incorrect as here we check
that product is salable and its Qty is more than 0
$qtyWithReservation = $stockItemData['quantity'] + $this->getReservationsQuantity->execute($sku, $stockId);
return (bool)$stockItemData['is_salable'] && $qtyWithReservation > 0.0001;
I believe for IsProductSalableForRequestedQtyInterface
we would not have this condition at all, as you make this check in IsCorrectQty condition
$qtyWithReservation = $stockItemData['quantity'] + $this->getReservationsQuantity->execute($sku, $stockId);
if ($requestedQty > $qtyWithReservation) {
return false;
}
|
||
namespace Magento\InventorySales\Model\IsProductSalableForRequestedQtyCondition; | ||
|
||
use Magento\CatalogInventory\Model\Configuration; |
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.
you should have dependency on Interface, \Magento\CatalogInventory\Api\StockConfigurationInterface
not concrete implementation
if (($globalEnableQtyIncrements | ||
&& $this->mathDivision->getExactDivision($requestedQty, $this->configuration->getQtyIncrements())) | ||
|| ($stockItemConfiguration->isEnableQtyIncrements() | ||
&& $this->mathDivision->getExactDivision($requestedQty, $stockItemConfiguration->getQtyIncrements()))) { |
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 check looks excessive.
As StockItemConfiguration which proxy call to legacy StockItemInterface has a method
public function getQtyIncrements(): float;
that's why initial check currently looks like
{
$result = new \Magento\Framework\DataObject();
if ($stockItem->getSuppressCheckQtyIncrements()) {
return $result;
}
$qtyIncrements = $stockItem->getQtyIncrements() * 1;
if ($qtyIncrements && $this->mathDivision->getExactDivision($qty, $qtyIncrements) != 0) {
$result->setHasError(true)
->setQuoteMessage(__('Please correct the quantity for some products.'))
->setErrorCode('qty_increments')
->setQuoteMessageIndex('qty');
if ($stockItem->getIsChildItem()) {
$result->setMessage(
__(
'You can buy %1 only in quantities of %2 at a time.',
$stockItem->getProductName(),
$qtyIncrements
)
);
} else {
$result->setMessage(__('You can buy this product only in quantities of %1 at a time.', $qtyIncrements));
}
}
return $result;
So, taking $stockItemConfiguration->getQtyIncrements() should be enough
This function under the hood looks like
public function getQtyIncrements()
{
if ($this->qtyIncrements === null) {
if ($this->getEnableQtyIncrements()) {
if ($this->getUseConfigQtyIncrements()) {
$this->qtyIncrements = $this->stockConfiguration->getQtyIncrements($this->getStoreId());
} else {
$this->qtyIncrements = (int) $this->getData(static::QTY_INCREMENTS);
}
}
if ($this->qtyIncrements <= 0) {
$this->qtyIncrements = false;
}
}
return $this->qtyIncrements;
}
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.
I would change it to the following (note: I added the explicit strict comparison with 0
)
if ($this->mathDivision->getExactDivision($requestedQty, $this->configuration->getQtyIncrements()) !== 0
|| $this->mathDivision->getExactDivision(
$requestedQty,
$stockItemConfiguration->getQtyIncrements()
) !== 0) {
return false;
}
['SKU-2', 30, 1, true], | ||
['SKU-3', 10, 1, false], | ||
['SKU-3', 20, 1, false], | ||
['SKU-3', 30, 1, false], |
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 always make a check for Qty = 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.
the qty is never used because the tested methods are only proxy methods;
for that reason, let's discuss removing those tests at all
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 discussed in our call, we decided to leave the tests
You did not cover with integration test the most complex logic - |
return false; | ||
} | ||
|
||
$globalMaxSaleQty = $this->configuration->getMaxSaleQty(); |
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.
For Quote, we need to preserve behavior from this function
\Magento\CatalogInventory\Model\StockStateProvider::checkQuoteItemQty
which works with Min Sale Qty
if ($stockItem->getMinSaleQty() && $qty < $stockItem->getMinSaleQty()) {
$result->setHasError(true)
->setMessage(__('The fewest you may purchase is %1.', $stockItem->getMinSaleQty() * 1))
->setErrorCode('qty_min')
->setQuoteMessage(__('Please correct the quantity for some products.'))
->setQuoteMessageIndex('qty');
return $result;
}
if ($stockItem->getMaxSaleQty() && $qty > $stockItem->getMaxSaleQty()) {
$result->setHasError(true)
->setMessage(__('The most you may purchase is %1.', $stockItem->getMaxSaleQty() * 1))
->setErrorCode('qty_max')
->setQuoteMessage(__('Please correct the quantity for some products.'))
->setQuoteMessageIndex('qty');
return $result;
}
That actually means that we need to have 2 chains:
- one for Quote
- another for Checkout
this could be done with VirtualType DI configuration
* @inheritdoc | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function execute(string $sku, int $stockId, float $requestedQty): bool |
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 need to return result object, not just typical bool
The same as for Validators.
Because we somehow need to return validation message, what exactly went wrong and what should be fixed, like in existing code:
if ($stockItem->getMaxSaleQty() && $qty > $stockItem->getMaxSaleQty()) {
$result->setHasError(true)
->setMessage(__('The most you may purchase is %1.', $stockItem->getMaxSaleQty() * 1))
->setErrorCode('qty_max')
->setQuoteMessage(__('Please correct the quantity for some products.'))
->setQuoteMessageIndex('qty');
return $result;
}
I propose to return next structure
interface IsProductSalableResultInterface
{
/**
* @return bool
*/
public function isSalable(): bool;
/**
* @return ProductSalabilityError[]
*/
public function getErrors(): array;
}
interface ProductSalabilityError
{
/**
* @return string
*/
public function getCode(): string;
/**
* @return string
*/
public function getMessage(): string;
}
ProductSalabilityError entities are created with ProductSalabilityErrorFactory
Note: the IsCorrectQtyConditionTest is still under development
/** | ||
* @inheritdoc | ||
*/ | ||
public function execute(string $sku, StockItemConfigurationInterface $stockItemConfiguration) |
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.
Our Get Service for Stock Item configuration looks like
interface GetStockItemConfigurationInterface
{
/**
* Return null if configuration for sku per stock is not exist
*
* @param string $sku
* @param int $stockId
* @return StockItemConfigurationInterface|null
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function execute(string $sku, int $stockId);
}
So, we define the scope with $sku and $stockId
existing Save service doesn't have $stockId, so all the config data would be saved for Default stock.
->addColumn(
'stock_id',
\Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
null,
['unsigned' => true, 'nullable' => false, 'default' => '0'],
'Stock Id'
)
in cataloginventory_stock_item
Also, we need to remove unneeded constants
interface StockItemConfigurationInterface
{
const SKU = 'sku';
const STOCK_ID = 'stock_id';
…rrectQtyCondition
]) | ||
]; | ||
return $this->isProductSalableResultFactory->create(['errors' => $errors]); | ||
} |
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.
Hey @aleron75 ,
I was looking at your PR and looks like you have an error in this check
// Out-of-Stock Threshold
// TODO verify whether we should use < or <=
$globalMinQty = $this->configuration->getMinQty();
if (($stockItemConfiguration->isUseConfigMinQty() == 1 && $requestedQty < $globalMinQty)
|| ($stockItemConfiguration->isUseConfigMinQty() == 0
&& $requestedQty < $stockItemConfiguration->getMinQty()
)) {
$errors = [
$this->productSalabilityErrorFactory->create([
'code' => 'is_correct_qty-out_of_stock_threshold',
'message' => __('The requested qty is not available')
])
];
return $this->isProductSalableResultFactory->create(['errors' => $errors]);
}
you make it the same as getMinSaleQty
, but these configuration options are different
getMinSaleQty
- represents minimal amount in the shopping cart, while MinQty
is responsible for minimal Qty available in Stock
for example, in Legacy Magento we do this check :
if ($stockItem->getQty() - $stockItem->getMinQty() - $qty < 0) { .. }
something similar should be here as well
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, you are right, I misunderstood the logic, will fix it
…torySalesApi on InventorySales
This work-in-progress PR is related to issue #520 and is intended to discuss about adding a new service for InventorySalesAPI to be used to check whether a customer can buy or put to Shopping Cart a certain amount of product.
The first two commits do the following:
ManageConfigConditionTest
class toManageStockConditionTest
IsProductSalableForRequestedQtyConditionChain
IsProductSalableForRequestedQtyConditionChain
IsCorrectQtyCondition
)Let's also discuss about naming.