From 33033bc5b196edd571ff1f475b919270d9f0d6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 7 Oct 2023 23:46:31 +0200 Subject: [PATCH 1/8] Added the MouserProvider by @pdo59 --- .env | 14 + config/services.yaml | 7 + .../Providers/MouserProvider.php | 333 ++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 src/Services/InfoProviderSystem/Providers/MouserProvider.php diff --git a/.env b/.env index 9947522e..7c09436b 100644 --- a/.env +++ b/.env @@ -142,6 +142,20 @@ PROVIDER_OCTOPART_SEARCH_LIMIT=10 # Set to false to include non authorized offers in the results PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1 +# Mouser Provider API V2: +# You can get your API key from https://www.mouser.it/api-hub/ +PROVIDER_MOUSER_KEY= +# If not provided, the default is false. Used when searching for keywords in the language specified when you signed up for Search API. +# Can use string representation: true. +PROVIDER_MOUSER_LANGUAGE='true' +#searchOptions string +#Optional. If not provided, the default is None. +#Refers to options supported by the search engine. +#Only one value at a time is supported. +#Available options: None | Rohs | InStock | RohsAndInStock - can use string representations or integer IDs: 1[None] | 2[Rohs] | 4[InStock] | 8[RohsAndInStock]. +PROVIDER_MOUSER_OPTION='1' +# The number of results to get from Mouser while searching (please note that this value is max 50) +PROVIDER_MOUSER_SEARCH_LIMIT=50 ################################################################################### # SAML Single sign on-settings diff --git a/config/services.yaml b/config/services.yaml index 8e6ee8af..1754b266 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -277,6 +277,13 @@ services: $search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%' $onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%' + App\Services\InfoProviderSystem\Providers\MouserProvider: + arguments: + $api_key: '%env(string:PROVIDER_MOUSER_KEY)%' + $language: '%env(string:PROVIDER_MOUSER_LANGUAGE)%' + $options: '%env(string:PROVIDER_MOUSER_OPTION)%' + $search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%' + #################################################################################################################### # API system #################################################################################################################### diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php new file mode 100644 index 00000000..7915aaa5 --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -0,0 +1,333 @@ +. + */ + +/* +* This file provide an interface with the Mouser API V2 (also compatible with the V1) +* +* Copyright (C) 2023 Pasquale D'Orsi (https://github.com/pdo59) +* +* TODO: Obtain an API keys with an US Mouser user (currency $) and test the result of prices +* +*/ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Entity\Parts\ManufacturingStatus; +use App\Form\InfoProviderSystem\ProviderSelectType; +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use Symfony\Contracts\HttpClient\HttpClientInterface; + + +class MouserProvider implements InfoProviderInterface +{ + + private const ENDPOINT_URL = 'https://api.mouser.com/api/v2/search'; + + private const NUMBER_OF_RESULTS = 50; //MAX 50 + + + public const DISTRIBUTOR_NAME = 'Mouser'; + + public function __construct(private readonly HttpClientInterface $mouserClient, + private readonly string $api_key, + private readonly string $language, + private readonly string $options, + private readonly int $search_limit) + { + + } + + public function getProviderInfo(): array + { + return [ + 'name' => 'Mouser', + 'description' => 'This provider uses the Mouser API to search for parts.', + 'url' => 'https://www.mouser.com/', + 'disabled_help' => 'Configure the API key in the PROVIDER_MOUSER_KEY environment variable to enable.' + ]; + } + + public function getProviderKey(): string + { + return 'Mouser'; + } + + public function isActive(): bool + { + return !empty($this->api_key); + } + + /** + * @param string $term + * @return PartDetailDTO[] + */ + private function queryByTerm(string $term): array + { + /* + SearchByKeywordRequest description: + Search parts by keyword and return a maximum of 50 parts. + + keyword* string + Used for keyword part search. + + records integer($int32) + Used to specify how many records the method should return. + + startingRecord integer($int32) + Indicates where in the total recordset the return set should begin. + From the startingRecord, the number of records specified will be returned up to the end of the recordset. + This is useful for paging through the complete recordset of parts matching keyword. + + searchOptions string + Optional. + If not provided, the default is None. + Refers to options supported by the search engine. + Only one value at a time is supported. + Available options: None | Rohs | InStock | RohsAndInStock - can use string representations or integer IDs: 1[None] | 2[Rohs] | 4[InStock] | 8[RohsAndInStock]. + + searchWithYourSignUpLanguage string + Optional. + If not provided, the default is false. + Used when searching for keywords in the language specified when you signed up for Search API. + Can use string representation: true. + { + "SearchByKeywordRequest": { + "keyword": "BC557", + "records": 0, + "startingRecord": 0, + "searchOptions": "", + "searchWithYourSignUpLanguage": "" + } + } + */ + + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/keyword?apiKey=" . $this->api_key , [ + 'json' => [ + 'SearchByKeywordRequest' => [ + 'keyword' => $term, + 'records' => $this->search_limit, //self::NUMBER_OF_RESULTS, + 'startingRecord' => 0, + 'searchOptions' => $this->options, + 'searchWithYourSignUpLanguage' => $this->language, + ] + ], + ]); + + $arr = $response->toArray(); + if (isset($arr['SearchResults'])) { + $products = $arr['SearchResults']['Parts'] ?? []; + } else { + throw new \RuntimeException('Unknown response format'); + } + $result = []; + foreach ($products as $product) { + $result[] = new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $product['MouserPartNumber'], + name: $product['ManufacturerPartNumber'], + description: $product['Description'], + manufacturer: $product['Manufacturer'], + mpn: $product['ManufacturerPartNumber'], + preview_image_url: $product['ImagePath'], + category: $product['Category'], + provider_url: $product['ProductDetailUrl'], + manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['LifecycleStatus'] ?? null), + datasheets: $this->parseDataSheets($product['DataSheetUrl'], $product['MouserPartNumber'] ?? null), + vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], $product['ProductDetailUrl']), + ); + } + return $result; + + } + + /** + * @param string $parte + * @return PartDetailDTO[] + */ + private function queryPartNumber(string $parte): array + { + /* + SearchByPartRequest description: + Search parts by part number and return a maximum of 50 parts. + + mouserPartNumber string + Used to search parts by the specific Mouser part number with a maximum input of 10 part numbers, separated by a pipe symbol for the search. + Each part number must be a minimum of 3 characters and a maximum of 40 characters. For example: 494-JANTX2N2222A|610-2N2222-TL|637-2N2222A + + partSearchOptions string + Optional. + If not provided, the default is None. Refers to options supported by the search engine. Only one value at a time is supported. + The following values are valid: None | Exact - can use string representations or integer IDs: 1[None] | 2[Exact] + + { + "SearchByPartRequest": { + "mouserPartNumber": "string", + "partSearchOptions": "string" + } + } + */ + + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/partnumber?apiKey=" . $this->api_key , [ + 'json' => [ + 'SearchByPartRequest' => [ + 'mouserPartNumber' => $parte, + 'partSearchOptions' => 2 + ] + ], + ]); + $arr = $response->toArray(); + if (isset($arr['SearchResults'])) { + $products = $arr['SearchResults']['Parts'] ?? []; + } else { + throw new \RuntimeException('Unknown response format'); + } + + $result = []; + foreach ($products as $product) { + $result[] = new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $product['MouserPartNumber'], + name: $product['ManufacturerPartNumber'], + description: $product['Description'], + manufacturer: $product['Manufacturer'], + mpn: $product['ManufacturerPartNumber'], + preview_image_url: $product['ImagePath'], + category: $product['Category'], + provider_url: $product['ProductDetailUrl'], + manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['LifecycleStatus'] ?? null), + datasheets: $this->parseDataSheets($product['DataSheetUrl'], $product['MouserPartNumber'] ?? null), + vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], $product['ProductDetailUrl']), + ); + } + return $result; + + } + + + private function generateProductURL($sku): string + { + return 'https://' . $this->store_id . '/' . $sku; + } + + + private function parseDataSheets(string $sheetUrl, string $sheetName): ?array + { + if ($sheetUrl === null) { + return null; + } + $result = []; + $result[] = new FileDTO(url: $sheetUrl, name: $sheetName); + return $result; + } + + /* + * Mouser API price is a string in the form "n[.,]nnn[.,] currency" + * then this convert it to a number + */ + private function floatvalue($val){ + $val = str_replace(",",".",$val); + $val = preg_replace('/\.(?=.*\.)/', '', $val); + return floatval($val); + } + + /** + * Converts the pricing (StandardPricing field) from the Mouser API to an array of PurchaseInfoDTOs + * @param array $price_breaks + * @param string $order_number + * @param string $product_url + * @return PurchaseInfoDTO[] + */ + private function pricingToDTOs(array $price_breaks, string $order_number, string $product_url): array + { + $prices = []; + + foreach ($price_breaks as $price_break) { + $number = $this->floatvalue($price_break['Price']); + $prices[] = new PriceDTO(minimum_discount_amount: $price_break['Quantity'], price: (string)$number, currency_iso_code: $price_break['Currency']); + $number = 0; + } + + return [ + new PurchaseInfoDTO(distributor_name: self::DISTRIBUTOR_NAME, order_number: $order_number, prices: $prices, product_url: $product_url) + ]; + } + + + + /* Converts the product status from the MOUSER API to the manufacturing status used in Part-DB: + Factory Special Order - Ordine speciale in fabbrica + Not Recommended for New Designs - Non raccomandato per nuovi progetti + New Product - Nuovo prodotto + End of Life - Fine vita + -vuoto- - Attivo + + TODO: Probably need to review the values of field Lifecyclestatus + */ + private function releaseStatusCodeToManufacturingStatus(?string $productStatus): ?ManufacturingStatus + { + return match ($productStatus) { + null => null, + "New Product" => ManufacturingStatus::ANNOUNCED, + "Not Recommended for New Designs" => ManufacturingStatus::NRFND, + "Factory Special Order" => ManufacturingStatus::DISCONTINUED, + "End of Life" => ManufacturingStatus::EOL, + "Obsolete" => ManufacturingStatus::DISCONTINUED, + default => ManufacturingStatus::ACTIVE, + }; + } + + public function searchByKeyword(string $keyword): array + { + return $this->queryByTerm($keyword); + } + + public function getDetails(string $id): PartDetailDTO + { + $tmp = $this->queryPartNumber($id); + + if (count($tmp) === 0) { + throw new \RuntimeException('No part found with ID ' . $id); + } + + if (count($tmp) > 1) { + throw new \RuntimeException('Multiple parts found with ID ' . $id); + } + + return $tmp[0]; + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } +} \ No newline at end of file From a0b31cfd7e90964f84ea85cf6f9a4cec3d88e907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 7 Oct 2023 23:49:04 +0200 Subject: [PATCH 2/8] Made mouser provider key lowercase to be consistent with other providers --- src/Services/InfoProviderSystem/Providers/MouserProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 7915aaa5..6d649647 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -74,7 +74,7 @@ class MouserProvider implements InfoProviderInterface public function getProviderKey(): string { - return 'Mouser'; + return 'mouser'; } public function isActive(): bool From d7bc74fb2b479a7d20a2b0659997bc18167bbf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:00:10 +0200 Subject: [PATCH 3/8] Slightly restructured mouserprovider to remove redundant code --- .../Providers/MouserProvider.php | 115 ++++++------------ 1 file changed, 39 insertions(+), 76 deletions(-) diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 6d649647..ee267aaa 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -41,6 +41,7 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; class MouserProvider implements InfoProviderInterface @@ -82,11 +83,7 @@ class MouserProvider implements InfoProviderInterface return !empty($this->api_key); } - /** - * @param string $term - * @return PartDetailDTO[] - */ - private function queryByTerm(string $term): array + public function searchByKeyword(string $keyword): array { /* SearchByKeywordRequest description: @@ -129,7 +126,7 @@ class MouserProvider implements InfoProviderInterface $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/keyword?apiKey=" . $this->api_key , [ 'json' => [ 'SearchByKeywordRequest' => [ - 'keyword' => $term, + 'keyword' => $keyword, 'records' => $this->search_limit, //self::NUMBER_OF_RESULTS, 'startingRecord' => 0, 'searchOptions' => $this->options, @@ -138,38 +135,10 @@ class MouserProvider implements InfoProviderInterface ], ]); - $arr = $response->toArray(); - if (isset($arr['SearchResults'])) { - $products = $arr['SearchResults']['Parts'] ?? []; - } else { - throw new \RuntimeException('Unknown response format'); - } - $result = []; - foreach ($products as $product) { - $result[] = new PartDetailDTO( - provider_key: $this->getProviderKey(), - provider_id: $product['MouserPartNumber'], - name: $product['ManufacturerPartNumber'], - description: $product['Description'], - manufacturer: $product['Manufacturer'], - mpn: $product['ManufacturerPartNumber'], - preview_image_url: $product['ImagePath'], - category: $product['Category'], - provider_url: $product['ProductDetailUrl'], - manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['LifecycleStatus'] ?? null), - datasheets: $this->parseDataSheets($product['DataSheetUrl'], $product['MouserPartNumber'] ?? null), - vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], $product['ProductDetailUrl']), - ); - } - return $result; - + return $this->responseToDTOArray($response); } - /** - * @param string $parte - * @return PartDetailDTO[] - */ - private function queryPartNumber(string $parte): array + public function getDetails(string $id): PartDetailDTO { /* SearchByPartRequest description: @@ -195,18 +164,49 @@ class MouserProvider implements InfoProviderInterface $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/partnumber?apiKey=" . $this->api_key , [ 'json' => [ 'SearchByPartRequest' => [ - 'mouserPartNumber' => $parte, + 'mouserPartNumber' => $id, 'partSearchOptions' => 2 ] ], ]); + $tmp = $this->responseToDTOArray($response); + + //Ensure that we have exactly one result + if (count($tmp) === 0) { + throw new \RuntimeException('No part found with ID ' . $id); + } + + if (count($tmp) > 1) { + throw new \RuntimeException('Multiple parts found with ID ' . $id); + } + + return $tmp[0]; + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } + + + /** + * @param ResponseInterface $response + * @return PartDetailDTO[] + */ + private function responseToDTOArray(ResponseInterface $response): array + { $arr = $response->toArray(); + if (isset($arr['SearchResults'])) { $products = $arr['SearchResults']['Parts'] ?? []; } else { throw new \RuntimeException('Unknown response format'); } - $result = []; foreach ($products as $product) { $result[] = new PartDetailDTO( @@ -225,13 +225,6 @@ class MouserProvider implements InfoProviderInterface ); } return $result; - - } - - - private function generateProductURL($sku): string - { - return 'https://' . $this->store_id . '/' . $sku; } @@ -252,7 +245,7 @@ class MouserProvider implements InfoProviderInterface private function floatvalue($val){ $val = str_replace(",",".",$val); $val = preg_replace('/\.(?=.*\.)/', '', $val); - return floatval($val); + return (float)$val; } /** @@ -300,34 +293,4 @@ class MouserProvider implements InfoProviderInterface default => ManufacturingStatus::ACTIVE, }; } - - public function searchByKeyword(string $keyword): array - { - return $this->queryByTerm($keyword); - } - - public function getDetails(string $id): PartDetailDTO - { - $tmp = $this->queryPartNumber($id); - - if (count($tmp) === 0) { - throw new \RuntimeException('No part found with ID ' . $id); - } - - if (count($tmp) > 1) { - throw new \RuntimeException('Multiple parts found with ID ' . $id); - } - - return $tmp[0]; - } - - public function getCapabilities(): array - { - return [ - ProviderCapabilities::BASIC, - ProviderCapabilities::PICTURE, - ProviderCapabilities::DATASHEET, - ProviderCapabilities::PRICE, - ]; - } } \ No newline at end of file From 4f0730b6f9eedc7cccf1ecfd97c6f1585ace7671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:15:57 +0200 Subject: [PATCH 4/8] Properly formatted MouserProvider and fixed some type issues --- .../Providers/MouserProvider.php | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index ee267aaa..3b8c9345 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -49,18 +49,15 @@ class MouserProvider implements InfoProviderInterface private const ENDPOINT_URL = 'https://api.mouser.com/api/v2/search'; - private const NUMBER_OF_RESULTS = 50; //MAX 50 - - public const DISTRIBUTOR_NAME = 'Mouser'; - public function __construct(private readonly HttpClientInterface $mouserClient, + public function __construct( + private readonly HttpClientInterface $mouserClient, private readonly string $api_key, private readonly string $language, private readonly string $options, - private readonly int $search_limit) - { - + private readonly int $search_limit + ) { } public function getProviderInfo(): array @@ -123,7 +120,7 @@ class MouserProvider implements InfoProviderInterface } */ - $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/keyword?apiKey=" . $this->api_key , [ + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/keyword?apiKey=".$this->api_key, [ 'json' => [ 'SearchByKeywordRequest' => [ 'keyword' => $keyword, @@ -161,7 +158,7 @@ class MouserProvider implements InfoProviderInterface } */ - $response = $this->mouserClient->request('POST', self::ENDPOINT_URL . "/partnumber?apiKey=" . $this->api_key , [ + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/partnumber?apiKey=".$this->api_key, [ 'json' => [ 'SearchByPartRequest' => [ 'mouserPartNumber' => $id, @@ -173,11 +170,11 @@ class MouserProvider implements InfoProviderInterface //Ensure that we have exactly one result if (count($tmp) === 0) { - throw new \RuntimeException('No part found with ID ' . $id); + throw new \RuntimeException('No part found with ID '.$id); } if (count($tmp) > 1) { - throw new \RuntimeException('Multiple parts found with ID ' . $id); + throw new \RuntimeException('Multiple parts found with ID '.$id); } return $tmp[0]; @@ -195,7 +192,7 @@ class MouserProvider implements InfoProviderInterface /** - * @param ResponseInterface $response + * @param ResponseInterface $response * @return PartDetailDTO[] */ private function responseToDTOArray(ResponseInterface $response): array @@ -214,23 +211,25 @@ class MouserProvider implements InfoProviderInterface provider_id: $product['MouserPartNumber'], name: $product['ManufacturerPartNumber'], description: $product['Description'], + category: $product['Category'], manufacturer: $product['Manufacturer'], mpn: $product['ManufacturerPartNumber'], preview_image_url: $product['ImagePath'], - category: $product['Category'], - provider_url: $product['ProductDetailUrl'], manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['LifecycleStatus'] ?? null), - datasheets: $this->parseDataSheets($product['DataSheetUrl'], $product['MouserPartNumber'] ?? null), - vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], $product['ProductDetailUrl']), + provider_url: $product['ProductDetailUrl'], + datasheets: $this->parseDataSheets($product['DataSheetUrl'] ?? null, + $product['MouserPartNumber'] ?? null), + vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], + $product['ProductDetailUrl']), ); } return $result; } - private function parseDataSheets(string $sheetUrl, string $sheetName): ?array + private function parseDataSheets(?string $sheetUrl, ?string $sheetName): ?array { - if ($sheetUrl === null) { + if (empty($sheetUrl)) { return null; } $result = []; @@ -242,8 +241,9 @@ class MouserProvider implements InfoProviderInterface * Mouser API price is a string in the form "n[.,]nnn[.,] currency" * then this convert it to a number */ - private function floatvalue($val){ - $val = str_replace(",",".",$val); + private function priceStrToFloat($val): float + { + $val = str_replace(",", ".", $val); $val = preg_replace('/\.(?=.*\.)/', '', $val); return (float)$val; } @@ -260,18 +260,21 @@ class MouserProvider implements InfoProviderInterface $prices = []; foreach ($price_breaks as $price_break) { - $number = $this->floatvalue($price_break['Price']); - $prices[] = new PriceDTO(minimum_discount_amount: $price_break['Quantity'], price: (string)$number, currency_iso_code: $price_break['Currency']); - $number = 0; + $number = $this->priceStrToFloat($price_break['Price']); + $prices[] = new PriceDTO( + minimum_discount_amount: $price_break['Quantity'], + price: (string)$number, + currency_iso_code: $price_break['Currency'] + ); } return [ - new PurchaseInfoDTO(distributor_name: self::DISTRIBUTOR_NAME, order_number: $order_number, prices: $prices, product_url: $product_url) + new PurchaseInfoDTO(distributor_name: self::DISTRIBUTOR_NAME, order_number: $order_number, prices: $prices, + product_url: $product_url) ]; } - /* Converts the product status from the MOUSER API to the manufacturing status used in Part-DB: Factory Special Order - Ordine speciale in fabbrica Not Recommended for New Designs - Non raccomandato per nuovi progetti From 7cd2662c77eab8c25d223c64083020878ea26174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:18:25 +0200 Subject: [PATCH 5/8] Moved API key to query options of HTTPClient in MouserProvider --- .../InfoProviderSystem/Providers/MouserProvider.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 3b8c9345..22da8f3e 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -120,7 +120,10 @@ class MouserProvider implements InfoProviderInterface } */ - $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/keyword?apiKey=".$this->api_key, [ + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/keyword", [ + 'query' => [ + 'apiKey' => $this->api_key, + ], 'json' => [ 'SearchByKeywordRequest' => [ 'keyword' => $keyword, @@ -158,7 +161,10 @@ class MouserProvider implements InfoProviderInterface } */ - $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/partnumber?apiKey=".$this->api_key, [ + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/partnumber", [ + 'query' => [ + 'apiKey' => $this->api_key, + ], 'json' => [ 'SearchByPartRequest' => [ 'mouserPartNumber' => $id, From 18ae32f15ad7b8308ee7433f5c702d70636833a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:33:48 +0200 Subject: [PATCH 6/8] Renamed some env to match their purpose better --- .env | 15 ++++++--------- config/services.yaml | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.env b/.env index 7c09436b..0b834c2e 100644 --- a/.env +++ b/.env @@ -145,17 +145,14 @@ PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1 # Mouser Provider API V2: # You can get your API key from https://www.mouser.it/api-hub/ PROVIDER_MOUSER_KEY= -# If not provided, the default is false. Used when searching for keywords in the language specified when you signed up for Search API. -# Can use string representation: true. -PROVIDER_MOUSER_LANGUAGE='true' -#searchOptions string -#Optional. If not provided, the default is None. -#Refers to options supported by the search engine. -#Only one value at a time is supported. -#Available options: None | Rohs | InStock | RohsAndInStock - can use string representations or integer IDs: 1[None] | 2[Rohs] | 4[InStock] | 8[RohsAndInStock]. -PROVIDER_MOUSER_OPTION='1' +# Filter search results by RoHS compliance and stock availability: +# Available options: None | Rohs | InStock | RohsAndInStock +PROVIDER_MOUSER_SEARCH_OPTION='None' # The number of results to get from Mouser while searching (please note that this value is max 50) PROVIDER_MOUSER_SEARCH_LIMIT=50 +# It is recommended to leave this set to 'true'. The option is not really good doumented by Mouser: +# Used when searching for keywords in the language specified when you signed up for Search API. +PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true' ################################################################################### # SAML Single sign on-settings diff --git a/config/services.yaml b/config/services.yaml index 1754b266..b99bb72a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -280,8 +280,8 @@ services: App\Services\InfoProviderSystem\Providers\MouserProvider: arguments: $api_key: '%env(string:PROVIDER_MOUSER_KEY)%' - $language: '%env(string:PROVIDER_MOUSER_LANGUAGE)%' - $options: '%env(string:PROVIDER_MOUSER_OPTION)%' + $language: '%env(string:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE)%' + $options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%' $search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%' #################################################################################################################### From 9d9287cefb89a24a90a9b5f3bbe0c1a94270465a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:39:08 +0200 Subject: [PATCH 7/8] Added documentation for MouserAPI --- docs/usage/information_provider_system.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index 49ff6ebe..ea7a1fda 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -126,6 +126,18 @@ Following env configuration options are available: * `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory) * `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and country of prices (optional, default: `de.farnell.com`, see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for availailable values) +### Mouser +The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and getting shopping information from [Mouser](https://www.mouser.com/). +You have to create an account at Mouser and register for an API key for the Search API on the [Mouser API page](https://www.mouser.de/api-home/). +You will receive an API token, which you have to enter in the Part-DB env configuration (see below): +At the registration you choose a country, language and currency in which you want to get the results. + +Following env configuration options are available: +* `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory) +* `PROVIDER_MOUSER_SEARCH_LIMIT`: The maximum number of results to return per search (maximum 50) +* `PROVIDER_MOUSER_SEARCH_OPTION`: You can choose an option here to restrict the search results to RoHs compliant and available parts. Possible values are `None`, `Rohs`, `InStock`, `RohsAndInStock`. +* `PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE`: A bit of an obscure option. The original description of Mouser is: Used when searching for keywords in the language specified when you signed up for Search API. + ### Custom provider To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long as it is a valid Symfony service, it will be automatically loaded and can be used. From 4b5f9648b14929ecee2b76329d383f0d670a4eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 8 Oct 2023 00:40:03 +0200 Subject: [PATCH 8/8] Allow to passthrought the mouser provider env using docker --- .docker/symfony.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/.docker/symfony.conf b/.docker/symfony.conf index 1527286c..f5847434 100644 --- a/.docker/symfony.conf +++ b/.docker/symfony.conf @@ -41,6 +41,7 @@ PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS + PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE # For most configuration files from conf-available/, which are # enabled or disabled at a global level, it is possible to