Skip to content

Commit 7ab33c8

Browse files
committed
Implemented basic functionality to search and retrieve part details
1 parent 705e71f commit 7ab33c8

File tree

3 files changed

+223
-24
lines changed

3 files changed

+223
-24
lines changed

src/Services/InfoProviderSystem/Providers/ConradProvider.php

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
readonly class ConradProvider implements InfoProviderInterface
3232
{
3333

34-
private const SEARCH_ENDPOINT = 'https://api.conrad.de/search/1/v3/facetSearch';
34+
private const SEARCH_ENDPOINT = '/search/1/v3/facetSearch';
3535

3636
public function __construct(private HttpClientInterface $httpClient, private ConradSettings $settings)
3737
{
@@ -40,7 +40,7 @@ public function __construct(private HttpClientInterface $httpClient, private Con
4040
public function getProviderInfo(): array
4141
{
4242
return [
43-
'name' => 'Pollin',
43+
'name' => 'Conrad',
4444
'description' => 'Retrieves part information from conrad.de',
4545
'url' => 'https://www.conrad.de/',
4646
'disabled_help' => 'Set API key in settings',
@@ -58,23 +58,54 @@ public function isActive(): bool
5858
return !empty($this->settings->apiKey);
5959
}
6060

61+
private function getProductUrl(string $productId): string
62+
{
63+
return 'https://' . $this->settings->shopID->getDomain() . '/' . $this->settings->shopID->getLanguage() . '/p/' . $productId;
64+
}
65+
66+
private function getFootprintFromTechnicalDetails(array $technicalDetails): ?string
67+
{
68+
foreach ($technicalDetails as $detail) {
69+
if ($detail['name'] === 'ATT_LOV_HOUSING_SEMICONDUCTORS') {
70+
return $detail['values'][0] ?? null;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
private function getFootprintFromTechnicalAttributes(array $technicalDetails): ?string
78+
{
79+
foreach ($technicalDetails as $detail) {
80+
if ($detail['attributeID'] === 'ATT.LOV.HOUSING_SEMICONDUCTORS') {
81+
return $detail['values'][0]['value'] ?? null;
82+
}
83+
}
84+
85+
return null;
86+
}
87+
6188
public function searchByKeyword(string $keyword): array
6289
{
63-
$url = self::SEARCH_ENDPOINT . '/' . $this->settings->country . '/' . $this->settings->language . '/' . $this->settings->customerType;
90+
$url = $this->settings->shopID->getAPIRoot() . self::SEARCH_ENDPOINT . '/'
91+
. $this->settings->shopID->getDomainEnd() . '/' . $this->settings->shopID->getLanguage()
92+
. '/' . $this->settings->shopID->getCustomerType();
6493

6594
$response = $this->httpClient->request('POST', $url, [
6695
'query' => [
6796
'apikey' => $this->settings->apiKey,
6897
],
6998
'json' => [
7099
'query' => $keyword,
100+
'size' => 25,
71101
],
72102
]);
73103

74104
$out = [];
75105
$results = $response->toArray();
76106

77-
foreach($results as $result) {
107+
foreach($results['hits'] as $result) {
108+
78109
$out[] = new SearchResultDTO(
79110
provider_key: $this->getProviderKey(),
80111
provider_id: $result['productId'],
@@ -83,6 +114,8 @@ public function searchByKeyword(string $keyword): array
83114
manufacturer: $result['brand']['name'] ?? null,
84115
mpn: $result['manufacturerId'] ?? null,
85116
preview_image_url: $result['image'] ?? null,
117+
provider_url: $this->getProductUrl($result['productId']),
118+
footprint: $this->getFootprintFromTechnicalDetails($result['technicalDetails'] ?? []),
86119
);
87120
}
88121

@@ -91,7 +124,29 @@ public function searchByKeyword(string $keyword): array
91124

92125
public function getDetails(string $id): PartDetailDTO
93126
{
94-
// TODO: Implement getDetails() method.
127+
$productInfoURL = $this->settings->shopID->getAPIRoot() . '/product/1/service/' . $this->settings->shopID->getShopID()
128+
. '/product/' . $id;
129+
130+
$response = $this->httpClient->request('GET', $productInfoURL, [
131+
'query' => [
132+
'apikey' => $this->settings->apiKey,
133+
]
134+
]);
135+
136+
$data = $response->toArray();
137+
138+
return new PartDetailDTO(
139+
provider_key: $this->getProviderKey(),
140+
provider_id: $data['shortProductNumber'],
141+
name: $data['productShortInformation']['title'],
142+
description: $data['productShortInformation']['shortDescription'] ?? '',
143+
manufacturer: $data['brand']['displayName'] ?? null,
144+
mpn: $data['productFullInformation']['manufacturer']['id'] ?? null,
145+
preview_image_url: $data['productShortInformation']['mainImage']['imageUrl'] ?? null,
146+
provider_url: $this->getProductUrl($data['shortProductNumber']),
147+
footprint: $this->getFootprintFromTechnicalAttributes($data['productFullInformation']['technicalAttributes'] ?? []),
148+
notes: $data['productFullInformation']['description'] ?? null,
149+
);
95150
}
96151

97152
public function getCapabilities(): array

src/Settings/InfoProviderSystem/ConradSettings.php

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
3232
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
3333
use Symfony\Component\Form\Extension\Core\Type\CountryType;
34+
use Symfony\Component\Form\Extension\Core\Type\EnumType;
3435
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
3536
use Symfony\Component\Translation\TranslatableMessage as TM;
3637
use Symfony\Component\Validator\Constraints as Assert;
@@ -46,24 +47,11 @@ class ConradSettings
4647
formOptions: ["help_html" => true], envVar: "PROVIDER_CONRAD_API_KEY", envVarMode: EnvVarMode::OVERWRITE)]
4748
public ?string $apiKey = null;
4849

49-
#[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class,
50-
envVar: "PROVIDER_CONRAD_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)]
51-
#[Assert\Country]
52-
public string $country = "DE";
50+
#[SettingsParameter(label: new TM("settings.ips.conrad.shopID"),
51+
formType: EnumType::class,
52+
formOptions: ['class' => ConradShopIDs::class],
53+
)]
54+
public ConradShopIDs $shopID = ConradShopIDs::COM_B2B;
5355

54-
#[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class,
55-
envVar: "PROVIDER_CONRAD_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)]
56-
#[Assert\Language]
57-
public string $language = "en";
58-
59-
#[SettingsParameter(label: new TM("settings.ips.conrad.customerType"), formType: ChoiceType::class,
60-
formOptions: [
61-
"choices" => [
62-
"settings.ips.conrad.customerType.b2c" => "b2c",
63-
"settings.ips.conrad.customerType.b2b" => "b2b",
64-
],
65-
],
66-
envVar: "PROVIDER_CONRAD_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE, )]
67-
#[Assert\Choice(choices: ["b2c", "b2b"])]
68-
public string $customerType = "b2c";
56+
public bool $includeVAT = true;
6957
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
24+
namespace App\Settings\InfoProviderSystem;
25+
26+
use Symfony\Contracts\Translation\TranslatableInterface;
27+
use Symfony\Contracts\Translation\TranslatorInterface;
28+
29+
enum ConradShopIDs: string implements TranslatableInterface
30+
{
31+
case COM_B2B = 'HP_COM_B2B';
32+
case DE_B2B = 'CQ_DE_B2B';
33+
case AT_B2C = 'CQ_AT_B2C';
34+
case CH_B2C = 'CQ_CH_B2C';
35+
case SE_B2B = 'HP_SE_B2B';
36+
case HU_B2C = 'CQ_HU_B2C';
37+
case CZ_B2B = 'HP_CZ_B2B';
38+
case SI_B2B = 'HP_SI_B2B';
39+
case SK_B2B = 'HP_SK_B2B';
40+
case BE_B2B = 'HP_BE_B2B';
41+
case DE_B2C = 'CQ_DE_B2C';
42+
case PL_B2B = 'HP_PL_B2B';
43+
case NL_B2B = 'CQ_NL_B2B';
44+
case DK_B2B = 'HP_DK_B2B';
45+
case IT_B2B = 'HP_IT_B2B';
46+
case NL_B2C = 'CQ_NL_B2C';
47+
case FR_B2B = 'HP_FR_B2B';
48+
case AT_B2B = 'CQ_AT_B2B';
49+
case HR_B2B = 'HP_HR_B2B';
50+
51+
52+
public function trans(TranslatorInterface $translator, ?string $locale = null): string
53+
{
54+
return match ($this) {
55+
self::DE_B2B => "conrad.de (B2B)",
56+
self::AT_B2C => "conrad.at (B2C)",
57+
self::CH_B2C => "conrad.ch (B2C)",
58+
self::SE_B2B => "conrad.se (B2B)",
59+
self::HU_B2C => "conrad.hu (B2C)",
60+
self::CZ_B2B => "conrad.cz (B2B)",
61+
self::SI_B2B => "conrad.si (B2B)",
62+
self::SK_B2B => "conrad.sk (B2B)",
63+
self::BE_B2B => "conrad.be (B2B)",
64+
self::DE_B2C => "conrad.de (B2C)",
65+
self::PL_B2B => "conrad.pl (B2B)",
66+
self::NL_B2B => "conrad.nl (B2B)",
67+
self::DK_B2B => "conrad.dk (B2B)",
68+
self::IT_B2B => "conrad.it (B2B)",
69+
self::NL_B2C => "conrad.nl (B2C)",
70+
self::FR_B2B => "conrad.fr (B2B)",
71+
self::COM_B2B => "conrad.com (B2B)",
72+
self::AT_B2B => "conrad.at (B2B)",
73+
self::HR_B2B => "conrad.hr (B2B)",
74+
};
75+
}
76+
77+
public function getDomain(): string
78+
{
79+
return 'conrad.' . $this->getDomainEnd();
80+
}
81+
82+
/**
83+
* Retrieves the API root URL for this shop ID. e.g. https://api.conrad.de
84+
* @return string
85+
*/
86+
public function getAPIRoot(): string
87+
{
88+
return 'https://api.' . $this->getDomain();
89+
}
90+
91+
/**
92+
* Returns the shop ID value used in the API requests. e.g. 'CQ_DE_B2B'
93+
* @return string
94+
*/
95+
public function getShopID(): string
96+
{
97+
return $this->value;
98+
}
99+
100+
public function getDomainEnd(): string
101+
{
102+
return match ($this) {
103+
self::DE_B2B, self::DE_B2C => 'de',
104+
self::AT_B2B, self::AT_B2C => 'at',
105+
self::CH_B2C => 'ch',
106+
self::SE_B2B => 'se',
107+
self::HU_B2C => 'hu',
108+
self::CZ_B2B => 'cz',
109+
self::SI_B2B => 'si',
110+
self::SK_B2B => 'sk',
111+
self::BE_B2B => 'be',
112+
self::PL_B2B => 'pl',
113+
self::NL_B2B, self::NL_B2C => 'nl',
114+
self::DK_B2B => 'dk',
115+
self::IT_B2B => 'it',
116+
self::FR_B2B => 'fr',
117+
self::COM_B2B => 'com',
118+
self::HR_B2B => 'hr',
119+
};
120+
}
121+
122+
public function getLanguage(): string
123+
{
124+
return match ($this) {
125+
self::DE_B2B, self::DE_B2C, self::AT_B2B, self::AT_B2C => 'de',
126+
self::CH_B2C => 'de',
127+
self::SE_B2B => 'sv',
128+
self::HU_B2C => 'hu',
129+
self::CZ_B2B => 'cs',
130+
self::SI_B2B => 'sl',
131+
self::SK_B2B => 'sk',
132+
self::BE_B2B => 'nl',
133+
self::PL_B2B => 'pl',
134+
self::NL_B2B, self::NL_B2C => 'nl',
135+
self::DK_B2B => 'da',
136+
self::IT_B2B => 'it',
137+
self::FR_B2B => 'fr',
138+
self::COM_B2B => 'en',
139+
self::HR_B2B => 'hr',
140+
};
141+
}
142+
143+
/**
144+
* Retrieves the customer type for this shop ID. e.g. 'b2b' or 'b2c'
145+
* @return string 'b2b' or 'b2c'
146+
*/
147+
public function getCustomerType(): string
148+
{
149+
return match ($this) {
150+
self::DE_B2B, self::AT_B2B, self::SE_B2B, self::CZ_B2B, self::SI_B2B,
151+
self::SK_B2B, self::BE_B2B, self::PL_B2B, self::NL_B2B, self::DK_B2B,
152+
self::IT_B2B, self::FR_B2B, self::COM_B2B, self::HR_B2B => 'b2b',
153+
self::DE_B2C, self::AT_B2C, self::CH_B2C, self::HU_B2C, self::NL_B2C => 'b2c',
154+
};
155+
}
156+
}

0 commit comments

Comments
 (0)