Skip to content

Commit

Permalink
Merge pull request #3 from AmpersandHQ/source-selection-order-placement
Browse files Browse the repository at this point in the history
Force source selection on order placement
  • Loading branch information
Cristian Quiroz authored Nov 19, 2019
2 parents e55998d + df5d6f4 commit 4b4a928
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 86 deletions.
124 changes: 124 additions & 0 deletions Model/GetInventoryRequestFromOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);

namespace Ampersand\DisableStockReservation\Model;

use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestExtensionInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\AddressInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\AddressInterface;
use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order\Address;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Store\Model\StoreManagerInterface;

class GetInventoryRequestFromOrder
{
/**
* @var InventoryRequestInterfaceFactory
*/
private $inventoryRequestFactory;

/**
* @var InventoryRequestExtensionInterfaceFactory
*/
private $inventoryRequestExtensionFactory;

/**
* @var OrderRepositoryInterface
*/
private $orderRepository;

/**
* @var AddressInterfaceFactory
*/
private $addressInterfaceFactory;

/**
* @var StoreManagerInterface
*/
private $storeManager;

/**
* @var StockByWebsiteIdResolverInterface
*/
private $stockByWebsiteIdResolver;

/**
* @param InventoryRequestInterfaceFactory $inventoryRequestFactory
* @param InventoryRequestExtensionInterfaceFactory $inventoryRequestExtensionFactory
* @param OrderRepositoryInterface $orderRepository
* @param AddressInterfaceFactory $addressInterfaceFactory
* @param StoreManagerInterface $storeManager
* @param StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver
*/
public function __construct(
InventoryRequestInterfaceFactory $inventoryRequestFactory,
InventoryRequestExtensionInterfaceFactory $inventoryRequestExtensionFactory,
OrderRepositoryInterface $orderRepository,
AddressInterfaceFactory $addressInterfaceFactory,
StoreManagerInterface $storeManager,
StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver
) {
$this->inventoryRequestFactory = $inventoryRequestFactory;
$this->inventoryRequestExtensionFactory = $inventoryRequestExtensionFactory;
$this->orderRepository = $orderRepository;
$this->addressInterfaceFactory = $addressInterfaceFactory;
$this->storeManager = $storeManager;
$this->stockByWebsiteIdResolver = $stockByWebsiteIdResolver;
}

/**
* Same as GetInventoryRequestFromOrder, but takes an order instead of an order id
* because in this scenario the order has not been saved yet.
*
* @param OrderInterface $order
* @param array $requestItems
* @return InventoryRequestInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function execute(OrderInterface $order, array $requestItems): InventoryRequestInterface
{
$store = $this->storeManager->getStore($order->getStoreId());
$stock = $this->stockByWebsiteIdResolver->execute((int)$store->getWebsiteId());

$inventoryRequest = $this->inventoryRequestFactory->create([
'stockId' => $stock->getStockId(),
'items' => $requestItems
]);

$address = $this->getAddressFromOrder($order);
if ($address !== null) {
$extensionAttributes = $this->inventoryRequestExtensionFactory->create();
$extensionAttributes->setDestinationAddress($address);
$inventoryRequest->setExtensionAttributes($extensionAttributes);
}

return $inventoryRequest;
}

/**
* Create an address from an order
*
* @param OrderInterface $order
* @return null|AddressInterface
*/
private function getAddressFromOrder(OrderInterface $order): ?AddressInterface
{
/** @var Address $shippingAddress */
$shippingAddress = $order->getShippingAddress();
if ($shippingAddress === null) {
return null;
}

return $this->addressInterfaceFactory->create([
'country' => $shippingAddress->getCountryId(),
'postcode' => $shippingAddress->getPostcode(),
'street' => implode("\n", $shippingAddress->getStreet()),
'region' => $shippingAddress->getRegion() ?? $shippingAddress->getRegionCode() ?? '',
'city' => $shippingAddress->getCity()
]);
}
}
124 changes: 124 additions & 0 deletions Model/GetSourceSelectionResultFromOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Ampersand\DisableStockReservation\Model;

use Ampersand\DisableStockReservation\Model\GetInventoryRequestFromOrder;
use Magento\Framework\App\ObjectManager;
use Magento\InventorySalesApi\Model\GetSkuFromOrderItemInterface;
use Magento\InventorySourceSelectionApi\Api\Data\ItemRequestInterfaceFactory;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\InventorySourceSelectionApi\Api\SourceSelectionServiceInterface;
use Magento\InventorySourceSelectionApi\Api\GetDefaultSourceSelectionAlgorithmCodeInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\Sales\Api\Data\OrderItemInterface;
use Traversable;

class GetSourceSelectionResultFromOrder
{
/**
* @var GetSkuFromOrderItemInterface
*/
private $getSkuFromOrderItem;

/**
* @var ItemRequestInterfaceFactory
*/
private $itemRequestFactory;

/**
* @var GetDefaultSourceSelectionAlgorithmCodeInterface
*/
private $getDefaultSourceSelectionAlgorithmCode;

/**
* @var SourceSelectionServiceInterface
*/
private $sourceSelectionService;

/**
* @var GetInventoryRequestFromOrder
*/
private $getInventoryRequestFromOrder;

/**
* @param GetSkuFromOrderItemInterface $getSkuFromOrderItem
* @param ItemRequestInterfaceFactory $itemRequestFactory
* @param GetDefaultSourceSelectionAlgorithmCodeInterface $getDefaultSourceSelectionAlgorithmCode
* @param SourceSelectionServiceInterface $sourceSelectionService
* @param GetInventoryRequestFromOrder|null $getInventoryRequestFromOrder
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
GetSkuFromOrderItemInterface $getSkuFromOrderItem,
ItemRequestInterfaceFactory $itemRequestFactory,
GetDefaultSourceSelectionAlgorithmCodeInterface $getDefaultSourceSelectionAlgorithmCode,
SourceSelectionServiceInterface $sourceSelectionService,
GetInventoryRequestFromOrder $getInventoryRequestFromOrder = null
) {
$this->itemRequestFactory = $itemRequestFactory;
$this->getDefaultSourceSelectionAlgorithmCode = $getDefaultSourceSelectionAlgorithmCode;
$this->sourceSelectionService = $sourceSelectionService;
$this->getSkuFromOrderItem = $getSkuFromOrderItem;
$this->getInventoryRequestFromOrder = $getInventoryRequestFromOrder ?:
ObjectManager::getInstance()->get(GetInventoryRequestFromOrder::class);
}

/**
* @param OrderInterface $order
* @return SourceSelectionResultInterface
*/
public function execute(OrderInterface $order): SourceSelectionResultInterface
{
/** @var OrderInterface $order */
$inventoryRequest = $this->getInventoryRequestFromOrder->execute(
$order,
$this->getSelectionRequestItems($order->getItems())
);

$selectionAlgorithmCode = $this->getDefaultSourceSelectionAlgorithmCode->execute();
return $this->sourceSelectionService->execute($inventoryRequest, $selectionAlgorithmCode);
}

/**
* Get selection request items
*
* @param OrderItemInterface[]|Traversable $orderItems
* @return array
*/
private function getSelectionRequestItems(iterable $orderItems): array
{
$selectionRequestItems = [];
foreach ($orderItems as $orderItem) {
if ($orderItem->isDummy()) {
continue;
}

$itemSku = $this->getSkuFromOrderItem->execute($orderItem);
$qty = $this->castQty($orderItem, $orderItem->getQtyOrdered());

$selectionRequestItems[] = $this->itemRequestFactory->create([
'sku' => $itemSku,
'qty' => $qty,
]);
}
return $selectionRequestItems;
}

/**
* Cast qty value
*
* @param OrderItemInterface $item
* @param string|int|float $qty
* @return float
*/
private function castQty(OrderItemInterface $item, $qty): float
{
if ($item->getIsQtyDecimal()) {
$qty = (float) $qty;
} else {
$qty = (int) $qty;
}

return $qty > 0 ? $qty : 0;
}
}
41 changes: 19 additions & 22 deletions Observer/CancelOrderItemObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,33 +151,30 @@ public function execute(EventObserver $observer): void

$order = $orderItem->getOrder();

// Purposely check for single source mode first
// If your store's configuration for inventory is not single source, then you'll need something to make source
// calculation on order placement, rather than order shipment
$sourceCode = null;
// Source selection happens by default on shipment, so only shipments contain information about the
// inventory source.
// @TODO add support for multi source stock replenishment on cancellation
if ($this->isSingleSourceMode->execute()) {
$sourceCode = $this->defaultSourceProvider->getCode();
} elseif (!empty($order->getExtensionAttributes()) && !empty($order->getExtensionAttributes()->getSourceCode())) {
$sourceCode = $order->getExtensionAttributes()->getSourceCode();
}

$itemsToDeduct = [];
foreach ($itemsToCancel as $itemToCancel) {
$itemsToDeduct[] = $this->itemToDeductFactory->create([
'sku' => $itemToCancel->getSku(),
'qty' => -$itemToCancel->getQuantity(),
$itemsToDeduct = [];
foreach ($itemsToCancel as $itemToCancel) {
$itemsToDeduct[] = $this->itemToDeductFactory->create([
'sku' => $itemToCancel->getSku(),
'qty' => -$itemToCancel->getQuantity(),
]);
}

$sourceDeductionRequest = $this->sourceDeductionRequestFactory->create([
'sourceCode' => $sourceCode,
'items' => $itemsToDeduct,
'salesChannel' => $salesChannel,
'salesEvent' => $salesEvent
]);
}

$sourceDeductionRequest = $this->sourceDeductionRequestFactory->create([
'sourceCode' => $sourceCode,
'items' => $itemsToDeduct,
'salesChannel' => $salesChannel,
'salesEvent' => $salesEvent
]);

$this->sourceDeductionService->execute($sourceDeductionRequest);
$this->sourceDeductionService->execute($sourceDeductionRequest);

$this->priceIndexer->reindexRow($orderItem->getProductId());
$this->priceIndexer->reindexRow($orderItem->getProductId());
}
}
}
Loading

0 comments on commit 4b4a928

Please sign in to comment.