Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\InventoryDefaultForCountrySourceSelection\Model\Algorithms;

use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\InventoryApi\Api\Data\SourceInterface;
use Magento\InventoryApi\Api\GetSourcesAssignedToStockOrderedByPriorityInterface;
use Magento\InventoryDefaultForCountrySourceSelection\Model\Configuration;
use Magento\InventorySourceSelectionApi\Api\Data\AddressInterface;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\InventorySourceSelectionApi\Api\SourceSelectionServiceInterface;
use Magento\InventorySourceSelectionApi\Model\Algorithms\Result\GetDefaultSortedSourcesResult;
use Magento\InventorySourceSelectionApi\Model\SourceSelectionInterface;

/**
* This shipping algorithm just iterates over all the sources one by one in distance order
*/
class DefaultForCountryAlgorithm implements SourceSelectionInterface
{
/**
* Algorithm code
*/
public const CODE = 'default_for_countries';

/**
* @var GetSourcesAssignedToStockOrderedByPriorityInterface
*/
private $getSourcesAssignedToStockOrderedByPriority;

/**
* @var GetDefaultSortedSourcesResult
*/
private $getDefaultSortedSourcesResult;

/**
* @var Configuration
*/
private $configuration;

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

/**
* DefaultForCountryAlgorithm constructor.
*
* @param GetSourcesAssignedToStockOrderedByPriorityInterface $getSourcesAssignedToStockOrderedByPriority
* @param GetDefaultSortedSourcesResult $getDefaultSortedSourcesResult
* @param Configuration $configuration
* @param SourceSelectionServiceInterface $sourceSelectionService
*/
public function __construct(
GetSourcesAssignedToStockOrderedByPriorityInterface $getSourcesAssignedToStockOrderedByPriority,
GetDefaultSortedSourcesResult $getDefaultSortedSourcesResult,
Configuration $configuration,
SourceSelectionServiceInterface $sourceSelectionService
) {
$this->getSourcesAssignedToStockOrderedByPriority = $getSourcesAssignedToStockOrderedByPriority;
$this->getDefaultSortedSourcesResult = $getDefaultSortedSourcesResult;
$this->configuration = $configuration;
$this->sourceSelectionService = $sourceSelectionService;
}

/**
* @inheritdoc
* @throws LocalizedException
*/
public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface
{
$destinationAddress = $inventoryRequest->getExtensionAttributes()->getDestinationAddress();
if ($destinationAddress === null) {
throw new LocalizedException(__('No destination address was provided in the request'));
}

$stockId = $inventoryRequest->getStockId();
$sortedSources = $this->getEnabledSourcesOrderedByDefaultForCountriesByStockId(
$stockId,
$destinationAddress,
$inventoryRequest
);

return $this->getDefaultSortedSourcesResult->execute($inventoryRequest, $sortedSources);
}

/**
* Get enabled sources ordered by countries and fallback algorithm by $stockId
*
* @param int $stockId
* @param AddressInterface $address
* @param InventoryRequestInterface $inventoryRequest
* @return array
* @throws InputException
* @throws LocalizedException
*/
private function getEnabledSourcesOrderedByDefaultForCountriesByStockId(
int $stockId,
AddressInterface $address,
InventoryRequestInterface $inventoryRequest
): array {
$priorityBySourceCode = $sortSources = $sourcesFromAdditional = [];

$additionalAlgorithmCode = $this->configuration->getAdditionalAlgorithmCode();
if (!empty($additionalAlgorithmCode)) {
$additionalAlgorithmResult = $this->sourceSelectionService->execute(
$inventoryRequest,
$additionalAlgorithmCode
);
$sourceSelectionItemsFromAdditional = $additionalAlgorithmResult->getSourceSelectionItems();
$i = 1;
foreach ($sourceSelectionItemsFromAdditional as $sourceSelectionItem) {
$sourcesFromAdditional[$sourceSelectionItem->getSourceCode()] = $i++;
}
}

// Keep priority order as computational base
$sources = $this->getSourcesAssignedToStockOrderedByPriority->execute($stockId);
$sources = array_filter($sources, function (SourceInterface $source) {
return $source->isEnabled();
});

$isExcludeUnmatchedEnabled = $this->configuration->isExcludeUnmatchedEnabled();
$defaultSortOrder = count($sourcesFromAdditional) + count($sources);
foreach ($sources as $source) {
// Keep default sort order big, so source from default for countries can be pushed to start of array
$sortOrder = $defaultSortOrder;
$defaultForCountries = $source->getExtensionAttributes()->getDefaultForCountries();

$countryMatchToSourceFlag = isset($defaultForCountries)
&& in_array($address->getCountry(), $defaultForCountries);
if ($countryMatchToSourceFlag) {
// push default for country source to start of array
$sortOrder = 0;
}

if ($isExcludeUnmatchedEnabled && !$countryMatchToSourceFlag) {
continue;
}

if (isset($sourcesFromAdditional[$source->getSourceCode()])) {
// increase sort order based on sort order from additional algorithm
$sortOrder += $sourcesFromAdditional[$source->getSourceCode()];
}

$priorityBySourceCode[$source->getSourceCode()] = $sortOrder;
$sortSources[] = $source;
}

// Sort sources by priority
uasort(
$sortSources,
function (SourceInterface $a, SourceInterface $b) use ($priorityBySourceCode) {
$priorityA = $priorityBySourceCode[$a->getSourceCode()];
$priorityB = $priorityBySourceCode[$b->getSourceCode()];

return ($priorityA < $priorityB) ? -1 : 1;
}
);

return $sortSources;
}
}
70 changes: 70 additions & 0 deletions InventoryDefaultForCountrySourceSelection/Model/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\InventoryDefaultForCountrySourceSelection\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;

