Skip to content

Commit 0140c9a

Browse files
mknerootjbtronics
authored
Fix #1305: Enable BOM sorting on part fields (Storage location, Manufacturing status) and fix BOM table query/pagination issues (#1338)
* Fix identation * Allow ordering of column Storage Locations in BOM fix-#1152 * Fix "[Semantical Error] line 0, col 274 near 'storageLocations.name))': Error: 'storageLocations' is not defined." when trying to sort by column Storage Locations * Try to fix "Iterate with fetch join in class App\Entity\Parts\PartLot using association part not allowed." when opening BOM * Revert "Try to fix "Iterate with fetch join in class App\Entity\Parts\PartLot using association part not allowed." when opening BOM" This reverts commit 5c5c7ce. * Try to fix "Iterate with fetch join in class App\Entity\Parts\PartLot using association part not allowed." when opening BOM 2nd try * Remove alias to fix: Unknown named parameter $alias * Reformat code to allow easier diff between ProjectBomEntriesDataTable.php and PartsDataTable.php * Try if 'data' es really needed as it is not used in PartDataTable.php * Use TwoStepORMAdapter to enable sorting based on other columns like storage location, manufacturing status * Add readonly hint to projectBom query --------- Co-authored-by: root <root@part-db.fritz.box> Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
1 parent d25ac26 commit 0140c9a

1 file changed

Lines changed: 84 additions & 23 deletions

File tree

src/DataTables/ProjectBomEntriesDataTable.php

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<?php
2-
3-
declare(strict_types=1);
4-
5-
/*
2+
/**
63
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
74
*
85
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
@@ -20,33 +17,41 @@
2017
* You should have received a copy of the GNU Affero General Public License
2118
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2219
*/
20+
21+
declare(strict_types=1);
22+
2323
namespace App\DataTables;
2424

25+
use App\DataTables\Adapters\TwoStepORMAdapter;
2526
use App\DataTables\Column\EntityColumn;
2627
use App\DataTables\Column\EnumColumn;
2728
use App\DataTables\Column\LocaleDateTimeColumn;
2829
use App\DataTables\Column\MarkdownColumn;
2930
use App\DataTables\Helpers\PartDataTableHelper;
30-
use App\Entity\Attachments\Attachment;
31+
use App\Doctrine\Helpers\FieldHelper;
3132
use App\Entity\Parts\Part;
3233
use App\Entity\Parts\ManufacturingStatus;
3334
use App\Entity\ProjectSystem\ProjectBOMEntry;
3435
use App\Services\ElementTypeNameGenerator;
3536
use App\Services\EntityURLGenerator;
3637
use App\Services\Formatters\AmountFormatter;
38+
use Doctrine\ORM\AbstractQuery;
39+
use Doctrine\ORM\Query;
3740
use Doctrine\ORM\QueryBuilder;
3841
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
39-
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
4042
use Omines\DataTablesBundle\Column\TextColumn;
4143
use Omines\DataTablesBundle\DataTable;
4244
use Omines\DataTablesBundle\DataTableTypeInterface;
4345
use Symfony\Contracts\Translation\TranslatorInterface;
4446

4547
class ProjectBomEntriesDataTable implements DataTableTypeInterface
4648
{
47-
public function __construct(protected TranslatorInterface $translator, protected PartDataTableHelper $partDataTableHelper,
48-
protected EntityURLGenerator $entityURLGenerator, protected AmountFormatter $amountFormatter)
49-
{
49+
public function __construct(
50+
protected EntityURLGenerator $entityURLGenerator,
51+
protected TranslatorInterface $translator,
52+
protected AmountFormatter $amountFormatter,
53+
protected PartDataTableHelper $partDataTableHelper
54+
) {
5055
}
5156

5257

@@ -62,7 +67,7 @@ public function configure(DataTable $dataTable, array $options): void
6267
return '';
6368
}
6469
return $this->partDataTableHelper->renderPicture($context->getPart());
65-
},
70+
}
6671
])
6772

