Added filter constraint for manufacturing status.

This commit is contained in:
Jan Böhmer 2022-09-04 00:45:10 +02:00
parent 7b3538a2c7
commit ec5e956e31
11 changed files with 342 additions and 1 deletions

View 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'],
});
}
}

View 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));
}
}
}

View file

@ -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);

View file

@ -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;
}
}

View 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',
]
]);
}
}

View file

@ -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
*/

View file

@ -35,3 +35,7 @@
{% block tags_constraint_widget %}
{{ block('text_constraint_widget') }}
{% endblock %}
{% block choice_constraint_widget %}
{{ block('text_constraint_widget') }}
{% endblock %}

View file

@ -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>

View 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);
}
}

View 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));
}
}

View file

@ -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>