diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index 4f9e2961..e8beb12f 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -26,6 +26,7 @@ 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; @@ -45,7 +46,8 @@ class InfoProviderController extends AbstractController public function __construct(private readonly ProviderRegistry $providerRegistry, private readonly PartInfoRetriever $infoRetriever, - private readonly EntityManagerInterface $em) + private readonly ExistingPartFinder $existingPartFinder + ) { } @@ -61,49 +63,6 @@ class InfoProviderController extends AbstractController ]); } - /** - * Looks for parts in the local database that match the results from the info provider, so the user doesn't - * accidentally add a duplicate - * - * @param array $partsList Array of Arrays. Outer Array contains one entry per search result from the info Provider - * Inner array contains one entry "dto" for the dto from the info provider - * and one entry "localPart" where the local Part will be put if it exists - * Form: [["dto" => dto from provider, "localPart" => null]["dto" => dto from provider, "localPart" => null]] - * This function might modify the original array, not sure - * @return array Same format as the input array, but for parts that exist locally null will be replaced by a Part - */ - private function matchResultsToKnownParts(array $partsList): array - { - //we need a manufacturer object to look for a manufacturer - $manufacturerQb = $this->em->getRepository(Manufacturer::class)->createQueryBuilder('manufacturer'); - $manufacturerQb->where($manufacturerQb->expr()->like('LOWER(manufacturer.name)', 'LOWER(:manufacturer_name)')); - - - //check if both manufacturer and Manufacturer part namber matches. If so, it must be the same part - //use LOWER to make the search independent of case - $mpnQb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); - $mpnQb->where($mpnQb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)')); - $mpnQb->andWhere($mpnQb->expr()->eq('part.manufacturer', ':manufacturer')); - - foreach ($partsList as $index => $part) { - $manufacturerQb->setParameter('manufacturer_name', $part['dto']->manufacturer); - $manufacturers = $manufacturerQb->getQuery()->getResult(); - if(!$manufacturers) { - continue; - } - - $mpnQb->setParameter('manufacturer', $manufacturers); - $mpnQb->setParameter('mpn', $part['dto']->mpn); - $localParts = $mpnQb->getQuery()->getResult(); - if(!$localParts) { - continue; - } - //We only use the first matching part. If a user already has duplicate parts they will get a random one - $partsList[$index]['localPart'] = $localParts[0]; - } - return $partsList; - } - #[Route('/search', name: 'info_providers_search')] #[Route('/update/{target}', name: 'info_providers_update_part_search')] public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger): Response @@ -125,20 +84,25 @@ 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];}, $results); + $results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos); if(!$update_target) { - $results = $this->matchResultsToKnownParts($results); + foreach ($results as $index => $result) { + $results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']); + } } } diff --git a/src/Services/InfoProviderSystem/ExistingPartFinder.php b/src/Services/InfoProviderSystem/ExistingPartFinder.php new file mode 100644 index 00000000..48a7cf6d --- /dev/null +++ b/src/Services/InfoProviderSystem/ExistingPartFinder.php @@ -0,0 +1,56 @@ +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') + //The manufacturer name must match + ->where("ILIKE(manufacturer.name, :manufacturerName) = TRUE") + //And the manufacturer product number must match + ->andWhere( + "ILIKE(part.manufacturer_product_number, :mpn) = TRUE" + ); + + $qb->setParameter('manufacturerName', $dto->manufacturer); + $qb->setParameter('mpn', $dto->mpn); + + return $qb->getQuery()->getResult(); + } +} \ No newline at end of file