/**
* Algorithm configuration
*/
class Configuration
{
private const XML_PATH_ADDITIONAL_ALGORITHM =
'cataloginventory/source_selection_default_for_country/additional_algorithm';
private const XML_PATH_EXCLUDE_UNMATCHED =
'cataloginventory/source_selection_default_for_country/exclude_unmatched';

/**
* @var ScopeConfigInterface
*/
private $scopeConfig;

/**
* Configuration constructor.
*
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(
ScopeConfigInterface $scopeConfig
) {
$this->scopeConfig = $scopeConfig;
}

/**
* Get additional algorithm code
*
* @param null|string|bool|int|Store $store
* @return string|null
*/
public function getAdditionalAlgorithmCode($store = null): ?string
{
return $this->scopeConfig->getValue(
self::XML_PATH_ADDITIONAL_ALGORITHM,
ScopeInterface::SCOPE_STORE,
$store
);
}

/**
* Get exclude_unmatched config flag
*
* @param null|string|bool|int|Store $store
* @return bool
*/
public function isExcludeUnmatchedEnabled($store = null): bool
{
return $this->scopeConfig->isSetFlag(
self::XML_PATH_EXCLUDE_UNMATCHED,
ScopeInterface::SCOPE_STORE,
$store
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\InventoryDefaultForCountrySourceSelection\Model\Source;

use Magento\Framework\Api\ExtensionAttributesFactory;
use Magento\Framework\DataObject;
use Magento\InventoryApi\Api\Data\SourceInterface;

/**
* Set store-pickup related source extension attributes
*/
class InitCountriesSelectionExtensionAttributes
{
public const DEFAULT_FOR_COUNTRIES_KEY = 'default_for_countries';
/**
* @var ExtensionAttributesFactory
*/
private $extensionAttributesFactory;

/**
* @param ExtensionAttributesFactory $extensionAttributesFactory
*/
public function __construct(ExtensionAttributesFactory $extensionAttributesFactory)
{
$this->extensionAttributesFactory = $extensionAttributesFactory;
}

/**
* Set store-pickup related source extension attributes.
*
* @param SourceInterface $source
*/
public function execute(SourceInterface $source): void
{
if (!$source instanceof DataObject) {
return;
}
$defaultForCountries = $source->getData(self::DEFAULT_FOR_COUNTRIES_KEY);
$defaultForCountries = (empty($defaultForCountries)) ? [] : explode(',', $defaultForCountries);
$extensionAttributes = $source->getExtensionAttributes();

if ($extensionAttributes === null) {
$extensionAttributes = $this->extensionAttributesFactory->create(SourceInterface::class);
/** @noinspection PhpParamsInspection */
$source->setExtensionAttributes($extensionAttributes);
}
if (!empty($defaultForCountries)) {
$extensionAttributes->setDefaultForCountries($defaultForCountries);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\InventoryDefaultForCountrySourceSelection\Plugin\InventoryApi\SourceRepository;

use Magento\InventoryApi\Api\Data\SourceSearchResultsInterface;
use Magento\InventoryApi\Api\SourceRepositoryInterface;
use Magento\InventoryDefaultForCountrySourceSelection\Model\Source\InitCountriesSelectionExtensionAttributes;

/**
* Populate countries selection extension attribute when loading a list of sources.
*/
class LoadCountriesSelectionGetListPlugin
{
/**
* @var InitCountriesSelectionExtensionAttributes
*/
private $setExtensionAttributes;

/**
* @param InitCountriesSelectionExtensionAttributes $setExtensionAttributes
*/
public function __construct(
InitCountriesSelectionExtensionAttributes $setExtensionAttributes
) {
$this->setExtensionAttributes = $setExtensionAttributes;
}

/**
* Add extension attribute object to source items
*
* @param SourceRepositoryInterface $subject
* @param SourceSearchResultsInterface $sourceSearchResults
*
* @return SourceSearchResultsInterface
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterGetList(
SourceRepositoryInterface $subject,
SourceSearchResultsInterface $sourceSearchResults
): SourceSearchResultsInterface {
$items = $sourceSearchResults->getItems();
array_walk(
$items,
[$this->setExtensionAttributes, 'execute']
);

return $sourceSearchResults;
}
}
Loading