mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-12-10 13:09:30 +00:00
Add makefile to help with development setup, change part_ids in bulk import jobs to junction table and implement filtering based on bulk import jobs status and its associated parts' statuses.
This commit is contained in:
parent
9b4d5e9c27
commit
cc9d50a8fe
22 changed files with 1357 additions and 120 deletions
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Filters\Constraints\Part;
|
||||
|
||||
use App\DataTables\Filters\Constraints\AbstractConstraint;
|
||||
use App\Entity\BulkInfoProviderImportJobPart;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class BulkImportJobExistsConstraint extends AbstractConstraint
|
||||
{
|
||||
/** @var bool|null The value of our constraint */
|
||||
protected ?bool $value = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('bulk_import_job_exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of this constraint. Null means "don't filter", true means "filter for parts in bulk import jobs", false means "filter for parts not in bulk import jobs".
|
||||
*/
|
||||
public function getValue(): ?bool
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this constraint. Null means "don't filter", true means "filter for parts in bulk import jobs", false means "filter for parts not in bulk import jobs".
|
||||
*/
|
||||
public function setValue(?bool $value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->value !== null;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
// Do not apply a filter if value is null (filter is set to ignore)
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use EXISTS subquery to avoid join conflicts
|
||||
$existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder();
|
||||
$existsSubquery->select('1')
|
||||
->from(BulkInfoProviderImportJobPart::class, 'bip_exists')
|
||||
->where('bip_exists.part = part.id');
|
||||
|
||||
if ($this->value === true) {
|
||||
// Filter for parts that ARE in bulk import jobs
|
||||
$queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
} else {
|
||||
// Filter for parts that are NOT in bulk import jobs
|
||||
$queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Filters\Constraints\Part;
|
||||
|
||||
use App\DataTables\Filters\Constraints\AbstractConstraint;
|
||||
use App\Entity\BulkInfoProviderImportJobPart;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class BulkImportJobStatusConstraint extends AbstractConstraint
|
||||
{
|
||||
/** @var array The status values to filter by */
|
||||
protected array $values = [];
|
||||
|
||||
/** @var string|null The operator to use ('any_of', 'none_of', 'all_of') */
|
||||
protected ?string $operator = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('bulk_import_job_status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status values to filter by.
|
||||
*/
|
||||
public function getValues(): array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status values to filter by.
|
||||
*/
|
||||
public function setValues(array $values): void
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the operator to use.
|
||||
*/
|
||||
public function getOperator(): ?string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operator to use.
|
||||
*/
|
||||
public function setOperator(?string $operator): void
|
||||
{
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !empty($this->values) && $this->operator !== null;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
// Do not apply a filter if values are empty or operator is null
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use EXISTS subquery to check if part has a job with the specified status(es)
|
||||
$existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder();
|
||||
$existsSubquery->select('1')
|
||||
->from(BulkInfoProviderImportJobPart::class, 'bip_status')
|
||||
->join('bip_status.job', 'job_status')
|
||||
->where('bip_status.part = part.id');
|
||||
|
||||
// Add status conditions based on operator
|
||||
if ($this->operator === 'ANY') {
|
||||
$existsSubquery->andWhere('job_status.status IN (:job_status_values)');
|
||||
$queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
$queryBuilder->setParameter('job_status_values', $this->values);
|
||||
} elseif ($this->operator === 'NONE') {
|
||||
$existsSubquery->andWhere('job_status.status IN (:job_status_values)');
|
||||
$queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
$queryBuilder->setParameter('job_status_values', $this->values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Filters\Constraints\Part;
|
||||
|
||||
use App\DataTables\Filters\Constraints\AbstractConstraint;
|
||||
use App\Entity\BulkInfoProviderImportJobPart;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class BulkImportPartStatusConstraint extends AbstractConstraint
|
||||
{
|
||||
/** @var array The status values to filter by */
|
||||
protected array $values = [];
|
||||
|
||||
/** @var string|null The operator to use ('any_of', 'none_of', 'all_of') */
|
||||
protected ?string $operator = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('bulk_import_part_status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status values to filter by.
|
||||
*/
|
||||
public function getValues(): array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status values to filter by.
|
||||
*/
|
||||
public function setValues(array $values): void
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the operator to use.
|
||||
*/
|
||||
public function getOperator(): ?string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operator to use.
|
||||
*/
|
||||
public function setOperator(?string $operator): void
|
||||
{
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !empty($this->values) && $this->operator !== null;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
// Do not apply a filter if values are empty or operator is null
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use EXISTS subquery to check if part has the specified status(es)
|
||||
$existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder();
|
||||
$existsSubquery->select('1')
|
||||
->from(BulkInfoProviderImportJobPart::class, 'bip_part_status')
|
||||
->where('bip_part_status.part = part.id');
|
||||
|
||||
// Add status conditions based on operator
|
||||
if ($this->operator === 'ANY') {
|
||||
$existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)');
|
||||
$queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
$queryBuilder->setParameter('part_status_values', $this->values);
|
||||
} elseif ($this->operator === 'NONE') {
|
||||
$existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)');
|
||||
$queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')');
|
||||
$queryBuilder->setParameter('part_status_values', $this->values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,9 @@ use App\DataTables\Filters\Constraints\NumberConstraint;
|
|||
use App\DataTables\Filters\Constraints\Part\LessThanDesiredConstraint;
|
||||
use App\DataTables\Filters\Constraints\Part\ParameterConstraint;
|
||||
use App\DataTables\Filters\Constraints\Part\TagsConstraint;
|
||||
use App\DataTables\Filters\Constraints\Part\BulkImportJobExistsConstraint;
|
||||
use App\DataTables\Filters\Constraints\Part\BulkImportJobStatusConstraint;
|
||||
use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint;
|
||||
use App\DataTables\Filters\Constraints\TextConstraint;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Parts\Category;
|
||||
|
|
@ -42,6 +45,8 @@ use App\Entity\Parts\StorageLocation;
|
|||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Entity\BulkInfoProviderImportJob;
|
||||
use App\Entity\BulkInfoProviderImportJobPart;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
|
@ -101,6 +106,14 @@ class PartFilter implements FilterInterface
|
|||
public readonly TextConstraint $bomName;
|
||||
public readonly TextConstraint $bomComment;
|
||||
|
||||
/*************************************************
|
||||
* Bulk Import Job tab
|
||||
*************************************************/
|
||||
|
||||
public readonly BulkImportJobExistsConstraint $inBulkImportJob;
|
||||
public readonly BulkImportJobStatusConstraint $bulkImportJobStatus;
|
||||
public readonly BulkImportPartStatusConstraint $bulkImportPartStatus;
|
||||
|
||||
public function __construct(NodesListBuilder $nodesListBuilder)
|
||||
{
|
||||
$this->name = new TextConstraint('part.name');
|
||||
|
|
@ -126,7 +139,7 @@ class PartFilter implements FilterInterface
|
|||
*/
|
||||
$this->amountSum = (new IntConstraint('(
|
||||
SELECT COALESCE(SUM(__partLot.amount), 0.0)
|
||||
FROM '.PartLot::class.' __partLot
|
||||
FROM ' . PartLot::class . ' __partLot
|
||||
WHERE __partLot.part = part.id
|
||||
AND __partLot.instock_unknown = false
|
||||
AND (__partLot.expiration_date IS NULL OR __partLot.expiration_date > CURRENT_DATE())
|
||||
|
|
@ -162,6 +175,11 @@ class PartFilter implements FilterInterface
|
|||
$this->bomName = new TextConstraint('_projectBomEntries.name');
|
||||
$this->bomComment = new TextConstraint('_projectBomEntries.comment');
|
||||
|
||||
// Bulk Import Job filters
|
||||
$this->inBulkImportJob = new BulkImportJobExistsConstraint();
|
||||
$this->bulkImportJobStatus = new BulkImportJobStatusConstraint();
|
||||
$this->bulkImportPartStatus = new BulkImportPartStatusConstraint();
|
||||
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ use App\Entity\Parts\ManufacturingStatus;
|
|||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\BulkInfoProviderImportJobPart;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
|
|
@ -142,23 +143,25 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.storeLocations'),
|
||||
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
|
||||
'orderField' => 'NATSORT(MIN(_storelocations.name))',
|
||||
'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
|
||||
'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),
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
|
||||
'orderField' => 'amountSum'
|
||||
])
|
||||
->add('minamount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.minamount'),
|
||||
'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value,
|
||||
$context->getPartUnit())),
|
||||
'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format(
|
||||
$value,
|
||||
$context->getPartUnit()
|
||||
)),
|
||||
])
|
||||
->add('partUnit', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.partUnit'),
|
||||
'orderField' => 'NATSORT(_partUnit.name)',
|
||||
'render' => function($value, Part $context): string {
|
||||
'render' => function ($value, Part $context): string {
|
||||
$partUnit = $context->getPartUnit();
|
||||
if ($partUnit === null) {
|
||||
return '';
|
||||
|
|
@ -167,7 +170,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
$tmp = htmlspecialchars($partUnit->getName());
|
||||
|
||||
if ($partUnit->getUnit()) {
|
||||
$tmp .= ' ('.htmlspecialchars($partUnit->getUnit()).')';
|
||||
$tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
|
@ -230,7 +233,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
}
|
||||
|
||||
if (count($projects) > $max) {
|
||||
$tmp .= ", + ".(count($projects) - $max);
|
||||
$tmp .= ", + " . (count($projects) - $max);
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
|
|
@ -366,7 +369,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
$builder->addSelect(
|
||||
'(
|
||||
SELECT COALESCE(SUM(partLot.amount), 0.0)
|
||||
FROM '.PartLot::class.' partLot
|
||||
FROM ' . PartLot::class . ' partLot
|
||||
WHERE partLot.part = part.id
|
||||
AND partLot.instock_unknown = false
|
||||
AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE())
|
||||
|
|
@ -423,6 +426,13 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
//Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1
|
||||
//$builder->addGroupBy('_projectBomEntries');
|
||||
}
|
||||
if (str_contains($dql, '_jobPart')) {
|
||||
$builder->leftJoin('part.bulkImportJobParts', '_jobPart');
|
||||
$builder->leftJoin('_jobPart.job', '_bulkImportJob');
|
||||
//Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1
|
||||
//$builder->addGroupBy('_jobPart');
|
||||
//$builder->addGroupBy('_bulkImportJob');
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue