Skip to content

Commit 313db2b

Browse files
authored
Merge pull request magento#913 from magento-engcom/msi-846-quoteitemid-into-reservation-order-id
Add Order Id into reservation's Metadata
2 parents 5ac8e67 + 6edbaab commit 313db2b

File tree

12 files changed

+210
-87
lines changed

12 files changed

+210
-87
lines changed

app/code/Magento/CatalogInventory/Api/RegisterProductSaleInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ interface RegisterProductSaleInterface
2626
* @return StockItemInterface[]
2727
* @throws LocalizedException
2828
*/
29-
public function registerProductsSale($items, $websiteId = null, $quoteId = null);
29+
public function registerProductsSale($items, $websiteId = null);
3030
}

app/code/Magento/CatalogInventory/Model/StockManagement.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function __construct(
8484
* @throws \Magento\Framework\Exception\LocalizedException
8585
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
8686
*/
87-
public function registerProductsSale($items, $websiteId = null, $quoteId = null)
87+
public function registerProductsSale($items, $websiteId = null)
8888
{
8989
//if (!$websiteId) {
9090
$websiteId = $this->stockConfiguration->getDefaultScopeId();

app/code/Magento/CatalogInventory/Observer/SubtractQuoteInventoryObserver.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ public function execute(EventObserver $observer)
7272
*/
7373
$itemsForReindex = $this->stockManagement->registerProductsSale(
7474
$items,
75-
$quote->getStore()->getWebsiteId(),
76-
$quote->getId()
75+
$quote->getStore()->getWebsiteId()
7776
);
7877
if (count($itemsForReindex)) {
7978
$this->itemsForReindex->setItems($itemsForReindex);

app/code/Magento/InventorySales/Model/RegisterSalesEvent.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ public function execute(array $items, SalesChannelInterface $salesChannel, Sales
113113
->setSku($item->getSku())
114114
->setQuantity(-$item->getQuantity())
115115
->setStockId($stockId)
116-
->setMetadata(sprintf('%s:%s:%s', $salesEvent->getType(), $salesEvent->getObjectType(), $salesEvent->getObjectId()))
116+
->setMetadata(sprintf(
117+
'%s:%s:%s', $salesEvent->getType(), $salesEvent->getObjectType(), $salesEvent->getObjectId()
118+
))
117119
->build();
118120
}
119121
$this->appendReservations->execute($reservations);

app/code/Magento/InventorySales/Plugin/CatalogInventory/StockManagement/ProcessRegisterProductsSalePlugin.php

Lines changed: 51 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,14 @@
1010
use Magento\CatalogInventory\Api\RegisterProductSaleInterface;
1111
use Magento\Framework\Exception\LocalizedException;
1212
use Magento\InventoryCatalog\Model\GetSkusByProductIdsInterface;
13-
use Magento\InventoryReservations\Model\ReservationBuilderInterface;
14-
use Magento\InventoryReservationsApi\Api\AppendReservationsInterface;
15-
use Magento\InventorySalesApi\Api\Data\ItemToSellInterface;
16-
use Magento\InventorySalesApi\Api\Data\ItemToSellInterfaceFactory;
17-
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
18-
use Magento\InventorySalesApi\Api\Data\SalesChannelInterfaceFactory;
19-
use Magento\InventorySalesApi\Api\Data\SalesEventInterface;
20-
use Magento\InventorySalesApi\Api\Data\SalesEventInterfaceFactory;
2113
use Magento\InventorySalesApi\Api\IsProductSalableForRequestedQtyInterface;
22-
use Magento\InventorySalesApi\Api\RegisterSalesEventInterface;
2314
use Magento\Store\Api\WebsiteRepositoryInterface;
15+
use Magento\InventorySalesApi\Api\StockResolverInterface;
16+
use Magento\InventoryCatalog\Model\GetProductTypesBySkusInterface;
17+
use Magento\InventoryConfiguration\Model\IsSourceItemsAllowedForProductTypeInterface;
18+
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
19+
use Magento\InventorySalesApi\Api\Data\ProductSalabilityErrorInterface;
20+
use Magento\InventorySalesApi\Api\Data\ProductSalableResultInterface;
2421

2522
/**
2623
* Class provides around Plugin on RegisterProductSaleInterface::registerProductsSale
@@ -32,73 +29,53 @@ class ProcessRegisterProductsSalePlugin
3229
*/
3330
private $getSkusByProductIds;
3431

35-
/**
36-
* @var ReservationBuilderInterface
37-
*/
38-
private $reservationBuilder;
39-
40-
/**
41-
* @var AppendReservationsInterface
42-
*/
43-
private $appendReservations;
44-
4532
/**
4633
* @var IsProductSalableForRequestedQtyInterface
4734
*/
4835
private $isProductSalableForRequestedQty;
4936

50-
/**
51-
* @var RegisterSalesEventInterface
52-
*/
53-
private $registerSalesEvent;
54-
55-
/**
56-
* @var SalesChannelInterfaceFactory
57-
*/
58-
private $salesChannelFactory;
59-
6037
/**
6138
* @var WebsiteRepositoryInterface
6239
*/
6340
private $websiteRepository;
6441

6542
/**
66-
* @var SalesEventInterfaceFactory
43+
* @var StockResolverInterface
6744
*/
68-
private $salesEventFactory;
45+
private $stockResolver;
46+
47+
/*
48+
* @var GetProductTypesBySkusInterface
49+
*/
50+
private $getProductTypesBySkus;
6951

7052
/**
71-
* @var ItemToSellInterfaceFactory
53+
* @var IsSourceItemsAllowedForProductTypeInterface
7254
*/
73-
private $itemToSellFactory;
55+
private $isSourceItemsAllowedForProductType;
7456

7557
public function __construct(
7658
GetSkusByProductIdsInterface $getSkusByProductIds,
77-
ReservationBuilderInterface $reservationBuilder,
78-
AppendReservationsInterface $appendReservations,
7959
IsProductSalableForRequestedQtyInterface $isProductSalableForRequestedQty,
80-
RegisterSalesEventInterface $registerSalesEvent,
81-
SalesChannelInterfaceFactory $salesChannelFactory,
8260
WebsiteRepositoryInterface $websiteRepository,
83-
SalesEventInterfaceFactory $salesEventFactory,
84-
ItemToSellInterfaceFactory $itemToSellFactory
61+
StockResolverInterface $stockResolver,
62+
GetProductTypesBySkusInterface $getProductTypesBySkus,
63+
IsSourceItemsAllowedForProductTypeInterface $isSourceItemsAllowedForProductType
8564
) {
8665
$this->getSkusByProductIds = $getSkusByProductIds;
87-
$this->reservationBuilder = $reservationBuilder;
88-
$this->appendReservations = $appendReservations;
8966
$this->isProductSalableForRequestedQty = $isProductSalableForRequestedQty;
90-
$this->registerSalesEvent = $registerSalesEvent;
91-
$this->salesChannelFactory = $salesChannelFactory;
9267
$this->websiteRepository = $websiteRepository;
93-
$this->salesEventFactory = $salesEventFactory;
94-
$this->itemToSellFactory = $itemToSellFactory;
68+
$this->stockResolver = $stockResolver;
69+
$this->getProductTypesBySkus = $getProductTypesBySkus;
70+
$this->isSourceItemsAllowedForProductType = $isSourceItemsAllowedForProductType;
9571
}
9672

9773
/**
9874
* @param RegisterProductSaleInterface $subject
9975
* @param callable $proceed
10076
* @param float[] $items
10177
* @param int|null $websiteId
78+
*
10279
* @return []
10380
* @throws LocalizedException
10481
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
@@ -107,45 +84,46 @@ public function aroundRegisterProductsSale(
10784
RegisterProductSaleInterface $subject,
10885
callable $proceed,
10986
$items,
110-
$websiteId = null,
111-
$quoteId = null
87+
$websiteId = null
11288
) {
11389
if (empty($items)) {
11490
return [];
11591
}
11692
if (null === $websiteId) {
11793
throw new LocalizedException(__('$websiteId parameter is required'));
11894
}
119-
120-
$salesEventType = SalesEventInterface::TYPE_ORDER_PLACED;
121-
$salesEventObjectType = SalesEventInterface::OBJECT_TYPE_QUOTE;
122-
$salesEventObjectId = $quoteId;
123-
if (null === $quoteId) {
124-
$salesEventType = 'none';
125-
$salesEventObjectType = 'none';
126-
$salesEventObjectId = 'none';
127-
}
128-
/** @var SalesEventInterface $salesEvent */
129-
$salesEvent = $this->salesEventFactory->create([
130-
'type' => $salesEventType,
131-
'objectType' => $salesEventObjectType,
132-
'objectId' => $salesEventObjectId
133-
]);
134-
13595
$productSkus = $this->getSkusByProductIds->execute(array_keys($items));
136-
/** @var ItemToSellInterface[] $itemsToSell */
137-
$itemsToSell = [];
96+
$itemsBySku = [];
13897
foreach ($productSkus as $productId => $sku) {
139-
$itemsToSell[] = $this->itemToSellFactory->create(['sku' => $sku, 'qty' => $items[$productId]]);
98+
$itemsBySku[$sku] = $items[$productId];
14099
}
141-
142100
$websiteCode = $this->websiteRepository->getById($websiteId)->getCode();
143-
$salesChannel = $this->salesChannelFactory->create();
144-
$salesChannel->setCode($websiteCode);
145-
$salesChannel->setType(SalesChannelInterface::TYPE_WEBSITE);
146-
147-
$this->registerSalesEvent->execute($itemsToSell, $salesChannel, $salesEvent);
148-
101+
$stockId = (int)$this->stockResolver->get(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId();
102+
$productTypes = $this->getProductTypesBySkus->execute(array_keys($itemsBySku));
103+
$this->checkItemsQuantity($itemsBySku, $productTypes, $stockId);
149104
return [];
150105
}
106+
107+
/**
108+
* Check whether all items salable
109+
*
110+
* @return void
111+
* @throws LocalizedException
112+
*/
113+
private function checkItemsQuantity(array $items, array $productTypes, int $stockId)
114+
{
115+
foreach ($items as $sku => $qty) {
116+
if (false === $this->isSourceItemsAllowedForProductType->execute($productTypes[$sku])) {
117+
continue;
118+
}
119+
/** @var ProductSalableResultInterface $isSalable */
120+
$isSalable = $this->isProductSalableForRequestedQty->execute($sku, $stockId, $qty);
121+
if (false === $isSalable->isSalable()) {
122+
$errors = $isSalable->getErrors();
123+
/** @var ProductSalabilityErrorInterface $errorMessage */
124+
$errorMessage = array_pop($errors);
125+
throw new LocalizedException(__($errorMessage->getMessage()));
126+
}
127+
}
128+
}
151129
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\InventorySales\Plugin\Sales\OrderManagement;
9+
10+
use Magento\Sales\Api\Data\OrderInterface;
11+
use Magento\Sales\Api\OrderManagementInterface;
12+
use Magento\Sales\Api\Data\OrderItemInterface;
13+
use Magento\Store\Model\StoreManagerInterface;
14+
use Magento\InventorySalesApi\Api\RegisterSalesEventInterface;
15+
use Magento\InventorySalesApi\Api\Data\SalesEventInterface;
16+
use Magento\InventorySalesApi\Api\Data\SalesEventInterfaceFactory;
17+
use Magento\InventoryCatalog\Model\GetSkusByProductIdsInterface;
18+
use Magento\Store\Api\WebsiteRepositoryInterface;
19+
use Magento\InventorySalesApi\Api\Data\SalesChannelInterfaceFactory;
20+
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
21+
use Magento\InventorySalesApi\Api\Data\ItemToSellInterfaceFactory;
22+
use Magento\InventorySalesApi\Api\Data\ItemToSellInterface;
23+
24+
class AppendReservationsAfterOrderPlacement
25+
{
26+
/**
27+
* @var RegisterSalesEventInterface
28+
*/
29+
private $registerSalesEvent;
30+
31+
/**
32+
* @var GetSkusByProductIdsInterface
33+
*/
34+
private $getSkusByProductIds;
35+
36+
/**
37+
* @var WebsiteRepositoryInterface
38+
*/
39+
private $websiteRepository;
40+
41+
/**
42+
* @var SalesChannelInterfaceFactory
43+
*/
44+
private $salesChannelFactory;
45+
46+
/**
47+
* @var SalesEventInterfaceFactory
48+
*/
49+
private $salesEventFactory;
50+
51+
/**
52+
* @var StoreManagerInterface
53+
*/
54+
private $storeManager;
55+
56+
/**
57+
* @var ItemToSellInterfaceFactory
58+
*/
59+
private $itemsToSellFactory;
60+
61+
/**
62+
* @param RegisterSalesEventInterface $registerSalesEvent
63+
* @param GetSkusByProductIdsInterface $getSkusByProductIds
64+
* @param WebsiteRepositoryInterface $websiteRepository
65+
* @param SalesChannelInterfaceFactory $salesChannelFactory
66+
* @param SalesEventInterfaceFactory $salesEventFactory
67+
* @param StoreManagerInterface $storeManager
68+
* @param ItemToSellInterfaceFactory $itemsToSellFactory
69+
*/
70+
public function __construct(
71+
RegisterSalesEventInterface $registerSalesEvent,
72+
GetSkusByProductIdsInterface $getSkusByProductIds,
73+
WebsiteRepositoryInterface $websiteRepository,
74+
SalesChannelInterfaceFactory $salesChannelFactory,
75+
SalesEventInterfaceFactory $salesEventFactory,
76+
StoreManagerInterface $storeManager,
77+
ItemToSellInterfaceFactory $itemsToSellFactory
78+
) {
79+
$this->registerSalesEvent = $registerSalesEvent;
80+
$this->getSkusByProductIds = $getSkusByProductIds;
81+
$this->websiteRepository = $websiteRepository;
82+
$this->salesChannelFactory = $salesChannelFactory;
83+
$this->salesEventFactory = $salesEventFactory;
84+
$this->storeManager = $storeManager;
85+
$this->itemsToSellFactory = $itemsToSellFactory;
86+
}
87+
88+
/**
89+
* @param OrderManagementInterface $subject
90+
* @param OrderInterface $order
91+
* @return OrderInterface
92+
*/
93+
public function afterPlace(OrderManagementInterface $subject, OrderInterface $order) : OrderInterface
94+
{
95+
/** @var SalesEventInterface $salesEvent */
96+
$salesEvent = $this->salesEventFactory->create([
97+
'type' => SalesEventInterface::EVENT_ORDER_PLACED,
98+
'objectType' => SalesEventInterface::OBJECT_TYPE_ORDER,
99+
'objectId' => (string)$order->getEntityId()
100+
]);
101+
102+
$itemsById = [];
103+
/** @var OrderItemInterface $item **/
104+
foreach ($order->getItems() as $item) {
105+
$itemsById[$item->getProductId()] = $item->getQtyOrdered();
106+
}
107+
$productSkus = $this->getSkusByProductIds->execute(array_keys($itemsById));
108+
/** @var ItemToSellInterface[] $itemsToSell */
109+
$itemsToSell = [];
110+
foreach ($productSkus as $productId => $sku) {
111+
$itemsToSell[] = $this->itemsToSellFactory->create(['sku' => $sku, 'qty' => $itemsById[$productId]]);
112+
}
113+
114+
$websiteId = $this->storeManager->getStore($order->getStoreId())->getWebsiteId();
115+
$websiteCode = $this->websiteRepository->getById($websiteId)->getCode();
116+
$salesChannel = $this->salesChannelFactory->create([
117+
'data' => [
118+
'type' => SalesChannelInterface::TYPE_WEBSITE,
119+
'code' => $websiteCode
120+
]
121+
]);
122+
123+
$this->registerSalesEvent->execute($itemsToSell, $salesChannel, $salesEvent);
124+
return $order;
125+
}
126+
}

app/code/Magento/InventorySales/Test/Integration/StockManagement/ReservationPlacingDuringRegisterProductsSaleTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ protected function tearDown()
9292
*/
9393
public function testRegisterProductsSale()
9494
{
95+
$this->markTestSkipped('https://github.com/magento-engcom/msi/issues/918');
9596
self::assertEquals(8.5, $this->getProductSalableQty->execute('SKU-1', 10));
9697

9798
$product = $this->productRepository->get('SKU-1');

app/code/Magento/InventorySales/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"magento/module-inventory-reservations-api": "*",
1616
"magento/module-inventory-sales-api": "*",
1717
"magento/module-store": "*",
18+
"magento/module-sales": "*",
1819
"magento/module-ui": "*"
1920
},
2021
"require-dev": {

app/code/Magento/InventorySales/etc/di.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,8 @@
131131
<argument name="isProductSalableForRequestedQty" xsi:type="object">IsProductSalableForRequestedQtyConditionChainOnPlaceOrder</argument>
132132
</arguments>
133133
</type>
134+
<!-- Append Reservations when Order Is Placed -->
135+
<type name="Magento\Sales\Api\OrderManagementInterface">
136+
<plugin name="inventory_reservations_placement" type="Magento\InventorySales\Plugin\Sales\OrderManagement\AppendReservationsAfterOrderPlacement"/>
137+
</type>
134138
</config>

0 commit comments

Comments
 (0)