6873
->add('id', TextColumn::class, [
@@ -133,23 +138,24 @@ public function configure(DataTable $dataTable, array $options): void
133138
->add('category', EntityColumn::class, [
134139
'label' => $this->translator->trans('part.table.category'),
135140
'property' => 'part.category',
136-
'orderField' => 'NATSORT(category.name)',
141+
'orderField' => 'NATSORT(category.name)'
137142
])
138143
->add('footprint', EntityColumn::class, [
139144
'property' => 'part.footprint',
140145
'label' => $this->translator->trans('part.table.footprint'),
141-
'orderField' => 'NATSORT(footprint.name)',
146+
'orderField' => 'NATSORT(footprint.name)'
142147
])
143148

144149
->add('manufacturer', EntityColumn::class, [
145150
'property' => 'part.manufacturer',
146151
'label' => $this->translator->trans('part.table.manufacturer'),
147-
'orderField' => 'NATSORT(manufacturer.name)',
152+
'orderField' => 'NATSORT(manufacturer.name)'
148153
])
149154

150155
->add('manufacturing_status', EnumColumn::class, [
151156
'label' => $this->translator->trans('part.table.manufacturingStatus'),
152-
'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(),
157+
'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(),
158+
'orderField' => 'part.manufacturing_status',
153159
'class' => ManufacturingStatus::class,
154160
'render' => function (?ManufacturingStatus $status, ProjectBOMEntry $context): string {
155161
if ($status === null) {
@@ -183,8 +189,10 @@ public function configure(DataTable $dataTable, array $options): void
183189
return '';
184190
}
185191
])
186-
->add('storageLocations', TextColumn::class, [
187-
'label' => 'part.table.storeLocations',
192+
->add('storelocation', TextColumn::class, [
193+
'label' => $this->translator->trans('part.table.storeLocations'),
194+
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
195+
'orderField' => 'NATSORT(MIN(_storelocations.name))',
188196
'visible' => false,
189197
'render' => function ($value, ProjectBOMEntry $context) {
190198
if ($context->getPart() !== null) {
@@ -207,11 +215,13 @@ public function configure(DataTable $dataTable, array $options): void
207215

208216
$dataTable->addOrderBy('name', DataTable::SORT_ASCENDING);
209217

210-
$dataTable->createAdapter(ORMAdapter::class, [
211-
'entity' => Attachment::class,
212-
'query' => function (QueryBuilder $builder) use ($options): void {
213-
$this->getQuery($builder, $options);
218+
$dataTable->createAdapter(TwoStepORMAdapter::class, [
219+
'entity' => ProjectBOMEntry::class,
220+
'hydrate' => AbstractQuery::HYDRATE_OBJECT,
221+
'filter_query' => function (QueryBuilder $builder) use ($options): void {
222+
$this->getFilterQuery($builder, $options);
214223
},
224+
'detail_query' => $this->getDetailQuery(...),
215225
'criteria' => [
216226
function (QueryBuilder $builder) use ($options): void {
217227
$this->buildCriteria($builder, $options);
@@ -221,20 +231,71 @@ function (QueryBuilder $builder) use ($options): void {
221231
]);
222232
}
223233

224-
private function getQuery(QueryBuilder $builder, array $options): void
234+
private function getFilterQuery(QueryBuilder $builder, array $options): void
225235
{
226-
$builder->select('bom_entry')
227-
->addSelect('part')
236+
$builder
237+
->select('bom_entry.id')
228238
->from(ProjectBOMEntry::class, 'bom_entry')
229239
->leftJoin('bom_entry.part', 'part')
230240
->leftJoin('part.category', 'category')
241+
->leftJoin('part.partLots', '_partLots')
242+
->leftJoin('_partLots.storage_location', '_storelocations')
231243
->leftJoin('part.footprint', 'footprint')
232244
->leftJoin('part.manufacturer', 'manufacturer')
245+
->leftJoin('part.partCustomState', 'partCustomState')
233246
->where('bom_entry.project = :project')
234247
->setParameter('project', $options['project'])
248+
->addGroupBy('bom_entry')
249+
->addGroupBy('part')
250+
->addGroupBy('category')
251+
->addGroupBy('footprint')
252+
->addGroupBy('manufacturer')
253+
->addGroupBy('partCustomState')
235254
;
236255
}
237256

257+
private function getDetailQuery(QueryBuilder $builder, array $filter_results): void
258+
{
259+
$ids = array_map(static fn (array $row) => $row['id'], $filter_results);
260+
if ($ids === []) {
261+
$ids = [-1];
262+
}
263+
264+
$builder
265+
->select('bom_entry')
266+
->addSelect('part')
267+
->addSelect('category')
268+
->addSelect('partLots')
269+
->addSelect('storelocations')
270+
->addSelect('footprint')
271+
->addSelect('manufacturer')
272+
->addSelect('partCustomState')
273+
->from(ProjectBOMEntry::class, 'bom_entry')
274+
->leftJoin('bom_entry.part', 'part')
275+
->leftJoin('part.category', 'category')
276+
->leftJoin('part.partLots', 'partLots')
277+
->leftJoin('partLots.storage_location', 'storelocations')
278+
->leftJoin('part.footprint', 'footprint')
279+
->leftJoin('part.manufacturer', 'manufacturer')
280+
->leftJoin('part.partCustomState', 'partCustomState')
281+
->where('bom_entry.id IN (:ids)')
282+
->setParameter('ids', $ids)
283+
->addGroupBy('bom_entry')
284+
->addGroupBy('part')
285+
->addGroupBy('partLots')
286+
->addGroupBy('category')
287+
->addGroupBy('storelocations')
288+
->addGroupBy('footprint')
289+
->addGroupBy('manufacturer')
290+
->addGroupBy('partCustomState')
291+
292+
->setHint(Query::HINT_READ_ONLY, true)
293+
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, false)
294+
;
295+
296+
FieldHelper::addOrderByFieldParam($builder, 'bom_entry.id', 'ids');
297+
}
298+
238299
private function buildCriteria(QueryBuilder $builder, array $options): void
239300
{
240301

0 commit comments

Comments
 (0)