Erweitere IPN-Suggest um Bauteilbeschreibung.

Die Implementierung berücksichtigt nun zusätzlich die Bauteilbeschreibung zu maximal 150 Zeichen Länge für die Generierung von IPN-Vorschlägen und Inkrementen.
This commit is contained in:
Marcel Diegelmann 2025-07-09 09:45:43 +02:00
parent a7665af6b8
commit b2b0f39ac6
20 changed files with 361 additions and 35 deletions

View file

@ -451,7 +451,7 @@ final class PartController extends AbstractController
$template,
[
'part' => $new_part,
'ipnSuggestions' => $partRepository->autoCompleteIpn($data, $this->autocompletePartDigits),
'ipnSuggestions' => $partRepository->autoCompleteIpn($data, base64_encode($data->getDescription()), $this->autocompletePartDigits),
'form' => $form,
'merge_old_name' => $merge_infos['tname_before'] ?? null,
'merge_other' => $merge_infos['other_part'] ?? null,

View file

@ -197,6 +197,7 @@ class TypeaheadController extends AbstractController
$partId = null;
}
$categoryId = $request->query->getInt('categoryId');
$description = $request->query->getString('description');
/** @var Part $part */
$part = $partId !== null ? $entityManager->getRepository(Part::class)->find($partId) : new Part();
@ -206,7 +207,7 @@ class TypeaheadController extends AbstractController
$clonedPart->setCategory($category);
$partRepository = $entityManager->getRepository(Part::class);
$ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $this->autocompletePartDigits);
$ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->autocompletePartDigits);
return new JsonResponse($ipnSuggestions);
}

View file

@ -86,6 +86,7 @@ class PartBaseType extends AbstractType
'attr' => [
'placeholder' => 'part.edit.description.placeholder',
'rows' => 2,
'data-ipn-suggestion' => 'descriptionField',
],
])
->add('minAmount', SIUnitType::class, [

View file

@ -109,10 +109,30 @@ class PartRepository extends NamedDBElementRepository
return $qb->getQuery()->getResult();
}
public function autoCompleteIpn(Part $part, int $autocompletePartDigits): array
/**
* Provides IPN (Internal Part Number) suggestions for a given part based on its category, description,
* and configured autocomplete digit length.
*
* This function generates suggestions for common prefixes and incremented prefixes based on
* the part's current category and its hierarchy. If the part is unsaved, a default "n.a." prefix is returned.
*
* @param Part $part The part for which autocomplete suggestions are generated.
* @param string $description Base64-encoded description to assist in generating suggestions.
* @param int $autocompletePartDigits The number of digits used in autocomplete increments.
*
* @return array An associative array containing the following keys:
* - 'commonPrefixes': List of common prefixes found for the part.
* - 'prefixesPartIncrement': Increments for the generated prefixes, including hierarchical prefixes.
*/
public function autoCompleteIpn(Part $part, string $description, int $autocompletePartDigits): array
{
$category = $part->getCategory();
$ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []];
$description = base64_decode($description);
if (strlen($description) > 150) {
$description = substr($description, 0, 150);
}
// Validate the category and ensure it's an instance of Category
if ($category instanceof Category) {
@ -127,6 +147,22 @@ class PartRepository extends NamedDBElementRepository
'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category')
];
$suggestionByDescription = $this->getIpnSuggestByDescription($description);
if ($suggestionByDescription !== null && $suggestionByDescription !== $part->getIpn() && $part->getIpn() !== null && $part->getIpn() !== '') {
$ipnSuggestions['prefixesPartIncrement'][] = [
'title' => $part->getIpn(),
'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.current-increment')
];
}
if ($suggestionByDescription !== null) {
$ipnSuggestions['prefixesPartIncrement'][] = [
'title' => $suggestionByDescription,
'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.increment')
];
}
$ipnSuggestions['prefixesPartIncrement'][] = [
'title' => $currentPath . '-' . $increment,
'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category.increment')
@ -165,7 +201,62 @@ class PartRepository extends NamedDBElementRepository
return $ipnSuggestions;
}
public function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $autocompletePartDigits): string
/**
* Suggests the next IPN (Internal Part Number) based on the provided part description.
*
* Searches for parts with similar descriptions and retrieves their existing IPNs to calculate the next suggestion.
* Returns null if the description is empty or no suggestion can be generated.
*
* @param string $description The part description to search for.
*
* @return string|null The suggested IPN, or null if no suggestion is possible.
*
* @throws NonUniqueResultException
*/
public function getIpnSuggestByDescription(string $description): ?string
{
if ($description === '') {
return null;
}
$qb = $this->createQueryBuilder('part');
$qb->select('part')
->where('part.description LIKE :descriptionPattern')
->setParameter('descriptionPattern', $description.'%')
->orderBy('part.id', 'ASC');
$partsBySameDescription = $qb->getQuery()->getResult();
$givenIpnsWithSameDescription = [];
foreach ($partsBySameDescription as $part) {
if ($part->getIpn() === null || $part->getIpn() === '') {
continue;
}
$givenIpnsWithSameDescription[] = $part->getIpn();
}
return $this->getNextIpnSuggestion($givenIpnsWithSameDescription);
}
/**
* Generates the next possible increment for a part within a given category, while ensuring uniqueness.
*
* This method calculates the next available increment for a part's identifier (`ipn`) based on the current path
* and the number of digits specified for the autocomplete feature. It ensures that the generated identifier
* aligns with the expected length and does not conflict with already existing identifiers in the same category.
*
* @param string $currentPath The base path or prefix for the part's identifier.
* @param Part $currentPart The part entity for which the increment is being generated.
* @param int $autocompletePartDigits The number of digits reserved for the increment.
*
* @return string|null The next possible increment as a zero-padded string, or null if it cannot be generated.
*
* @throws NonUniqueResultException If the query returns non-unique results.
* @throws NoResultException If the query fails to return a result.
*/
private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $autocompletePartDigits): ?string
{
$qb = $this->createQueryBuilder('part');
@ -213,4 +304,41 @@ class PartRepository extends NamedDBElementRepository
return str_pad((string) $nextIncrement, $autocompletePartDigits, '0', STR_PAD_LEFT);
}
/**
* Generates the next IPN suggestion based on the maximum numeric suffix found in the given IPNs.
*
* The new IPN is constructed using the base format of the first provided IPN,
* incremented by the next free numeric suffix. If no base IPNs are found,
* returns null.
*
* @param array $givenIpns List of IPNs to analyze.
*
* @return string|null The next suggested IPN, or null if no base IPNs can be derived.
*/
private function getNextIpnSuggestion(array $givenIpns): ?string {
$maxSuffix = 0;
foreach ($givenIpns as $ipn) {
// Check whether the IPN contains a suffix "_ <number>"
if (preg_match('/_(\d+)$/', $ipn, $matches)) {
$suffix = (int)$matches[1];
if ($suffix > $maxSuffix) {
$maxSuffix = $suffix; // Höchste Nummer speichern
}
}
}
// Find the basic format (the IPN without suffix) from the first IPN
$baseIpn = $givenIpns[0] ?? '';
$baseIpn = preg_replace('/_\d+$/', '', $baseIpn); // Entferne vorhandene "_<Zahl>"
if ($baseIpn === '') {
return null;
}
// Generate next free possible IPN
return $baseIpn . '_' . ($maxSuffix + 1);
}
}