diff --git a/src/Services/InfoProviderSystem/Providers/ConradProvider.php b/src/Services/InfoProviderSystem/Providers/ConradProvider.php index b72be0bd..7212444b 100644 --- a/src/Services/InfoProviderSystem/Providers/ConradProvider.php +++ b/src/Services/InfoProviderSystem/Providers/ConradProvider.php @@ -31,7 +31,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; readonly class ConradProvider implements InfoProviderInterface { - private const SEARCH_ENDPOINT = 'https://api.conrad.de/search/1/v3/facetSearch'; + private const SEARCH_ENDPOINT = '/search/1/v3/facetSearch'; public function __construct(private HttpClientInterface $httpClient, private ConradSettings $settings) { @@ -40,7 +40,7 @@ readonly class ConradProvider implements InfoProviderInterface public function getProviderInfo(): array { return [ - 'name' => 'Pollin', + 'name' => 'Conrad', 'description' => 'Retrieves part information from conrad.de', 'url' => 'https://www.conrad.de/', 'disabled_help' => 'Set API key in settings', @@ -58,9 +58,38 @@ readonly class ConradProvider implements InfoProviderInterface return !empty($this->settings->apiKey); } + private function getProductUrl(string $productId): string + { + return 'https://' . $this->settings->shopID->getDomain() . '/' . $this->settings->shopID->getLanguage() . '/p/' . $productId; + } + + private function getFootprintFromTechnicalDetails(array $technicalDetails): ?string + { + foreach ($technicalDetails as $detail) { + if ($detail['name'] === 'ATT_LOV_HOUSING_SEMICONDUCTORS') { + return $detail['values'][0] ?? null; + } + } + + return null; + } + + private function getFootprintFromTechnicalAttributes(array $technicalDetails): ?string + { + foreach ($technicalDetails as $detail) { + if ($detail['attributeID'] === 'ATT.LOV.HOUSING_SEMICONDUCTORS') { + return $detail['values'][0]['value'] ?? null; + } + } + + return null; + } + public function searchByKeyword(string $keyword): array { - $url = self::SEARCH_ENDPOINT . '/' . $this->settings->country . '/' . $this->settings->language . '/' . $this->settings->customerType; + $url = $this->settings->shopID->getAPIRoot() . self::SEARCH_ENDPOINT . '/' + . $this->settings->shopID->getDomainEnd() . '/' . $this->settings->shopID->getLanguage() + . '/' . $this->settings->shopID->getCustomerType(); $response = $this->httpClient->request('POST', $url, [ 'query' => [ @@ -68,13 +97,15 @@ readonly class ConradProvider implements InfoProviderInterface ], 'json' => [ 'query' => $keyword, + 'size' => 25, ], ]); $out = []; $results = $response->toArray(); - foreach($results as $result) { + foreach($results['hits'] as $result) { + $out[] = new SearchResultDTO( provider_key: $this->getProviderKey(), provider_id: $result['productId'], @@ -83,6 +114,8 @@ readonly class ConradProvider implements InfoProviderInterface manufacturer: $result['brand']['name'] ?? null, mpn: $result['manufacturerId'] ?? null, preview_image_url: $result['image'] ?? null, + provider_url: $this->getProductUrl($result['productId']), + footprint: $this->getFootprintFromTechnicalDetails($result['technicalDetails'] ?? []), ); } @@ -91,7 +124,29 @@ readonly class ConradProvider implements InfoProviderInterface public function getDetails(string $id): PartDetailDTO { - // TODO: Implement getDetails() method. + $productInfoURL = $this->settings->shopID->getAPIRoot() . '/product/1/service/' . $this->settings->shopID->getShopID() + . '/product/' . $id; + + $response = $this->httpClient->request('GET', $productInfoURL, [ + 'query' => [ + 'apikey' => $this->settings->apiKey, + ] + ]); + + $data = $response->toArray(); + + return new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $data['shortProductNumber'], + name: $data['productShortInformation']['title'], + description: $data['productShortInformation']['shortDescription'] ?? '', + manufacturer: $data['brand']['displayName'] ?? null, + mpn: $data['productFullInformation']['manufacturer']['id'] ?? null, + preview_image_url: $data['productShortInformation']['mainImage']['imageUrl'] ?? null, + provider_url: $this->getProductUrl($data['shortProductNumber']), + footprint: $this->getFootprintFromTechnicalAttributes($data['productFullInformation']['technicalAttributes'] ?? []), + notes: $data['productFullInformation']['description'] ?? null, + ); } public function getCapabilities(): array diff --git a/src/Settings/InfoProviderSystem/ConradSettings.php b/src/Settings/InfoProviderSystem/ConradSettings.php index 2330e729..999ebfe0 100644 --- a/src/Settings/InfoProviderSystem/ConradSettings.php +++ b/src/Settings/InfoProviderSystem/ConradSettings.php @@ -31,6 +31,7 @@ use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\LanguageType; use Symfony\Component\Translation\TranslatableMessage as TM; use Symfony\Component\Validator\Constraints as Assert; @@ -46,24 +47,11 @@ class ConradSettings formOptions: ["help_html" => true], envVar: "PROVIDER_CONRAD_API_KEY", envVarMode: EnvVarMode::OVERWRITE)] public ?string $apiKey = null; - #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, - envVar: "PROVIDER_CONRAD_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)] - #[Assert\Country] - public string $country = "DE"; + #[SettingsParameter(label: new TM("settings.ips.conrad.shopID"), + formType: EnumType::class, + formOptions: ['class' => ConradShopIDs::class], + )] + public ConradShopIDs $shopID = ConradShopIDs::COM_B2B; - #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, - envVar: "PROVIDER_CONRAD_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)] - #[Assert\Language] - public string $language = "en"; - - #[SettingsParameter(label: new TM("settings.ips.conrad.customerType"), formType: ChoiceType::class, - formOptions: [ - "choices" => [ - "settings.ips.conrad.customerType.b2c" => "b2c", - "settings.ips.conrad.customerType.b2b" => "b2b", - ], - ], - envVar: "PROVIDER_CONRAD_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE, )] - #[Assert\Choice(choices: ["b2c", "b2b"])] - public string $customerType = "b2c"; + public bool $includeVAT = true; } diff --git a/src/Settings/InfoProviderSystem/ConradShopIDs.php b/src/Settings/InfoProviderSystem/ConradShopIDs.php new file mode 100644 index 00000000..2d8710e7 --- /dev/null +++ b/src/Settings/InfoProviderSystem/ConradShopIDs.php @@ -0,0 +1,156 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum ConradShopIDs: string implements TranslatableInterface +{ + case COM_B2B = 'HP_COM_B2B'; + case DE_B2B = 'CQ_DE_B2B'; + case AT_B2C = 'CQ_AT_B2C'; + case CH_B2C = 'CQ_CH_B2C'; + case SE_B2B = 'HP_SE_B2B'; + case HU_B2C = 'CQ_HU_B2C'; + case CZ_B2B = 'HP_CZ_B2B'; + case SI_B2B = 'HP_SI_B2B'; + case SK_B2B = 'HP_SK_B2B'; + case BE_B2B = 'HP_BE_B2B'; + case DE_B2C = 'CQ_DE_B2C'; + case PL_B2B = 'HP_PL_B2B'; + case NL_B2B = 'CQ_NL_B2B'; + case DK_B2B = 'HP_DK_B2B'; + case IT_B2B = 'HP_IT_B2B'; + case NL_B2C = 'CQ_NL_B2C'; + case FR_B2B = 'HP_FR_B2B'; + case AT_B2B = 'CQ_AT_B2B'; + case HR_B2B = 'HP_HR_B2B'; + + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return match ($this) { + self::DE_B2B => "conrad.de (B2B)", + self::AT_B2C => "conrad.at (B2C)", + self::CH_B2C => "conrad.ch (B2C)", + self::SE_B2B => "conrad.se (B2B)", + self::HU_B2C => "conrad.hu (B2C)", + self::CZ_B2B => "conrad.cz (B2B)", + self::SI_B2B => "conrad.si (B2B)", + self::SK_B2B => "conrad.sk (B2B)", + self::BE_B2B => "conrad.be (B2B)", + self::DE_B2C => "conrad.de (B2C)", + self::PL_B2B => "conrad.pl (B2B)", + self::NL_B2B => "conrad.nl (B2B)", + self::DK_B2B => "conrad.dk (B2B)", + self::IT_B2B => "conrad.it (B2B)", + self::NL_B2C => "conrad.nl (B2C)", + self::FR_B2B => "conrad.fr (B2B)", + self::COM_B2B => "conrad.com (B2B)", + self::AT_B2B => "conrad.at (B2B)", + self::HR_B2B => "conrad.hr (B2B)", + }; + } + + public function getDomain(): string + { + return 'conrad.' . $this->getDomainEnd(); + } + + /** + * Retrieves the API root URL for this shop ID. e.g. https://api.conrad.de + * @return string + */ + public function getAPIRoot(): string + { + return 'https://api.' . $this->getDomain(); + } + + /** + * Returns the shop ID value used in the API requests. e.g. 'CQ_DE_B2B' + * @return string + */ + public function getShopID(): string + { + return $this->value; + } + + public function getDomainEnd(): string + { + return match ($this) { + self::DE_B2B, self::DE_B2C => 'de', + self::AT_B2B, self::AT_B2C => 'at', + self::CH_B2C => 'ch', + self::SE_B2B => 'se', + self::HU_B2C => 'hu', + self::CZ_B2B => 'cz', + self::SI_B2B => 'si', + self::SK_B2B => 'sk', + self::BE_B2B => 'be', + self::PL_B2B => 'pl', + self::NL_B2B, self::NL_B2C => 'nl', + self::DK_B2B => 'dk', + self::IT_B2B => 'it', + self::FR_B2B => 'fr', + self::COM_B2B => 'com', + self::HR_B2B => 'hr', + }; + } + + public function getLanguage(): string + { + return match ($this) { + self::DE_B2B, self::DE_B2C, self::AT_B2B, self::AT_B2C => 'de', + self::CH_B2C => 'de', + self::SE_B2B => 'sv', + self::HU_B2C => 'hu', + self::CZ_B2B => 'cs', + self::SI_B2B => 'sl', + self::SK_B2B => 'sk', + self::BE_B2B => 'nl', + self::PL_B2B => 'pl', + self::NL_B2B, self::NL_B2C => 'nl', + self::DK_B2B => 'da', + self::IT_B2B => 'it', + self::FR_B2B => 'fr', + self::COM_B2B => 'en', + self::HR_B2B => 'hr', + }; + } + + /** + * Retrieves the customer type for this shop ID. e.g. 'b2b' or 'b2c' + * @return string 'b2b' or 'b2c' + */ + public function getCustomerType(): string + { + return match ($this) { + self::DE_B2B, self::AT_B2B, self::SE_B2B, self::CZ_B2B, self::SI_B2B, + self::SK_B2B, self::BE_B2B, self::PL_B2B, self::NL_B2B, self::DK_B2B, + self::IT_B2B, self::FR_B2B, self::COM_B2B, self::HR_B2B => 'b2b', + self::DE_B2C, self::AT_B2C, self::CH_B2C, self::HU_B2C, self::NL_B2C => 'b2c', + }; + } +}