mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-07-08 17:34:32 +02:00
Allow to retrieve price and shopping informations from info providers
This commit is contained in:
parent
c4439cc9db
commit
0cb46039dd
11 changed files with 348 additions and 4 deletions
|
@ -44,6 +44,8 @@ class PartDetailDTO extends SearchResultDTO
|
|||
public readonly ?array $datasheets = null,
|
||||
/** @var ParameterDTO[]|null */
|
||||
public readonly ?array $parameters = null,
|
||||
/** @var PurchaseInfoDTO[]|null */
|
||||
public readonly ?array $vendor_infos = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
provider_key: $provider_key,
|
||||
|
|
53
src/Services/InfoProviderSystem/DTOs/PriceDTO.php
Normal file
53
src/Services/InfoProviderSystem/DTOs/PriceDTO.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use Brick\Math\BigDecimal;
|
||||
|
||||
/**
|
||||
* This DTO represents a price for a single unit in a certain discount range
|
||||
*/
|
||||
class PriceDTO
|
||||
{
|
||||
private readonly BigDecimal $price_as_big_decimal;
|
||||
|
||||
public function __construct(
|
||||
/** @var float The minimum amount that needs to get ordered for this price to be valid */
|
||||
public readonly float $minimum_discount_amount,
|
||||
/** @var string The price as string (with .) */
|
||||
public readonly string $price,
|
||||
/** @var string The currency of the used ISO code of this price detail */
|
||||
public readonly ?string $currency_iso_code,
|
||||
/** @var bool If the price includes tax */
|
||||
public readonly ?bool $includes_tax = true,
|
||||
)
|
||||
{
|
||||
$this->price_as_big_decimal = BigDecimal::of($this->price);
|
||||
}
|
||||
|
||||
public function getPriceAsBigDecimal(): BigDecimal
|
||||
{
|
||||
return $this->price_as_big_decimal;
|
||||
}
|
||||
}
|
44
src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php
Normal file
44
src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
class PurchaseInfoDTO
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $distributor_name,
|
||||
public readonly string $order_number,
|
||||
/** @var PriceDTO[] */
|
||||
public readonly array $prices,
|
||||
/** @var string|null An url to the product page of the vendor */
|
||||
public readonly ?string $product_url = null,
|
||||
)
|
||||
{
|
||||
//Ensure that the prices are PriceDTO instances
|
||||
foreach ($this->prices as $price) {
|
||||
if (!$price instanceof PriceDTO) {
|
||||
throw new \InvalidArgumentException('The prices array must only contain PriceDTO instances');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,9 +31,16 @@ use App\Entity\Parameters\PartParameter;
|
|||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
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\PurchaseInfoDTO;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
|
@ -42,7 +49,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||
class DTOtoEntityConverter
|
||||
{
|
||||
|
||||
public function __construct(private readonly EntityManagerInterface $em)
|
||||
public function __construct(private readonly EntityManagerInterface $em, private readonly string $base_currency)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -60,6 +67,35 @@ class DTOtoEntityConverter
|
|||
return $entity;
|
||||
}
|
||||
|
||||
public function convertPrice(PriceDTO $dto, Pricedetail $entity = new Pricedetail()): Pricedetail
|
||||
{
|
||||
$entity->setMinDiscountQuantity($dto->minimum_discount_amount);
|
||||
$entity->setPrice($dto->getPriceAsBigDecimal());
|
||||
|
||||
//Currency TODO
|
||||
if ($dto->currency_iso_code !== null) {
|
||||
$entity->setCurrency($this->getCurrency($dto->currency_iso_code));
|
||||
} else {
|
||||
$entity->setCurrency(null);
|
||||
}
|
||||
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function convertPurchaseInfo(PurchaseInfoDTO $dto, Orderdetail $entity = new Orderdetail()): Orderdetail
|
||||
{
|
||||
$entity->setSupplierpartnr($dto->order_number);
|
||||
$entity->setSupplierProductUrl($dto->product_url ?? '');
|
||||
|
||||
$entity->setSupplier($this->getOrCreateEntityNonNull(Supplier::class, $dto->distributor_name));
|
||||
foreach ($dto->prices as $price) {
|
||||
$entity->addPricedetail($this->convertPrice($price));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function convertFile(FileDTO $dto, PartAttachment $entity = new PartAttachment()): PartAttachment
|
||||
{
|
||||
$entity->setURL($dto->url);
|
||||
|
@ -101,9 +137,22 @@ class DTOtoEntityConverter
|
|||
$entity->addAttachment($this->convertFile($datasheet));
|
||||
}
|
||||
|
||||
//Add orderdetails and prices
|
||||
foreach ($dto->vendor_infos ?? [] as $vendor_info) {
|
||||
$entity->addOrderdetail($this->convertPurchaseInfo($vendor_info));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of AbstractStructuralDBElement
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<T> $class
|
||||
* @param string|null $name
|
||||
* @return AbstractStructuralDBElement|null
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
private function getOrCreateEntity(string $class, ?string $name): ?AbstractStructuralDBElement
|
||||
{
|
||||
//Fall through to make converting easier
|
||||
|
@ -111,7 +160,30 @@ class DTOtoEntityConverter
|
|||
return null;
|
||||
}
|
||||
|
||||
return $this->getOrCreateEntityNonNull($class, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of AbstractStructuralDBElement
|
||||
* @param string $class The class of the entity to create
|
||||
* @phpstan-param class-string<T> $class
|
||||
* @param string $name The name of the entity to create
|
||||
* @return AbstractStructuralDBElement
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
private function getOrCreateEntityNonNull(string $class, string $name): AbstractStructuralDBElement
|
||||
{
|
||||
return $this->em->getRepository($class)->findOrCreateForInfoProvider($name);
|
||||
}
|
||||
|
||||
private function getCurrency(string $iso_code): ?Currency
|
||||
{
|
||||
//Check if the currency is the base currency (then we can just return null)
|
||||
if ($iso_code === $this->base_currency) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->em->getRepository(Currency::class)->findOrCreateByISOCode($iso_code);
|
||||
}
|
||||
|
||||
}
|
|
@ -28,7 +28,9 @@ 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 Element14Provider implements InfoProviderInterface
|
||||
|
@ -39,6 +41,8 @@ class Element14Provider implements InfoProviderInterface
|
|||
private const API_VERSION_NUMBER = '1.2';
|
||||
private const NUMBER_OF_RESULTS = 20;
|
||||
|
||||
public const DISTRIBUTOR_NAME = 'Farnell';
|
||||
|
||||
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
|
||||
'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode'];
|
||||
|
||||
|
@ -105,15 +109,21 @@ class Element14Provider implements InfoProviderInterface
|
|||
mpn: $product['translatedManufacturerPartNumber'],
|
||||
preview_image_url: $this->toImageUrl($product['image'] ?? null),
|
||||
manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['releaseStatusCode'] ?? null),
|
||||
provider_url: 'https://' . self::FARNELL_STORE_ID . '/' . $product['sku'],
|
||||
provider_url: $this->generateProductURL($product['sku']),
|
||||
datasheets: $this->parseDataSheets($product['datasheets'] ?? null),
|
||||
parameters: $this->attributesToParameters($product['attributes'] ?? null),
|
||||
vendor_infos: $this->pricesToVendorInfo($product['sku'], $product['prices'] ?? [])
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function generateProductURL($sku): string
|
||||
{
|
||||
return 'https://' . self::FARNELL_STORE_ID . '/' . $sku;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[]|null $datasheets
|
||||
* @return FileDTO[]|null Array of FileDTOs
|
||||
|
@ -147,6 +157,90 @@ class Element14Provider implements InfoProviderInterface
|
|||
return 'https://' . self::FARNELL_STORE_ID . '/productimages/standard/' . $locale . $image['baseName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the price array to a VendorInfoDTO array to be used in the PartDetailDTO
|
||||
* @param string $sku
|
||||
* @param array $prices
|
||||
* @return array
|
||||
*/
|
||||
private function pricesToVendorInfo(string $sku, array $prices): array
|
||||
{
|
||||
$price_dtos = [];
|
||||
|
||||
foreach ($prices as $price) {
|
||||
$price_dtos[] = new PriceDTO(
|
||||
minimum_discount_amount: $price['from'],
|
||||
price: (string) $price['cost'],
|
||||
currency_iso_code: $this->getUsedCurrency(),
|
||||
includes_tax: false,
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
new PurchaseInfoDTO(
|
||||
distributor_name: self::DISTRIBUTOR_NAME,
|
||||
order_number: $sku,
|
||||
prices: $price_dtos,
|
||||
product_url: $this->generateProductURL($sku)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public function getUsedCurrency(): string
|
||||
{
|
||||
//Decide based on the shop ID
|
||||
return match (self::FARNELL_STORE_ID) {
|
||||
'bg.farnell.com' => 'EUR',
|
||||
'cz.farnell.com' => 'CZK',
|
||||
'dk.farnell.com' => 'DKK',
|
||||
'at.farnell.com' => 'EUR',
|
||||
'ch.farnell.com' => 'CHF',
|
||||
'de.farnell.com' => 'EUR',
|
||||
'cpc.farnell.com' => 'GBP',
|
||||
'cpcireland.farnell.com' => 'EUR',
|
||||
'export.farnell.com' => 'GBP',
|
||||
'onecall.farnell.com' => 'GBP',
|
||||
'ie.farnell.com' => 'EUR',
|
||||
'il.farnell.com' => 'USD',
|
||||
'uk.farnell.com' => 'GBP',
|
||||
'es.farnell.com' => 'EUR',
|
||||
'ee.farnell.com' => 'EUR',
|
||||
'fi.farnell.com' => 'EUR',
|
||||
'fr.farnell.com' => 'EUR',
|
||||
'hu.farnell.com' => 'HUF',
|
||||
'it.farnell.com' => 'EUR',
|
||||
'lt.farnell.com' => 'EUR',
|
||||
'lv.farnell.com' => 'EUR',
|
||||
'be.farnell.com' => 'EUR',
|
||||
'nl.farnell.com' => 'EUR',
|
||||
'no.farnell.com' => 'NOK',
|
||||
'pl.farnell.com' => 'PLN',
|
||||
'pt.farnell.com' => 'EUR',
|
||||
'ro.farnell.com' => 'EUR',
|
||||
'ru.farnell.com' => 'RUB',
|
||||
'sk.farnell.com' => 'EUR',
|
||||
'si.farnell.com' => 'EUR',
|
||||
'se.farnell.com' => 'SEK',
|
||||
'tr.farnell.com' => 'TRY',
|
||||
'canada.newark.com' => 'CAD',
|
||||
'mexico.newark.com' => 'MXN',
|
||||
'www.newark.com' => 'USD',
|
||||
'cn.element14.com' => 'CNY',
|
||||
'au.element14.com' => 'AUD',
|
||||
'nz.element14.com' => 'NZD',
|
||||
'hk.element14.com' => 'HKD',
|
||||
'sg.element14.com' => 'SGD',
|
||||
'my.element14.com' => 'MYR',
|
||||
'ph.element14.com' => 'PHP',
|
||||
'th.element14.com' => 'THB',
|
||||
'in.element14.com' => 'INR',
|
||||
'tw.element14.com' => 'TWD',
|
||||
'kr.element14.com' => 'KRW',
|
||||
'vn.element14.com' => 'VND',
|
||||
default => throw new \RuntimeException('Unknown store ID: ' . self::FARNELL_STORE_ID)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $attributes
|
||||
* @return ParameterDTO[]|null
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue