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