diff --git a/config/reference.php b/config/reference.php index 5d58153f..6ea52419 100644 --- a/config/reference.php +++ b/config/reference.php @@ -1548,6 +1548,11 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * dump_destination?: scalar|null, // A stream URL where dumps should be written to. // Default: null * theme?: "dark"|"light", // Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light". // Default: "dark" * } + * @psalm-type MakerConfig = array{ + * root_namespace?: scalar|null, // Default: "App" + * generate_final_classes?: bool, // Default: true + * generate_final_entities?: bool, // Default: false + * } * @psalm-type WebpackEncoreConfig = array{ * output_path: scalar|null, // The path where Encore is building the assets - i.e. Encore.setOutputPath() * crossorigin?: false|"anonymous"|"use-credentials", // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false @@ -1672,12 +1677,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * post_processors?: array>, * }, * } - * @psalm-type DamaDoctrineTestConfig = array{ - * enable_static_connection?: mixed, // Default: true - * enable_static_meta_data_cache?: bool, // Default: true - * enable_static_query_cache?: bool, // Default: true - * connection_keys?: list, - * } * @psalm-type TwigExtraConfig = array{ * cache?: bool|array{ * enabled?: bool, // Default: false @@ -2373,6 +2372,13 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * invalidate_on_env_change?: bool, // Default: true * }, * } + * @psalm-type JbtronicsTranslationEditorConfig = array{ + * translations_path?: scalar|null, // Default: "%translator.default_path%" + * format?: scalar|null, // Default: "xlf" + * xliff_version?: scalar|null, // Default: "2.0" + * use_intl_icu_format?: bool, // Default: false + * writer_options?: list, + * } * @psalm-type ApiPlatformConfig = array{ * title?: scalar|null, // The title of the API. // Default: "" * description?: scalar|null, // The description of the API. // Default: "" @@ -2628,17 +2634,11 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * ... * }, * } - * @psalm-type MakerConfig = array{ - * root_namespace?: scalar|null, // Default: "App" - * generate_final_classes?: bool, // Default: true - * generate_final_entities?: bool, // Default: false - * } - * @psalm-type JbtronicsTranslationEditorConfig = array{ - * translations_path?: scalar|null, // Default: "%translator.default_path%" - * format?: scalar|null, // Default: "xlf" - * xliff_version?: scalar|null, // Default: "2.0" - * use_intl_icu_format?: bool, // Default: false - * writer_options?: list, + * @psalm-type DamaDoctrineTestConfig = array{ + * enable_static_connection?: mixed, // Default: true + * enable_static_meta_data_cache?: bool, // Default: true + * enable_static_query_cache?: bool, // Default: true + * connection_keys?: list, * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 3c83001a..9d5fee5e 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -132,6 +132,20 @@ class PartRepository extends NamedDBElementRepository $category = $part->getCategory(); $ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []]; + //Show global prefix first if configured + if ($this->ipnSuggestSettings->globalPrefix !== null && $this->ipnSuggestSettings->globalPrefix !== '') { + $ipnSuggestions['commonPrefixes'][] = [ + 'title' => $this->ipnSuggestSettings->globalPrefix, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') + ]; + + $increment = $this->generateNextPossibleGlobalIncrement(); + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $this->ipnSuggestSettings->globalPrefix . $increment, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') + ]; + } + if (strlen($description) > 150) { $description = substr($description, 0, 150); } @@ -160,17 +174,17 @@ class PartRepository extends NamedDBElementRepository if ($category instanceof Category) { $currentPath = $category->getPartIpnPrefix(); $directIpnPrefixEmpty = $category->getPartIpnPrefix() === ''; - $currentPath = $currentPath === '' ? 'n.a.' : $currentPath; + $currentPath = $currentPath === '' ? $this->ipnSuggestSettings->fallbackPrefix : $currentPath; $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $currentPath . '-', + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, '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') ]; $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $currentPath . '-' . $increment, + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $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') ]; @@ -179,18 +193,19 @@ class PartRepository extends NamedDBElementRepository while ($parentCategory instanceof Category) { // Prepend the parent category's prefix to the current path - $currentPath = $parentCategory->getPartIpnPrefix() . '-' . $currentPath; - $currentPath = $parentCategory->getPartIpnPrefix() === '' ? 'n.a.-' . $currentPath : $currentPath; + $effectiveIPNPrefix = $parentCategory->getPartIpnPrefix() === '' ? $this->ipnSuggestSettings->fallbackPrefix : $parentCategory->getPartIpnPrefix(); + + $currentPath = $effectiveIPNPrefix . $this->ipnSuggestSettings->categorySeparator . $currentPath; $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $currentPath . '-', + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment') ]; $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $currentPath . '-' . $increment, + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $increment, 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.increment') ]; @@ -199,7 +214,7 @@ class PartRepository extends NamedDBElementRepository } } elseif ($part->getID() === null) { $ipnSuggestions['commonPrefixes'][] = [ - 'title' => 'n.a.', + 'title' => $this->ipnSuggestSettings->fallbackPrefix, 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.not_saved') ]; } @@ -246,6 +261,33 @@ class PartRepository extends NamedDBElementRepository return $this->getNextIpnSuggestion($givenIpnsWithSameDescription); } + private function generateNextPossibleGlobalIncrement(): string + { + $qb = $this->createQueryBuilder('part'); + + + $qb->select('part.ipn') + ->where('REGEXP(part.ipn, :ipnPattern) = TRUE') + ->setParameter('ipnPattern', '^' . preg_quote($this->ipnSuggestSettings->globalPrefix, '/') . '\d+$') + ->orderBy('NATSORT(part.ipn)', 'DESC') + ->setMaxResults(1) + ; + + $highestIPN = $qb->getQuery()->getOneOrNullResult(); + if ($highestIPN !== null) { + //Remove the prefix and extract the increment part + $incrementPart = substr($highestIPN['ipn'], strlen($this->ipnSuggestSettings->globalPrefix)); + //Extract a number using regex + preg_match('/(\d+)$/', $incrementPart, $matches); + $incrementInt = isset($matches[1]) ? (int) $matches[1] + 1 : 0; + } else { + $incrementInt = 1; + } + + + return str_pad((string) $incrementInt, $this->ipnSuggestSettings->suggestPartDigits, '0', STR_PAD_LEFT); + } + /** * Generates the next possible increment for a part within a given category, while ensuring uniqueness. * @@ -266,7 +308,7 @@ class PartRepository extends NamedDBElementRepository { $qb = $this->createQueryBuilder('part'); - $expectedLength = strlen($currentPath) + 1 + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits + $expectedLength = strlen($currentPath) + strlen($this->ipnSuggestSettings->categorySeparator) + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits // Fetch all parts in the given category, sorted by their ID in ascending order $qb->select('part') diff --git a/src/Settings/MiscSettings/IpnSuggestSettings.php b/src/Settings/MiscSettings/IpnSuggestSettings.php index 44c4e24e..16face21 100644 --- a/src/Settings/MiscSettings/IpnSuggestSettings.php +++ b/src/Settings/MiscSettings/IpnSuggestSettings.php @@ -78,4 +78,32 @@ class IpnSuggestSettings envVar: "bool:IPN_USE_DUPLICATE_DESCRIPTION", envVarMode: EnvVarMode::OVERWRITE, )] public bool $useDuplicateDescription = false; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.fallbackPrefix"), + description: new TM("settings.misc.ipn_suggest.fallbackPrefix.help"), + options: ['type' => StringType::class], + )] + public string $fallbackPrefix = 'N.A.'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.numberSeparator"), + description: new TM("settings.misc.ipn_suggest.numberSeparator.help"), + options: ['type' => StringType::class], + )] + public ?string $numberSeparator = '-'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.categorySeparator"), + description: new TM("settings.misc.ipn_suggest.categorySeparator.help"), + options: ['type' => StringType::class], + )] + public ?string $categorySeparator = '-'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.globalPrefix"), + description: new TM("settings.misc.ipn_suggest.globalPrefix.help"), + options: ['type' => StringType::class], + )] + public ?string $globalPrefix = null; } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 6e938601..5b7749e1 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -14478,5 +14478,11 @@ Please note that this system is currently experimental, and the synonyms defined e.g. Format: 3–4 alphanumeric segments (any number) separated by "-", followed by "-" and 4 digits, e.g., PCOM-RES-0001 + + + part.edit.tab.advanced.ipn.prefix.global_prefix + The global IPN prefix, common across all parts + +