diff --git a/InventoryDefaultForCountrySourceSelection/Model/Algorithms/DefaultForCountryAlgorithm.php b/InventoryDefaultForCountrySourceSelection/Model/Algorithms/DefaultForCountryAlgorithm.php
new file mode 100644
index 000000000000..7eb0f653c391
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Model/Algorithms/DefaultForCountryAlgorithm.php
@@ -0,0 +1,169 @@
+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;
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/Model/Configuration.php b/InventoryDefaultForCountrySourceSelection/Model/Configuration.php
new file mode 100644
index 000000000000..47fbc1521424
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Model/Configuration.php
@@ -0,0 +1,70 @@
+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
+ );
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/Model/Source/InitCountriesSelectionExtensionAttributes.php b/InventoryDefaultForCountrySourceSelection/Model/Source/InitCountriesSelectionExtensionAttributes.php
new file mode 100644
index 000000000000..f29f4cf1895a
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Model/Source/InitCountriesSelectionExtensionAttributes.php
@@ -0,0 +1,57 @@
+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);
+ }
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionGetListPlugin.php b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionGetListPlugin.php
new file mode 100644
index 000000000000..a4b12170f059
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionGetListPlugin.php
@@ -0,0 +1,54 @@
+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;
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionOnGetPlugin.php b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionOnGetPlugin.php
new file mode 100644
index 000000000000..0151b8f3f225
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/LoadCountriesSelectionOnGetPlugin.php
@@ -0,0 +1,50 @@
+setExtensionAttributes = $setExtensionAttributes;
+ }
+
+ /**
+ * Add extension attribute object to source
+ *
+ * @param SourceRepositoryInterface $subject
+ * @param SourceInterface $source
+ *
+ * @return SourceInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGet(
+ SourceRepositoryInterface $subject,
+ SourceInterface $source
+ ): SourceInterface {
+ $this->setExtensionAttributes->execute($source);
+
+ return $source;
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/SaveCountriesSelectionPlugin.php b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/SaveCountriesSelectionPlugin.php
new file mode 100644
index 000000000000..feeb27f18dcf
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Plugin/InventoryApi/SourceRepository/SaveCountriesSelectionPlugin.php
@@ -0,0 +1,50 @@
+getExtensionAttributes();
+
+ if ($extensionAttributes !== null) {
+ $defaultForCountries = $extensionAttributes->getDefaultForCountries();
+ if (!empty($defaultForCountries)) {
+ $source->setData(
+ InitCountriesSelectionExtensionAttributes::DEFAULT_FOR_COUNTRIES_KEY,
+ implode(',', $defaultForCountries)
+ );
+ }
+ }
+
+ return [$source];
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/README.md b/InventoryDefaultForCountrySourceSelection/README.md
new file mode 100644
index 000000000000..b5e224537282
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/README.md
@@ -0,0 +1,3 @@
+# InventoryDefaultForCountrySourceSelection module
+
+The `InventoryDefaultForCountrySourceSelection` module implements logic for default for countries source selection. Example, source based in Japan may be preferred by merchant for shipping even if another source based in China is a closer to customer.
diff --git a/InventoryDefaultForCountrySourceSelection/Test/Api/DefaultForCountrySSATest.php b/InventoryDefaultForCountrySourceSelection/Test/Api/DefaultForCountrySSATest.php
new file mode 100644
index 000000000000..627994335056
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/Test/Api/DefaultForCountrySSATest.php
@@ -0,0 +1,212 @@
+getCountriesToTestData();
+ foreach ($countriesToTest as $countryCode => $sources) {
+ foreach ($sources as $sourceCode) {
+ $source = $this->sourceRepository->get($sourceCode);
+ $source->setData(
+ InitCountriesSelectionExtensionAttributes::DEFAULT_FOR_COUNTRIES_KEY,
+ $countryCode
+ );
+ $this->sourceRepository->save($source);
+ }
+ }
+
+ $expectedListOrder = [
+ 'DE' => ['eu-2', 'eu-1'],
+ 'FR' => ['eu-1', 'eu-2'],
+ ];
+ $wrongListOrder = [
+ 'DE' => ['eu-1', 'eu-2'],
+ 'FR' => ['eu-2', 'eu-1'],
+ ];
+ $this->assertSourceSelectionOrderLists($expectedListOrder, $wrongListOrder);
+ }
+
+ /**
+ * Test default for countries SSA with enabled exclude_unmatched config
+ *
+ * @magentoConfigFixture cataloginventory/source_selection_default_for_country/additional_algorithm priority
+ * @magentoConfigFixture cataloginventory/source_selection_default_for_country/exclude_unmatched 1
+ * @magentoApiDataFixture ../../../../vendor/magento/module-inventory-api/Test/_files/products.php
+ * @magentoApiDataFixture ../../../../vendor/magento/module-inventory-api/Test/_files/sources.php
+ * @magentoApiDataFixture ../../../../vendor/magento/module-inventory-api/Test/_files/stocks.php
+ * @magentoApiDataFixture ../../../../vendor/magento/module-inventory-api/Test/_files/stock_source_links.php
+ * @magentoApiDataFixture ../../../../vendor/magento/module-inventory-api/Test/_files/source_items.php
+ */
+ public function testDefaultForCountrySSAWithExcludeUnmatched()
+ {
+ $countriesToTest = $this->getCountriesToTestData();
+ foreach ($countriesToTest as $countryCode => $sources) {
+ foreach ($sources as $sourceCode) {
+ $source = $this->sourceRepository->get($sourceCode);
+ $source->setData(
+ InitCountriesSelectionExtensionAttributes::DEFAULT_FOR_COUNTRIES_KEY,
+ $countryCode
+ );
+ $this->sourceRepository->save($source);
+ }
+ }
+
+ $expectedListOrder = [
+ 'DE' => ['eu-2'],
+ 'FR' => ['eu-1'],
+ ];
+ $wrongListOrder = [
+ 'DE' => ['eu-1', 'eu-2'],
+ 'FR' => ['eu-2', 'eu-1'],
+ ];
+ $this->assertSourceSelectionOrderLists($expectedListOrder, $wrongListOrder);
+ }
+
+ /**
+ * @inheridoc
+ */
+ protected function setUp(): void
+ {
+ parent::setUp();
+ $this->defaultAlgorithmCode = Bootstrap::getObjectManager()->get(
+ GetDefaultSourceSelectionAlgorithmCodeInterface::class
+ );
+ $this->sourceRepository = Bootstrap::getObjectManager()->get(
+ SourceRepositoryInterface::class
+ );
+ $this->sourceFactory = Bootstrap::getObjectManager()->get(
+ SourceInterfaceFactory::class
+ );
+ }
+
+ /**
+ * Call webapi service and assert order list
+ *
+ * @param array $expectedListOrder
+ * @param array $wrongListOrder
+ * @return void
+ */
+ private function assertSourceSelectionOrderLists(array $expectedListOrder, array $wrongListOrder): void
+ {
+ $countriesToTest = $this->getCountriesToTestData();
+ $inventoryRequest = [
+ 'stockId' => 10,
+ 'items' => [
+ [
+ 'sku' => 'SKU-1',
+ 'qty' => 1,
+ ],
+ ],
+ 'extension_attributes' => [
+ 'destination_address' => [
+ 'country' => 'DE',
+ 'postcode' => '45000',
+ 'street' => 'test street',
+ 'region' => 'Region',
+ 'city' => 'City',
+ ],
+ ],
+ ];
+
+ $algorithmCode = DefaultForCountryAlgorithm::CODE;
+ $requestData = [
+ 'inventoryRequest' => $inventoryRequest,
+ 'algorithmCode' => $algorithmCode,
+ ];
+
+ $serviceInfo = [
+ 'rest' => [
+ 'resourcePath' => self::RESOURCE_PATH,
+ 'httpMethod' => Request::HTTP_METHOD_POST,
+ ],
+ 'soap' => [
+ 'service' => self::SERVICE_NAME,
+ 'operation' => self::SERVICE_NAME . 'Execute',
+ ],
+ ];
+
+ foreach ($countriesToTest as $countryCode => $sources) {
+ $requestData['inventoryRequest']['extension_attributes']['destination_address']['country'] =
+ $countryCode;
+ $response = $this->_webApiCall($serviceInfo, $requestData);
+
+ $this->assertIsArray($response);
+ $this->assertNotEmpty($response);
+ $this->assertArrayHasKey('source_selection_items', $response);
+ $listByPriority = [];
+ foreach ($response['source_selection_items'] as $sourceSelectionItem) {
+ $this->assertIsArray($sourceSelectionItem);
+ $this->assertArrayHasKey('source_code', $sourceSelectionItem);
+ $listByPriority[] = $sourceSelectionItem['source_code'];
+ }
+ $this->assertEquals($expectedListOrder[$countryCode], $listByPriority);
+ $this->assertNotEquals($wrongListOrder[$countryCode], $listByPriority);
+ }
+ }
+
+ /**
+ * Countries to test data array
+ *
+ * @return array
+ */
+ private function getCountriesToTestData(): array
+ {
+ return [
+ 'DE' => ['eu-2'],
+ 'FR' => ['eu-1'],
+ ];
+ }
+}
diff --git a/InventoryDefaultForCountrySourceSelection/composer.json b/InventoryDefaultForCountrySourceSelection/composer.json
new file mode 100644
index 000000000000..d19efed422af
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "magento/module-inventory-default-for-country-source-selection",
+ "description": "N/A",
+ "require": {
+ "php": "~7.3.0||~7.4.0",
+ "magento/framework": "*",
+ "magento/module-store": "*",
+ "magento/module-inventory": "*",
+ "magento/module-inventory-api": "*",
+ "magento/module-inventory-source-selection-api": "*"
+ },
+ "type": "magento2-module",
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\InventoryDefaultForCountrySourceSelection\\": ""
+ }
+ },
+ "version": "1.2.0"
+}
diff --git a/InventoryDefaultForCountrySourceSelection/etc/config.xml b/InventoryDefaultForCountrySourceSelection/etc/config.xml
new file mode 100644
index 000000000000..a98e71e019d1
--- /dev/null
+++ b/InventoryDefaultForCountrySourceSelection/etc/config.xml
@@ -0,0 +1,18 @@
+
+
+
+
+