mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Use natural sorting for trees and others repository functions
This commit is contained in:
parent
9db822eabd
commit
8bb8118d9f
13 changed files with 71 additions and 44 deletions
|
@ -25,7 +25,7 @@ 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 COLLATION numeric (provider = icu, locale = 'en-u-kn-true');");
|
||||
|
||||
$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)');
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace App\Doctrine\Functions;
|
|||
|
||||
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
|
@ -33,7 +34,7 @@ use Doctrine\ORM\Query\TokenType;
|
|||
|
||||
class Natsort extends FunctionNode
|
||||
{
|
||||
private Node $field;
|
||||
private ?Node $field = null;
|
||||
|
||||
public function parse(Parser $parser): void
|
||||
{
|
||||
|
@ -47,15 +48,17 @@ class Natsort extends FunctionNode
|
|||
|
||||
public function getSql(SqlWalker $sqlWalker): string
|
||||
{
|
||||
assert($this->field !== null, 'Field is not set');
|
||||
|
||||
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||
|
||||
if ($platform instanceof AbstractPostgreSQLDriver) {
|
||||
if ($platform instanceof PostgreSQLPlatform) {
|
||||
return $this->field->dispatch($sqlWalker) . ' COLLATE numeric';
|
||||
}
|
||||
|
||||
if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) {
|
||||
/*if ($platform instanceof MariaDBPlatform && $sqlWalker->getConnection()->getServerVersion()) {
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
//For every other platform, return the field as is
|
||||
return $this->field->dispatch($sqlWalker);
|
||||
|
|
|
@ -30,11 +30,11 @@ interface PartsContainingRepositoryInterface
|
|||
* Returns all parts associated with this element.
|
||||
*
|
||||
* @param object $element the element for which the parts should be determined
|
||||
* @param array $order_by The order of the parts. Format ['name' => 'ASC']
|
||||
* @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC
|
||||
*
|
||||
* @return Part[]
|
||||
*/
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array;
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array;
|
||||
|
||||
/**
|
||||
* Gets the count of the parts associated with this element.
|
||||
|
|
|
@ -40,11 +40,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
|||
* Returns all parts associated with this element.
|
||||
*
|
||||
* @param object $element the element for which the parts should be determined
|
||||
* @param array $order_by The order of the parts. Format ['name' => 'ASC']
|
||||
* @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC
|
||||
*
|
||||
* @return Part[]
|
||||
*/
|
||||
abstract public function getParts(object $element, array $order_by = ['name' => 'ASC']): array;
|
||||
abstract public function getParts(object $element, string $nameOrderDirection = "ASC"): array;
|
||||
|
||||
/**
|
||||
* Gets the count of the parts associated with this element.
|
||||
|
@ -113,7 +113,7 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
|||
return $parts;
|
||||
}
|
||||
|
||||
protected function getPartsByField(object $element, array $order_by, string $field_name): array
|
||||
protected function getPartsByField(object $element, string $nameOrderDirection, string $field_name): array
|
||||
{
|
||||
if (!$element instanceof AbstractPartsContainingDBElement) {
|
||||
throw new InvalidArgumentException('$element must be an instance of AbstractPartContainingDBElement!');
|
||||
|
@ -121,7 +121,14 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
|||
|
||||
$repo = $this->getEntityManager()->getRepository(Part::class);
|
||||
|
||||
return $repo->findBy([$field_name => $element], $order_by);
|
||||
//Build a query builder to get the parts with a custom order by
|
||||
|
||||
$qb = $repo->createQueryBuilder('part')
|
||||
->where('part.'.$field_name.' = :element')
|
||||
->setParameter('element', $element)
|
||||
->orderBy('NATSORT(part.name)', $nameOrderDirection);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
protected function getPartsCountByField(object $element, string $field_name): int
|
||||
|
|
|
@ -42,7 +42,7 @@ class NamedDBElementRepository extends DBElementRepository
|
|||
{
|
||||
$result = [];
|
||||
|
||||
$entities = $this->findBy([], ['name' => 'ASC']);
|
||||
$entities = $this->getFlatList();
|
||||
foreach ($entities as $entity) {
|
||||
/** @var AbstractNamedDBElement $entity */
|
||||
$node = new TreeViewNode($entity->getName(), null, null);
|
||||
|
@ -65,13 +65,17 @@ class NamedDBElementRepository extends DBElementRepository
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a flattened list of all nodes.
|
||||
* Returns a flattened list of all nodes, sorted by name in natural order.
|
||||
* @return AbstractNamedDBElement[]
|
||||
* @phpstan-return array<int, AbstractNamedDBElement>
|
||||
*/
|
||||
public function getFlatList(): array
|
||||
{
|
||||
//All nodes are sorted by name
|
||||
return $this->findBy([], ['name' => 'ASC']);
|
||||
$qb = $this->createQueryBuilder('e');
|
||||
$q = $qb->select('e')
|
||||
->orderBy('NATSORT(e.name)', 'ASC')
|
||||
->getQuery();
|
||||
|
||||
return $q->getResult();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ class PartRepository extends NamedDBElementRepository
|
|||
$qb->setParameter('query', '%'.$query.'%');
|
||||
|
||||
$qb->setMaxResults($max_limits);
|
||||
$qb->orderBy('part.name', 'ASC');
|
||||
$qb->orderBy('NATSORT(part.name)', 'ASC');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
|
|
@ -28,13 +28,13 @@ use InvalidArgumentException;
|
|||
|
||||
class CategoryRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof Category) {
|
||||
throw new InvalidArgumentException('$element must be an Category!');
|
||||
}
|
||||
|
||||
return $this->getPartsByField($element, $order_by, 'category');
|
||||
return $this->getPartsByField($element, $nameOrderDirection, 'category');
|
||||
}
|
||||
|
||||
public function getPartsCount(object $element): int
|
||||
|
|
|
@ -28,13 +28,13 @@ use InvalidArgumentException;
|
|||
|
||||
class FootprintRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof Footprint) {
|
||||
throw new InvalidArgumentException('$element must be an Footprint!');
|
||||
}
|
||||
|
||||
return $this->getPartsByField($element, $order_by, 'footprint');
|
||||
return $this->getPartsByField($element, $nameOrderDirection, 'footprint');
|
||||
}
|
||||
|
||||
public function getPartsCount(object $element): int
|
||||
|
|
|
@ -28,13 +28,13 @@ use InvalidArgumentException;
|
|||
|
||||
class ManufacturerRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof Manufacturer) {
|
||||
throw new InvalidArgumentException('$element must be an Manufacturer!');
|
||||
}
|
||||
|
||||
return $this->getPartsByField($element, $order_by, 'manufacturer');
|
||||
return $this->getPartsByField($element, $nameOrderDirection, 'manufacturer');
|
||||
}
|
||||
|
||||
public function getPartsCount(object $element): int
|
||||
|
|
|
@ -28,13 +28,13 @@ use InvalidArgumentException;
|
|||
|
||||
class MeasurementUnitRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof MeasurementUnit) {
|
||||
throw new InvalidArgumentException('$element must be an MeasurementUnit!');
|
||||
}
|
||||
|
||||
return $this->getPartsByField($element, $order_by, 'partUnit');
|
||||
return $this->getPartsByField($element, $nameOrderDirection, 'partUnit');
|
||||
}
|
||||
|
||||
public function getPartsCount(object $element): int
|
||||
|
|
|
@ -30,12 +30,7 @@ use InvalidArgumentException;
|
|||
|
||||
class StorelocationRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
/**
|
||||
* @param object $element
|
||||
* @param array $order_by
|
||||
* @return array
|
||||
*/
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof StorageLocation) {
|
||||
throw new InvalidArgumentException('$element must be an Storelocation!');
|
||||
|
@ -47,11 +42,9 @@ class StorelocationRepository extends AbstractPartsContainingRepository
|
|||
->from(Part::class, 'part')
|
||||
->leftJoin('part.partLots', 'lots')
|
||||
->where('lots.storage_location = ?1')
|
||||
->setParameter(1, $element);
|
||||
|
||||
foreach ($order_by as $field => $order) {
|
||||
$qb->addOrderBy('part.'.$field, $order);
|
||||
}
|
||||
->setParameter(1, $element)
|
||||
->orderBy('NATSORT(part.name)', $nameOrderDirection)
|
||||
;
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ use InvalidArgumentException;
|
|||
|
||||
class SupplierRepository extends AbstractPartsContainingRepository
|
||||
{
|
||||
public function getParts(object $element, array $order_by = ['name' => 'ASC']): array
|
||||
public function getParts(object $element, string $nameOrderDirection = "ASC"): array
|
||||
{
|
||||
if (!$element instanceof Supplier) {
|
||||
throw new InvalidArgumentException('$element must be an Supplier!');
|
||||
|
@ -42,11 +42,9 @@ class SupplierRepository extends AbstractPartsContainingRepository
|
|||
->from(Part::class, 'part')
|
||||
->leftJoin('part.orderdetails', 'orderdetail')
|
||||
->where('orderdetail.supplier = ?1')
|
||||
->setParameter(1, $element);
|
||||
|
||||
foreach ($order_by as $field => $order) {
|
||||
$qb->addOrderBy('part.'.$field, $order);
|
||||
}
|
||||
->setParameter(1, $element)
|
||||
->orderBy('NATSORT(part.name)', $nameOrderDirection)
|
||||
;
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,28 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
|||
*/
|
||||
private array $new_entity_cache = [];
|
||||
|
||||
/**
|
||||
* Finds all nodes for the given parent node, ordered by name in a natural sort way
|
||||
* @param AbstractStructuralDBElement|null $parent
|
||||
* @param string $nameOrdering The ordering of the names. Either ASC or DESC
|
||||
* @return array
|
||||
*/
|
||||
public function findNodesForParent(?AbstractStructuralDBElement $parent, string $nameOrdering = "ASC"): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('e');
|
||||
$qb->select('e')
|
||||
->orderBy('NATSORT(e.name)', $nameOrdering);
|
||||
|
||||
if ($parent) {
|
||||
$qb->where('e.parent = :parent')
|
||||
->setParameter('parent', $parent);
|
||||
} else {
|
||||
$qb->where('e.parent IS NULL');
|
||||
}
|
||||
//@phpstan-ignore-next-line [parent is only defined by the sub classes]
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all nodes without a parent node. They are our root nodes.
|
||||
*
|
||||
|
@ -47,7 +69,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
|||
*/
|
||||
public function findRootNodes(): array
|
||||
{
|
||||
return $this->findBy(['parent' => null], ['name' => 'ASC']);
|
||||
return $this->findNodesForParent(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +85,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
|||
{
|
||||
$result = [];
|
||||
|
||||
$entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']);
|
||||
$entities = $this->findNodesForParent($parent);
|
||||
foreach ($entities as $entity) {
|
||||
/** @var AbstractStructuralDBElement $entity */
|
||||
//Make a recursive call to find all children nodes
|
||||
|
@ -89,7 +111,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
|||
{
|
||||
$result = [];
|
||||
|
||||
$entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']);
|
||||
$entities = $this->findNodesForParent($parent);
|
||||
|
||||
$elementIterator = new StructuralDBElementIterator($entities);
|
||||
$recursiveIterator = new RecursiveIteratorIterator($elementIterator, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue