diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 4048f399..451c8630 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -46,6 +46,7 @@ doctrine: regexp: App\Doctrine\Functions\Regexp field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 + natsort: App\Doctrine\Functions\Natsort when@test: doctrine: diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index b23bea9b..df94814b 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -24,6 +24,9 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme public function postgreSQLUp(Schema $schema): void { + //Create a collation for natural sorting + $this->addSql("CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');"); + $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 70995209..a53d61c9 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -86,6 +86,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', + 'orderField' => 'NATSORT(attachment.name)', 'render' => function ($value, Attachment $context) { //Link to external source if ($context->isExternal()) { @@ -111,6 +112,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('attachment_type', TextColumn::class, [ 'label' => 'attachment.table.type', 'field' => 'attachment_type.name', + 'orderField' => 'NATSORT(attachment_type.name)', 'render' => fn($value, Attachment $context): string => sprintf( '%s', $this->entityURLGenerator->editURL($context->getAttachmentType()), diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index b63fc2d7..b14c1b1f 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -154,7 +154,7 @@ class LogDataTable implements DataTableTypeInterface $dataTable->add('user', TextColumn::class, [ 'label' => 'log.user', - 'orderField' => 'user.name', + 'orderField' => 'NATSORT(user.name)', 'render' => function ($value, AbstractLogEntry $context): string { $user = $context->getUser(); diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 36da9eda..e04546ef 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -108,36 +108,40 @@ final class PartsDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context), + 'orderField' => 'NATSORT(part.name)' ]) ->add('id', TextColumn::class, [ 'label' => $this->translator->trans('part.table.id'), ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), + 'orderField' => 'NATSORT(part.ipn)' ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), + 'orderField' => 'NATSORT(part.description)' ]) ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'category', - 'orderField' => '_category.name' + 'orderField' => 'NATSORT(_category.name)' ]) ->add('footprint', EntityColumn::class, [ 'property' => 'footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => '_footprint.name' + 'orderField' => 'NATSORT(_footprint.name)' ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => '_manufacturer.name' + 'orderField' => 'NATSORT(_manufacturer.name)' ]) ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), - 'orderField' => '_storelocations.name', + 'orderField' => 'NATSORT(_storelocations.name)', 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), ], alias: 'storage_location') + ->add('amount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.amount'), 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context), @@ -151,7 +155,7 @@ final class PartsDataTable implements DataTableTypeInterface ->add('partUnit', TextColumn::class, [ 'field' => 'partUnit.name', 'label' => $this->translator->trans('part.table.partUnit'), - 'orderField' => '_partUnit.name' + 'orderField' => 'NATSORT(_partUnit.name)' ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index ffe166f3..c59ca3bd 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -82,7 +82,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), - 'orderField' => 'part.name', + 'orderField' => 'NATSORT(part.name)', 'render' => function ($value, ProjectBOMEntry $context) { if(!$context->getPart() instanceof Part) { return htmlspecialchars($context->getName()); @@ -101,7 +101,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), - 'orderField' => 'part.ipn', + 'orderField' => 'NATSORT(part.ipn)', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { if($context->getPart() instanceof Part) { @@ -124,18 +124,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'part.category', - 'orderField' => 'category.name', + 'orderField' => 'NATSORT(category.name)', ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => 'footprint.name', + 'orderField' => 'NATSORT(footprint.name)', ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'part.manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => 'manufacturer.name', + 'orderField' => 'NATSORT(manufacturer.name)', ]) ->add('mountnames', TextColumn::class, [ diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php new file mode 100644 index 00000000..51aeb01b --- /dev/null +++ b/src/Doctrine/Functions/Natsort.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class Natsort extends FunctionNode +{ + private Node $field; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + if ($platform instanceof AbstractPostgreSQLDriver) { + return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; + } + + if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) { + + } + + //For every other platform, return the field as is + return $this->field->dispatch($sqlWalker); + } + + +} \ No newline at end of file