diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 4cb9939e..01130c1b 100644 --- a/src/DataTables/Filters/Constraints/FilterTrait.php +++ b/src/DataTables/Filters/Constraints/FilterTrait.php @@ -7,6 +7,23 @@ use Doctrine\ORM\QueryBuilder; trait FilterTrait { + protected $useHaving = false; + + public function useHaving($value = true): self + { + $this->useHaving = $value; + return $this; + } + + /** + * Checks if the given input is an aggregateFunction like COUNT(part.partsLot) or so + * @return bool + */ + protected function isAggregateFunctionString(string $input): bool + { + return preg_match('/^[a-zA-Z]+\(.*\)$/', $input) === 1; + } + /** * Generates a parameter identifier that can be used for the given property. It gives random results, to be unique, so you have to cache it. * @param string $property @@ -14,7 +31,10 @@ trait FilterTrait */ protected function generateParameterIdentifier(string $property): string { - return str_replace('.', '_', $property) . '_' . uniqid("", false); + //Replace all special characters with underscores + $property = preg_replace('/[^a-zA-Z0-9_]/', '_', $property); + //Add a random number to the end of the property name for uniqueness + return $property . '_' . uniqid("", false); } /** @@ -27,7 +47,12 @@ trait FilterTrait */ protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, $value): void { - $queryBuilder->andWhere(sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier)); + if($this->useHaving || $this->isAggregateFunctionString($property)) { //If the property is an aggregate function, we have to use the "having" instead of the "where" + $queryBuilder->andHaving(sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier)); + } else { + $queryBuilder->andWhere(sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier)); + } + $queryBuilder->setParameter($parameterIdentifier, $value); } } \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/IntConstraint.php b/src/DataTables/Filters/Constraints/IntConstraint.php new file mode 100644 index 00000000..2df3864e --- /dev/null +++ b/src/DataTables/Filters/Constraints/IntConstraint.php @@ -0,0 +1,20 @@ +value1 !== null) { + $this->value1 = (int) $this->value1; + } + if($this->value2 !== null) { + $this->value2 = (int) $this->value2; + } + + parent::apply($queryBuilder); + } +} \ No newline at end of file diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 9b46e4e8..8935d6ba 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -5,6 +5,7 @@ namespace App\DataTables\Filters; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; +use App\DataTables\Filters\Constraints\IntConstraint; use App\DataTables\Filters\Constraints\NumberConstraint; use App\DataTables\Filters\Constraints\Part\TagsConstraint; use App\DataTables\Filters\Constraints\TextConstraint; @@ -70,6 +71,9 @@ class PartFilter implements FilterInterface /** @var EntityConstraint */ protected $storelocation; + /** @var NumberConstraint */ + protected $lotCount; + /** @var EntityConstraint */ protected $measurementUnit; @@ -92,7 +96,7 @@ class PartFilter implements FilterInterface $this->needsReview = new BooleanConstraint('part.needs_review'); $this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit'); $this->mass = new NumberConstraint('part.mass'); - $this->dbId = new NumberConstraint('part.id'); + $this->dbId = new IntConstraint('part.id'); $this->addedDate = new DateTimeConstraint('part.addedDate'); $this->lastModified = new DateTimeConstraint('part.lastModified'); @@ -104,6 +108,7 @@ class PartFilter implements FilterInterface $this->manufacturer_product_url = new TextConstraint('part.manufacturer_product_url'); $this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location'); + $this->lotCount = new IntConstraint('COUNT(partLots)'); } public function apply(QueryBuilder $queryBuilder): void @@ -244,6 +249,11 @@ class PartFilter implements FilterInterface return $this->manufacturer_product_number; } + public function getLotCount(): NumberConstraint + { + return $this->lotCount; + } + /** * @return TagsConstraint */ diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index f1ed8d66..55891457 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -142,6 +142,11 @@ class PartFilterType extends AbstractType 'min' => 0, ]); + $builder->add('lotCount', NumberConstraintType::class, [ + 'label' => 'part.filter.lot_count', + 'min' => 0 + ]); + $builder->add('submit', SubmitType::class, [ 'label' => 'Update',