From 92e497639616ce8a73ae8b980d12dae66c600f2c Mon Sep 17 00:00:00 2001 From: Treeed <21248276+Treeed@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:03:36 +0100 Subject: [PATCH] Show when parts from info provider already exist (#810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added button to show existing part with same manufacturer and mpn in provider list * added button to edit existing part in provider list * added docstring and comments * replaced unnecessary double quotes * Introduced a new twig variable localPart to split up the result * Highlight a row, if the part is already existing * Made buttons translatable * Improved styling of the buttons and added a badge to show a hint * Extracted database queries for part matching into its own service and optimized the query reducing the required queries by factor 2 * Allow to find existing parts via the stored providerReference This should allow the database to more quickly find entries * Allow to use part name and manufacturer alternative names for mapping * Added a button to update a local part from the info provider and moved some buttons into dropdown menu --------- Co-authored-by: jona Co-authored-by: Jan Böhmer --- src/Controller/InfoProviderController.php | 21 +++- .../InfoProviderSystem/ExistingPartFinder.php | 77 +++++++++++++++ .../search/part_search.html.twig | 95 ++++++++++++++----- translations/messages.en.xlf | 30 ++++++ 4 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 src/Services/InfoProviderSystem/ExistingPartFinder.php diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index 46c5f041..e8beb12f 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -23,10 +23,13 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; use App\Form\InfoProviderSystem\PartSearchType; +use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\PartInfoRetriever; use App\Services\InfoProviderSystem\ProviderRegistry; +use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -42,7 +45,9 @@ class InfoProviderController extends AbstractController { public function __construct(private readonly ProviderRegistry $providerRegistry, - private readonly PartInfoRetriever $infoRetriever) + private readonly PartInfoRetriever $infoRetriever, + private readonly ExistingPartFinder $existingPartFinder + ) { } @@ -79,14 +84,26 @@ class InfoProviderController extends AbstractController $keyword = $form->get('keyword')->getData(); $providers = $form->get('providers')->getData(); + $dtos = []; + try { - $results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers); + $dtos = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers); } catch (ClientException $e) { $this->addFlash('error', t('info_providers.search.error.client_exception')); $this->addFlash('error',$e->getMessage()); //Log the exception $exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]); } + + // modify the array to an array of arrays that has a field for a matching local Part + // the advantage to use that format even when we don't look for local parts is that we + // always work with the same interface + $results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos); + if(!$update_target) { + foreach ($results as $index => $result) { + $results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']); + } + } } return $this->render('info_providers/search/part_search.html.twig', [ diff --git a/src/Services/InfoProviderSystem/ExistingPartFinder.php b/src/Services/InfoProviderSystem/ExistingPartFinder.php new file mode 100644 index 00000000..762c1517 --- /dev/null +++ b/src/Services/InfoProviderSystem/ExistingPartFinder.php @@ -0,0 +1,77 @@ +findAllExisting($dto); + return count($results) > 0 ? $results[0] : null; + } + + /** + * Returns all existing local parts that match the search result. + * If no part is found, return an empty array. + * @param SearchResultDTO $dto + * @return Part[] + */ + public function findAllExisting(SearchResultDTO $dto): array + { + $qb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + $qb->select('part') + ->leftJoin('part.manufacturer', 'manufacturer') + ->Orwhere($qb->expr()->andX( + 'part.providerReference.provider_key = :providerKey', + 'part.providerReference.provider_id = :providerId', + )) + + //Or the manufacturer (allowing for alternative names) and the MPN (or part name) must match + ->OrWhere( + $qb->expr()->andX( + $qb->expr()->orX( + "ILIKE(manufacturer.name, :manufacturerName) = TRUE", + "ILIKE(manufacturer.alternative_names, :manufacturerAltNames) = TRUE", + ), + $qb->expr()->orX( + "ILIKE(part.manufacturer_product_number, :mpn) = TRUE", + "ILIKE(part.name, :mpn) = TRUE", + ) + ) + ) + ; + + $qb->setParameter('providerKey', $dto->provider_key); + $qb->setParameter('providerId', $dto->provider_id); + + $qb->setParameter('manufacturerName', $dto->manufacturer); + $qb->setParameter('manufacturerAltNames', '%'.$dto->manufacturer.'%'); + $qb->setParameter('mpn', $dto->mpn); + + return $qb->getQuery()->getResult(); + } +} \ No newline at end of file diff --git a/templates/info_providers/search/part_search.html.twig b/templates/info_providers/search/part_search.html.twig index 0de72587..3d741c34 100644 --- a/templates/info_providers/search/part_search.html.twig +++ b/templates/info_providers/search/part_search.html.twig @@ -56,61 +56,104 @@ {% for result in results %} - + {% set dto = result["dto"] %} + {# @var App\Entity\Parts\Part localPart #} + {% set localPart = result["localPart"] %} + + - - {% if result.provider_url is not null %} - {{ result.name }} + {% if dto.provider_url is not null %} + {{ dto.name }} {% else %} - {{ result.name }} + {{ dto.name }} {% endif %} - {% if result.mpn is not null %} + {% if dto.mpn is not null %}
- {{ result.mpn }} + {{ dto.mpn }} + {% endif %} + {% if result["localPart"] is not null %} + {% endif %} - {{ result.description }} - {% if result.category is not null %} + {{ dto.description }} + {% if dto.category is not null %}
- {{ result.category }} + {{ dto.category }} {% endif %} - {{ result.manufacturer ?? '' }} - {% if result.footprint is not null %} + {{ dto.manufacturer ?? '' }} + {% if dto.footprint is not null %}
- {{ result.footprint }} + {{ dto.footprint }} {% endif %} - {{ helper.m_status_to_badge(result.manufacturing_status) }} + {{ helper.m_status_to_badge(dto.manufacturing_status) }} - {% if result.provider_url %} - - {{ info_provider_label(result.provider_key)|default(result.provider_key) }} + {% if dto.provider_url %} + + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} {% else %} - {{ info_provider_label(result.provider_key)|default(result.provider_key) }} + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} {% endif %}
- {{ result.provider_id }} - + {{ dto.provider_id }} + + + {% if update_target %} {# We update an existing part #} {% set href = path('info_providers_update_part', - {'providerKey': result.provider_key, 'providerId': result.provider_id, 'id': update_target.iD}) %} + {'providerKey': dto.provider_key, 'providerId': dto.provider_id, 'id': update_target.iD}) %} {% else %} {# Create a fresh part #} {% set href = path('info_providers_create_part', - {'providerKey': result.provider_key, 'providerId': result.provider_id}) %} + {'providerKey': dto.provider_key, 'providerId': dto.provider_id}) %} {% endif %} - - - + {# If we have no local part, then we can just show the create button #} + {% if localPart is null %} + + + + {% else %} {# Otherwise add a button group with all three buttons #} + + + {% trans %}info_providers.search.existing_part_found.short{% endtrans %} + + + + + {% endif %} {% endfor %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index de8531aa..b63c1377 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12227,5 +12227,35 @@ Please note, that you can not impersonate a disabled user. If you try you will g Generated code + + + info_providers.search.show_existing_part + Show existing part + + + + + info_providers.search.edit_existing_part + Edit existing part + + + + + info_providers.search.existing_part_found.short + Part already existing + + + + + info_providers.search.existing_part_found + This part (or a very similar one) was already found in the database. Please check if it is the same and if you want to create it again! + + + + + info_providers.search.update_existing_part + Update existing part from info provider + +