mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Added filter constraint for manufacturing status.
This commit is contained in:
parent
7b3538a2c7
commit
ec5e956e31
11 changed files with 342 additions and 1 deletions
15
assets/controllers/elements/select_multiple_controller.js
Normal file
15
assets/controllers/elements/select_multiple_controller.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import {Controller} from "@hotwired/stimulus";
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
this._tomSelect = new TomSelect(this.element, {
|
||||
maxItems: 1000,
|
||||
allowEmptyOption: true,
|
||||
plugins: ['remove_button'],
|
||||
});
|
||||
}
|
||||
|
||||
}
|
84
src/DataTables/Filters/Constraints/ChoiceConstraint.php
Normal file
84
src/DataTables/Filters/Constraints/ChoiceConstraint.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class ChoiceConstraint extends AbstractConstraint
|
||||
{
|
||||
public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE'];
|
||||
|
||||
/**
|
||||
* @var string[] The values to compare to
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var string The operator to use
|
||||
*/
|
||||
protected $operator;
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getValue(): array
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $value
|
||||
* @return ChoiceConstraint
|
||||
*/
|
||||
public function setValue(array $value): ChoiceConstraint
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator(): string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @return ChoiceConstraint
|
||||
*/
|
||||
public function setOperator(string $operator): ChoiceConstraint
|
||||
{
|
||||
$this->operator = $operator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !empty($this->operator);
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
//If no value is provided then we do not apply a filter
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure we have an valid operator
|
||||
if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) {
|
||||
throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES));
|
||||
}
|
||||
|
||||
if ($this->operator === 'ANY') {
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'IN', $this->value);
|
||||
} elseif ($this->operator === 'NONE') {
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $this->value);
|
||||
} else {
|
||||
throw new \RuntimeException('Unknown operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ trait FilterTrait
|
|||
*/
|
||||
protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, $value): void
|
||||
{
|
||||
if ($comparison_operator === 'IN') {
|
||||
if ($comparison_operator === 'IN' || $comparison_operator === 'NOT IN') {
|
||||
$expression = sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier);
|
||||
} else {
|
||||
$expression = sprintf("%s %s :%s", $property, $comparison_operator, $parameterIdentifier);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\DataTables\Filters;
|
||||
|
||||
use App\DataTables\Filters\Constraints\BooleanConstraint;
|
||||
use App\DataTables\Filters\Constraints\ChoiceConstraint;
|
||||
use App\DataTables\Filters\Constraints\DateTimeConstraint;
|
||||
use App\DataTables\Filters\Constraints\EntityConstraint;
|
||||
use App\DataTables\Filters\Constraints\IntConstraint;
|
||||
|
@ -67,6 +68,9 @@ class PartFilter implements FilterInterface
|
|||
/** @var EntityConstraint */
|
||||
protected $manufacturer;
|
||||
|
||||
/** @var ChoiceConstraint */
|
||||
protected $manufacturing_status;
|
||||
|
||||
/** @var EntityConstraint */
|
||||
protected $supplier;
|
||||
|
||||
|
@ -133,6 +137,7 @@ class PartFilter implements FilterInterface
|
|||
$this->manufacturer = new EntityConstraint($nodesListBuilder, Manufacturer::class, 'part.manufacturer');
|
||||
$this->manufacturer_product_number = new TextConstraint('part.manufacturer_product_number');
|
||||
$this->manufacturer_product_url = new TextConstraint('part.manufacturer_product_url');
|
||||
$this->manufacturing_status = new ChoiceConstraint('part.manufacturing_status');
|
||||
|
||||
$this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location');
|
||||
|
||||
|
@ -350,6 +355,11 @@ class PartFilter implements FilterInterface
|
|||
return $this->attachmentName;
|
||||
}
|
||||
|
||||
public function getManufacturingStatus(): ChoiceConstraint
|
||||
{
|
||||
return $this->manufacturing_status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
48
src/Form/Filters/Constraints/ChoiceConstraintType.php
Normal file
48
src/Form/Filters/Constraints/ChoiceConstraintType.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Filters\Constraints;
|
||||
|
||||
use App\DataTables\Filters\Constraints\ChoiceConstraint;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ChoiceConstraintType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired('choices');
|
||||
$resolver->setAllowedTypes('choices', 'array');
|
||||
|
||||
$resolver->setDefaults([
|
||||
'compound' => true,
|
||||
'data_class' => ChoiceConstraint::class,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$choices = [
|
||||
'' => '',
|
||||
'filter.choice_constraint.operator.ANY' => 'ANY',
|
||||
'filter.choice_constraint.operator.NONE' => 'NONE',
|
||||
];
|
||||
|
||||
$builder->add('operator', ChoiceType::class, [
|
||||
'choices' => $choices,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->add('value', ChoiceType::class, [
|
||||
'choices' => $options['choices'],
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'attr' => [
|
||||
'data-controller' => 'elements--select-multiple',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Form\Filters\Constraints\BooleanConstraintType;
|
||||
use App\Form\Filters\Constraints\ChoiceConstraintType;
|
||||
use App\Form\Filters\Constraints\DateTimeConstraintType;
|
||||
use App\Form\Filters\Constraints\NumberConstraintType;
|
||||
use App\Form\Filters\Constraints\StructuralEntityConstraintType;
|
||||
|
@ -123,6 +124,20 @@ class PartFilterType extends AbstractType
|
|||
'label' => 'part.edit.mpn'
|
||||
]);
|
||||
|
||||
$status_choices = [
|
||||
'm_status.unknown' => '',
|
||||
'm_status.announced' => 'announced',
|
||||
'm_status.active' => 'active',
|
||||
'm_status.nrfnd' => 'nrfnd',
|
||||
'm_status.eol' => 'eol',
|
||||
'm_status.discontinued' => 'discontinued',
|
||||
];
|
||||
|
||||
$builder->add('manufacturing_status', ChoiceConstraintType::class, [
|
||||
'label' => 'part.edit.manufacturing_status',
|
||||
'choices' => $status_choices,
|
||||
]);
|
||||
|
||||
/*
|
||||
* Purchasee informations
|
||||
*/
|
||||
|
|
|
@ -35,3 +35,7 @@
|
|||
{% block tags_constraint_widget %}
|
||||
{{ block('text_constraint_widget') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block choice_constraint_widget %}
|
||||
{{ block('text_constraint_widget') }}
|
||||
{% endblock %}
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
<div class="tab-pane pt-3" id="filter-manufacturer" role="tabpanel" aria-labelledby="filter-manufacturer-tab" tabindex="0">
|
||||
{{ form_row(filterForm.manufacturer) }}
|
||||
{{ form_row(filterForm.manufacturing_status) }}
|
||||
{{ form_row(filterForm.manufacturer_product_number) }}
|
||||
{{ form_row(filterForm.manufacturer_product_url) }}
|
||||
</div>
|
||||
|
|
112
tests/DataTables/Filters/CompoundFilterTraitTest.php
Normal file
112
tests/DataTables/Filters/CompoundFilterTraitTest.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests\DataTables\Filters;
|
||||
|
||||
use App\DataTables\Filters\CompoundFilterTrait;
|
||||
use App\DataTables\Filters\FilterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CompoundFilterTraitTest extends TestCase
|
||||
{
|
||||
|
||||
public function testFindAllChildFiltersEmpty(): void
|
||||
{
|
||||
$filter = new class {
|
||||
use CompoundFilterTrait;
|
||||
|
||||
public function _findAllChildFilters()
|
||||
{
|
||||
return $this->findAllChildFilters();
|
||||
}
|
||||
};
|
||||
|
||||
$result = $filter->_findAllChildFilters();
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
public function testFindAllChildFilters(): void
|
||||
{
|
||||
$f1 = $this->createMock(FilterInterface::class);
|
||||
$f2 = $this->createMock(FilterInterface::class);
|
||||
$f3 = $this->createMock(FilterInterface::class);
|
||||
|
||||
$filter = new class($f1, $f2, $f3, null) {
|
||||
use CompoundFilterTrait;
|
||||
|
||||
protected $filter1;
|
||||
private $filter2;
|
||||
public $filter3;
|
||||
protected $filter4;
|
||||
|
||||
public function __construct($f1, $f2, $f3, $f4) {
|
||||
$this->filter1 = $f1;
|
||||
$this->filter2 = $f2;
|
||||
$this->filter3 = $f3;
|
||||
$this->filter4 = $f4;
|
||||
}
|
||||
|
||||
public function _findAllChildFilters()
|
||||
{
|
||||
return $this->findAllChildFilters();
|
||||
}
|
||||
};
|
||||
|
||||
$result = $filter->_findAllChildFilters();
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertContainsOnlyInstancesOf(FilterInterface::class, $result);
|
||||
$this->assertSame([
|
||||
'filter1' => $f1,
|
||||
'filter2' => $f2,
|
||||
'filter3' => $f3
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function testApplyAllChildFilters(): void
|
||||
{
|
||||
$f1 = $this->createMock(FilterInterface::class);
|
||||
$f2 = $this->createMock(FilterInterface::class);
|
||||
$f3 = $this->createMock(FilterInterface::class);
|
||||
|
||||
$f1->expects($this->once())
|
||||
->method('apply')
|
||||
->with($this->isInstanceOf(QueryBuilder::class));
|
||||
|
||||
$f2->expects($this->once())
|
||||
->method('apply')
|
||||
->with($this->isInstanceOf(QueryBuilder::class));
|
||||
|
||||
$f3->expects($this->once())
|
||||
->method('apply')
|
||||
->with($this->isInstanceOf(QueryBuilder::class));
|
||||
|
||||
$filter = new class($f1, $f2, $f3, null) {
|
||||
use CompoundFilterTrait;
|
||||
|
||||
protected $filter1;
|
||||
private $filter2;
|
||||
public $filter3;
|
||||
protected $filter4;
|
||||
|
||||
public function __construct($f1, $f2, $f3, $f4) {
|
||||
$this->filter1 = $f1;
|
||||
$this->filter2 = $f2;
|
||||
$this->filter3 = $f3;
|
||||
$this->filter4 = $f4;
|
||||
}
|
||||
|
||||
public function _applyAllChildFilters(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$this->applyAllChildFilters($queryBuilder);
|
||||
}
|
||||
};
|
||||
|
||||
$qb = $this->createMock(QueryBuilder::class);
|
||||
$filter->_applyAllChildFilters($qb);
|
||||
}
|
||||
|
||||
|
||||
}
|
40
tests/DataTables/Filters/Constraints/FilterTraitTest.php
Normal file
40
tests/DataTables/Filters/Constraints/FilterTraitTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests\DataTables\Filters\Constraints;
|
||||
|
||||
use App\DataTables\Filters\Constraints\FilterTrait;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FilterTraitTest extends TestCase
|
||||
{
|
||||
use FilterTrait;
|
||||
|
||||
public function testUseHaving(): void
|
||||
{
|
||||
$this->assertFalse($this->useHaving);
|
||||
|
||||
$this->useHaving();
|
||||
$this->assertTrue($this->useHaving);
|
||||
|
||||
$this->useHaving(false);
|
||||
$this->assertFalse($this->useHaving);
|
||||
}
|
||||
|
||||
public function isAggregateFunctionStringDataProvider(): iterable
|
||||
{
|
||||
yield [false, 'parts.test'];
|
||||
yield [false, 'attachments.test'];
|
||||
yield [true, 'COUNT(attachments)'];
|
||||
yield [true, 'MAX(attachments.value)'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider isAggregateFunctionStringDataProvider
|
||||
*/
|
||||
public function testIsAggregateFunctionString(bool $expected, string $input): void
|
||||
{
|
||||
$this->assertEquals($expected, $this->isAggregateFunctionString($input));
|
||||
}
|
||||
|
||||
}
|
|
@ -9501,5 +9501,17 @@ Element 3</target>
|
|||
<target>Attachment name</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".gg9fsx" name="filter.choice_constraint.operator.ANY">
|
||||
<segment>
|
||||
<source>filter.choice_constraint.operator.ANY</source>
|
||||
<target>Any of</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dbzWAHM" name="filter.choice_constraint.operator.NONE">
|
||||
<segment>
|
||||
<source>filter.choice_constraint.operator.NONE</source>
|
||||
<target>None of</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue