Passed
Pull Request — master (#1)
by Maxime
05:51
created

ReportRepository::appendAdjustmentsAndParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 25
c 2
b 0
f 0
dl 0
loc 36
rs 9.52
cc 2
nc 2
nop 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MonsieurBiz\SyliusSalesReportsPlugin\Repository;
6
7
use Doctrine\ORM\QueryBuilder;
8
use MonsieurBiz\SyliusSalesReportsPlugin\Exception\InvalidDateException;
9
use MonsieurBiz\SyliusSalesReportsPlugin\Exception\MissingLocaleException;
10
use Sylius\Component\Core\Model\AdjustmentInterface;
11
use Sylius\Component\Core\Model\ChannelInterface;
12
use Sylius\Component\Core\Model\OrderInterface;
13
use Sylius\Component\Core\Model\PaymentInterface;
14
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
15
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
16
17
class ReportRepository
18
{
19
    // Adjustment if Admin Order Creation plugin is installed
20
    public const ADMIN_ORDER_DISCOUNT_ADJUSTMENT = 'order_discount';
21
    public const ADMIN_ORDER_ITEM_DISCOUNT_ADJUSTMENT = 'order_item_discount';
22
23
    /**
24
     * @var OrderRepositoryInterface
25
     */
26
    protected $orderRepository;
27
28
    /**
29
     * @var ProductVariantRepositoryInterface
30
     */
31
    protected $productVariantRepository;
32
33
    /**
34
     * Result with totals, one dimensional array
35
     *
36
     * @var array
37
     */
38
    protected $result;
39
40
    /**
41
     * Results with totals by elements, two dimensional array
42
     *
43
     * @var array
44
     */
45
    protected $results;
46
47
    /**
48
     * The elements we want to group, id as key, id or label as value, one dimensional array
49
     *
50
     * @var array
51
     */
52
    protected $elements;
53
54
    /**
55
     * An array of elements we want to group, two dimensional array
56
     *
57
     * @var array
58
     */
59
    protected $elementsArray;
60
61
    /**
62
     * ReportRepository constructor.
63
     * @param OrderRepositoryInterface $orderRepository
64
     * @param ProductVariantRepositoryInterface $productVariantRepository
65
     */
66
    public function __construct(OrderRepositoryInterface $orderRepository, ProductVariantRepositoryInterface $productVariantRepository)
67
    {
68
        $this->orderRepository = $orderRepository;
69
        $this->productVariantRepository = $productVariantRepository;
70
    }
71
72
    /**
73
     * Get total sales for channel between 2 date times, or average sales from a given field
74
     *
75
     * @param ChannelInterface $channel
76
     * @param \DateTimeInterface $from
77
     * @param \DateTimeInterface|null $to
78
     * @param string|null $groupField
79
     * @return array
80
     * @throws InvalidDateException
81
     */
82
    public function getSalesForChannelForDates(
83
        ChannelInterface $channel,
84
        \DateTimeInterface $from,
85
        ?\DateTimeInterface $to = null,
86
        ?string $groupField = null
87
    ): array {
88
        $to = $to ?? $from; // If to is null, take the same day as from to make report on one day
89
        try {
90
            $from = new \DateTime($from->format("Y-m-d")." 00:00:00");
91
            $to   = new \DateTime($to->format("Y-m-d")." 23:59:59");
92
        } catch (\Exception $e) {
93
            throw new InvalidDateException('Invalid date given to report.');
94
        }
95
96
        $this->initResult();
97
98
        // Order Item Units values
99
        $this->addResults($this->getOrderItemUnitValues($channel, $from, $to), $groupField);
100
        // Order Items values
101
        $this->addResults($this->getOrderItemValues($channel, $from, $to), $groupField);
102
        // Order values
103
        $this->addResults($this->getOrderValues($channel, $from, $to), $groupField);
104
105
        // Divide results by number of elements if needed
106
        $this->averageResult();
107
108
        return $this->result;
109
    }
110
111
112
    /**
113
     * Get average sales for channel between 2 date times
114
     *
115
     * @param ChannelInterface $channel
116
     * @param \DateTimeInterface $from
117
     * @param \DateTimeInterface|null $to
118
     * @return array
119
     * @throws InvalidDateException
120
     */
121
    public function getAverageSalesForChannelForDates(
122
        ChannelInterface $channel,
123
        \DateTimeInterface $from,
124
        ?\DateTimeInterface $to = null
125
    ): array {
126
        return $this->getSalesForChannelForDates($channel, $from, $to, 'order_id');
127
    }
128
129
    /**
130
     * Get sales per product variant for channel between 2 date times
131
     *
132
     * @param ChannelInterface $channel
133
     * @param \DateTimeInterface $from
134
     * @param \DateTimeInterface|null $to
135
     * @return array
136
     * @throws InvalidDateException
137
     */
138
    public function getProductVariantSalesForChannelForDates(
139
        ChannelInterface $channel,
140
        \DateTimeInterface $from,
141
        ?\DateTimeInterface $to = null
142
    ): array {
143
        $to = $to ?? $from; // If to is null, take the same day as from to make report on one day
144
        try {
145
            $from = new \DateTime($from->format("Y-m-d")." 00:00:00");
146
            $to   = new \DateTime($to->format("Y-m-d")." 23:59:59");
147
        } catch (\Exception $e) {
148
            throw new InvalidDateException('Invalid date given to report.');
149
        }
150
151
        $this->results = [];
152
153
        // Order Item Units values
154
        $this->addResultsByElement(
155
            $this->getOrderItemUnitValues($channel, $from, $to), 'variant_id', 'variant_name'
156
        );
157
        // Order Items values
158
        $this->addResultsByElement(
159
            $this->getOrderItemValues($channel, $from, $to), 'variant_id', 'variant_name'
160
        );
161
162
        return $this->results;
163
    }
164
165
    /**
166
     * Get sales per product option for channel between 2 date times
167
     *
168
     * @param ChannelInterface $channel
169
     * @param \DateTimeInterface $from
170
     * @param \DateTimeInterface|null $to
171
     * @return array
172
     * @throws InvalidDateException
173
     * @throws MissingLocaleException
174
     */
175
    public function getProductOptionSalesForChannelForDates(
176
        ChannelInterface $channel,
177
        \DateTimeInterface $from,
178
        ?\DateTimeInterface $to = null
179
    ): array {
180
        $to = $to ?? $from; // If to is null, take the same day as from to make report on one day
181
        try {
182
            $from = new \DateTime($from->format("Y-m-d")." 00:00:00");
183
            $to   = new \DateTime($to->format("Y-m-d")." 23:59:59");
184
        } catch (\Exception $e) {
185
            throw new InvalidDateException('Invalid date given to report.');
186
        }
187
188
        $this->results = [];
189
190
        // Order Item Units values
191
        $this->addResultsByElement(
192
            $this->getOrderItemUnitValues($channel, $from, $to), 'variant_id', 'variant_name'
193
        );
194
        // Order Items values
195
        $this->addResultsByElement(
196
            $this->getOrderItemValues($channel, $from, $to), 'variant_id', 'variant_name'
197
        );
198
199
        // Populate array with options values data
200
        if (!($locale = $channel->getDefaultLocale()) || (!$localeCode = $locale->getCode())) {
201
            throw new MissingLocaleException('Missing default locale for channel');
202
        }
203
        $resultsWithOptions = $this->populateOptions($localeCode);
204
205
        // Reinit results to generate a new one
206
        $this->results = [];
207
208
        $this->addResultsByElement($resultsWithOptions, 'option_code', 'option_label');
209
210
        return $this->results;
211
    }
212
213
    /**
214
     * Get sales per product option for channel between 2 date times
215
     *
216
     * @param ChannelInterface $channel
217
     * @param \DateTimeInterface $from
218
     * @param \DateTimeInterface|null $to
219
     * @return array
220
     * @throws InvalidDateException
221
     * @throws MissingLocaleException
222
     */
223
    public function getProductOptionValueSalesForChannelForDates(
224
        ChannelInterface $channel,
225
        \DateTimeInterface $from,
226
        ?\DateTimeInterface $to = null
227
    ): array {
228
        $to = $to ?? $from; // If to is null, take the same day as from to make report on one day
229
        try {
230
            $from = new \DateTime($from->format("Y-m-d")." 00:00:00");
231
            $to   = new \DateTime($to->format("Y-m-d")." 23:59:59");
232
        } catch (\Exception $e) {
233
            throw new InvalidDateException('Invalid date given to report.');
234
        }
235
236
        $this->results = [];
237
238
        // Order Item Units values
239
        $this->addResultsByElement(
240
            $this->getOrderItemUnitValues($channel, $from, $to), 'variant_id', 'variant_name'
241
        );
242
        // Order Items values
243
        $this->addResultsByElement(
244
            $this->getOrderItemValues($channel, $from, $to), 'variant_id', 'variant_name'
245
        );
246
247
        // Populate array with options values data
248
        if (!($locale = $channel->getDefaultLocale()) || (!$localeCode = $locale->getCode())) {
249
            throw new MissingLocaleException('Missing default locale for channel');
250
        }
251
        $resultsWithOptions = $this->populateOptions($localeCode);
252
253
        // Reinit results to generate a new one
254
        $this->results = [];
255
256
        $this->addResultsByElement($resultsWithOptions, 'option_value_code', 'option_value_label', ['option_code', 'option_label']);
257
258
        return $this->results;
259
    }
260
261
    /**
262
     * Get sales per product for channel between 2 date times
263
     *
264
     * @param ChannelInterface $channel
265
     * @param \DateTimeInterface $from
266
     * @param \DateTimeInterface|null $to
267
     * @return array
268
     * @throws InvalidDateException
269
     */
270
    public function getProductSalesForChannelForDates(
271
        ChannelInterface $channel,
272
        \DateTimeInterface $from,
273
        ?\DateTimeInterface $to = null
274
    ): array {
275
        $to = $to ?? $from; // If to is null, take the same day as from to make report on one day
276
        try {
277
            $from = new \DateTime($from->format("Y-m-d")." 00:00:00");
278
            $to   = new \DateTime($to->format("Y-m-d")." 23:59:59");
279
        } catch (\Exception $e) {
280
            throw new InvalidDateException('Invalid date given to report.');
281
        }
282
283
        $this->results = [];
284
285
        // Order Item Units values
286
        $this->addResultsByElement(
287
            $this->getOrderItemUnitValues($channel, $from, $to), 'product_id', 'product_name'
288
        );
289
        // Order Items values
290
        $this->addResultsByElement(
291
            $this->getOrderItemValues($channel, $from, $to), 'product_id', 'product_name'
292
        );
293
294
        return $this->results;
295
    }
296
297
    /**
298
     * Generate results for order item units
299
     *
300
     * @param ChannelInterface $channel
301
     * @param \DateTimeInterface $from
302
     * @param \DateTimeInterface $to
303
     * @return array
304
     */
305
    protected function getOrderItemUnitValues(
306
        ChannelInterface $channel,
307
        \DateTimeInterface $from,
308
        \DateTimeInterface $to
309
    ): array {
310
        $queryBuilder = $this->orderRepository->createQueryBuilder('o')
0 ignored issues
show
Bug introduced by
The method createQueryBuilder() does not exist on Sylius\Component\Core\Re...rderRepositoryInterface. Did you maybe mean createListQueryBuilder()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

310
        $queryBuilder = $this->orderRepository->/** @scrutinizer ignore-call */ createQueryBuilder('o')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
311
            ->select($this->getSelectColumns(true, false, false))
312
            ->leftJoin('o.items', 'item')
313
            ->leftJoin('item.variant', 'variant')
314
            ->leftJoin('item.units', 'element')
315
        ;
316
        $queryBuilder = $this->appendAdjustmentsAndParameters($queryBuilder, $channel, $from, $to);
317
        return $queryBuilder->getQuery()->getArrayResult();
318
    }
319
320
    /**
321
     * Generate results for order items
322
     *
323
     * @param ChannelInterface $channel
324
     * @param \DateTimeInterface $from
325
     * @param \DateTimeInterface $to
326
     * @return array
327
     */
328
    protected function getOrderItemValues(
329
        ChannelInterface $channel,
330
        \DateTimeInterface $from,
331
        \DateTimeInterface $to
332
    ): array {
333
        $queryBuilder = $this->orderRepository->createQueryBuilder('o')
334
            ->select($this->getSelectColumns(false, true, false))
335
            ->leftJoin('o.items', 'element')
336
            ->leftJoin('element.variant', 'variant')
337
        ;
338
        $queryBuilder = $this->appendAdjustmentsAndParameters($queryBuilder, $channel, $from, $to);
339
        return $queryBuilder->getQuery()->getArrayResult();
340
    }
341
342
    /**
343
     * Generate results for orders
344
     *
345
     * @param ChannelInterface $channel
346
     * @param \DateTimeInterface $from
347
     * @param \DateTimeInterface $to
348
     * @return array
349
     */
350
    protected function getOrderValues(
351
        ChannelInterface $channel,
352
        \DateTimeInterface $from,
353
        \DateTimeInterface $to
354
    ): array {
355
        $queryBuilder = $this->orderRepository->createQueryBuilder('o')->select($this->getSelectColumns(false, false, true));
356
        $queryBuilder = $this->appendAdjustmentsAndParameters($queryBuilder, $channel, $from, $to, true);
357
        return $queryBuilder->getQuery()->getArrayResult();
358
    }
359
360
    /**
361
     * Retrieve columns for select.
362
     * Column order_id is used to generate average report
363
     * Column without_tax is for unit price without tax in item units
364
     * Columns without_tax_promo, without_tax_shipping, tax columns are respectively for promotions, shipping, tax amounts
365
     * Columns item and total are respectively for total for items total for orders (With shipping etc.)
366
     *
367
     * @param bool $isItemUnit
368
     * @param bool $isItem
369
     * @param bool $isOrder
370
     * @return string
371
     */
372
    protected function getSelectColumns(bool $isItemUnit = false, bool $isItem = false, bool $isOrder = false): string
373
    {
374
        return implode(',',[
375
            // Order ID
376
            'o.id as order_id',
377
378
            // Product infos
379
            ($isItemUnit ? 'IDENTITY(variant.product) as product_id' : ($isItem ? 'IDENTITY(variant.product) as product_id' : '\'\' as product_id')),
380
            ($isItemUnit ? 'item.productName as product_name' : ($isItem ? 'element.productName as product_name' : '\'\' as product_name')),
381
382
            // Variant infos
383
            ($isItemUnit ? 'IDENTITY(item.variant) as variant_id' : ($isItem ? 'IDENTITY(element.variant) as variant_id' : '\'\' as variant_id')),
384
            ($isItemUnit ? 'CONCAT(item.productName, \' \' ,item.variantName) as variant_name' : ($isItem ? 'CONCAT(element.productName, \' \' , element.variantName) as variant_name' : '\'\' as variant_name')),
385
386
            // Adjustments
387
            $isItemUnit ? 'item.unitPrice - COALESCE(tax_adjustment.amount, 0) as without_tax' : '0 as without_tax', // Only retrieve without_tax price for item units
388
            '(COALESCE(order_promotion_adjustment.amount, 0) + COALESCE(order_item_promotion_adjustment.amount, 0) + COALESCE(order_shipping_promotion_adjustment.amount, 0) + COALESCE(order_unit_promotion_adjustment.amount, 0)) AS without_tax_promo',
389
            'shipping_adjustment.amount as without_tax_shipping',
390
            'tax_adjustment.amount as tax',
391
392
            // Totals
393
            $isOrder ? 'o.total as total' : '0 as total',
394
            $isItem ? 'element.total as item_row' : '0 as item_row'
395
        ]);
396
    }
397
398
    /**
399
     * Make joins with all adjustments, add conditions and set parameters to query
400
     *
401
     * @param QueryBuilder $queryBuilder
402
     * @param ChannelInterface $channel
403
     * @param \DateTimeInterface $from
404
     * @param \DateTimeInterface $to
405
     * @param bool $isOrder
406
     * @return mixed
407
     */
408
    protected function appendAdjustmentsAndParameters(
409
        QueryBuilder $queryBuilder,
410
        ChannelInterface $channel,
411
        \DateTimeInterface $from,
412
        \DateTimeInterface $to,
413
        bool $isOrder = false
414
    ) {
415
        $elementAlias = $isOrder ? 'o' : 'element';
416
        return $queryBuilder
417
            // Adjustments joins
418
            ->leftJoin($elementAlias . '.adjustments', 'tax_adjustment', 'WITH', 'tax_adjustment.type = :tax_type')
419
            ->leftJoin($elementAlias . '.adjustments', 'shipping_adjustment', 'WITH', 'shipping_adjustment.type = :shipping_type')
420
            ->leftJoin($elementAlias . '.adjustments', 'order_promotion_adjustment', 'WITH', 'order_promotion_adjustment.type = :order_promotion_type OR order_promotion_adjustment.type = :admin_order_promotion_type')
421
            ->leftJoin($elementAlias . '.adjustments', 'order_item_promotion_adjustment', 'WITH', 'order_item_promotion_adjustment.type = :order_item_promotion_type OR order_item_promotion_adjustment.type = :admin_order_item_promotion_type')
422
            ->leftJoin($elementAlias . '.adjustments', 'order_shipping_promotion_adjustment', 'WITH', 'order_shipping_promotion_adjustment.type = :order_shipping_promotion_type')
423
            ->leftJoin($elementAlias . '.adjustments', 'order_unit_promotion_adjustment', 'WITH', 'order_unit_promotion_adjustment.type = :order_unit_promotion_type')
424
            // Adjustments parameters
425
            ->setParameter('tax_type', AdjustmentInterface::TAX_ADJUSTMENT)
426
            ->setParameter('shipping_type', AdjustmentInterface::SHIPPING_ADJUSTMENT)
427
            ->setParameter('order_promotion_type', AdjustmentInterface::ORDER_PROMOTION_ADJUSTMENT)
428
            ->setParameter('admin_order_promotion_type', self::ADMIN_ORDER_DISCOUNT_ADJUSTMENT)
429
            ->setParameter('order_item_promotion_type', AdjustmentInterface::ORDER_ITEM_PROMOTION_ADJUSTMENT)
430
            ->setParameter('admin_order_item_promotion_type', self::ADMIN_ORDER_ITEM_DISCOUNT_ADJUSTMENT)
431
            ->setParameter('order_shipping_promotion_type', AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT)
432
            ->setParameter('order_unit_promotion_type', AdjustmentInterface::ORDER_UNIT_PROMOTION_ADJUSTMENT)
433
            // Filters on orders in channel, which are paid, not refunded and completed between the wanted dates
434
            ->andWhere('o.channel = :channel')
435
            ->andWhere('o.state = :state')
436
            ->andWhere('o.paymentState != :payment_state')
437
            ->andWhere('o.checkoutCompletedAt BETWEEN :from AND :to')
438
            // Filters parameters
439
            ->setParameter('channel', $channel)
440
            ->setParameter('state', OrderInterface::STATE_FULFILLED)
441
            ->setParameter('payment_state', PaymentInterface::STATE_REFUNDED)
442
            ->setParameter('from', $from)
443
            ->setParameter('to', $to)
444
        ;
445
    }
446
447
    /**
448
     * Populate result array with options and option values data
449
     *
450
     * @param string $localeCode
451
     * @return array
452
     */
453
    protected function populateOptions(string $localeCode): array
454
    {
455
        $variantOptions = $this->getVariantsOptions($localeCode);
456
        $salesResults = [];
457
458
        foreach($this->results as $result) {
459
            $variantId = $result['variant_id'];
460
            $options = $variantOptions[$variantId];
461
462
            // Rename field with _total
463
            foreach ($result as $key => $value) {
464
                if (strpos($key, '_total')) {
465
                    $result[str_replace('_total', '', $key)] = $value;
466
                    unset($result[$key]);
467
                }
468
            }
469
            foreach ($options as $optionCode => $option) {
470
                $result['option_code'] = $optionCode;
471
                $result['option_label'] = $option['label'];
472
                $result['option_value_code'] = $option['value_code'];
473
                $result['option_value_label'] = $option['value_label'];
474
                $salesResults[] = $result;
475
            }
476
        }
477
478
        return $salesResults;
479
    }
480
481
    /**
482
     * Retrieve options for all variants and build an array
483
     *
484
     * @param string $localeCode
485
     * @return array
486
     */
487
    protected function getVariantsOptions(string $localeCode): array
488
    {
489
        $queryBuilder = $this->productVariantRepository->createQueryBuilder('v')
0 ignored issues
show
Bug introduced by
The method createQueryBuilder() does not exist on Sylius\Component\Core\Re...iantRepositoryInterface. Did you maybe mean createQueryBuilderByProductId()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

489
        $queryBuilder = $this->productVariantRepository->/** @scrutinizer ignore-call */ createQueryBuilder('v')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
490
            ->select('v.id AS variant_id, option.code AS option_code, option_translation.name AS option_label, option_value.code AS option_value_code, option_value_translation.value AS option_value_label')
491
            ->leftJoin('v.optionValues', 'option_value')
492
            ->leftJoin('option_value.translations', 'option_value_translation', 'WITH', 'option_value_translation.locale = :locale')
493
            ->leftJoin('option_value.option', 'option')
494
            ->leftJoin('option.translations', 'option_translation', 'WITH', 'option_translation.locale = :locale')
495
            ->setParameter('locale', $localeCode)
496
        ;
497
498
        $variantOptionsValues = [];
499
500
        $result = $queryBuilder->getQuery()->getArrayResult();
501
        foreach ($result as $variantOptionValue) {
502
            $variantOptionsValues[$variantOptionValue['variant_id']][$variantOptionValue['option_code']] = [
503
                'label' => $variantOptionValue['option_label'],
504
                'value_code' => $variantOptionValue['option_value_code'],
505
                'value_label' => $variantOptionValue['option_value_label'],
506
            ];
507
        }
508
509
        return $variantOptionsValues;
510
    }
511
512
    /**
513
     * Init the result with 0 totals
514
     */
515
    protected function initResult()
516
    {
517
        $this->result = [
518
            'without_tax_total' => 0,
519
            'without_tax_promo_total' => 0,
520
            'without_tax_shipping_total' => 0,
521
            'tax_total' => 0,
522
            'total' => 0,
523
            'item_row_total' => 0,
524
        ];
525
        $this->elements = [];
526
    }
527
528
    /**
529
     * Increment results with given array
530
     *
531
     * @param array $elementResults
532
     * @param string|null $groupField
533
     */
534
    protected function addResults(array $elementResults, ?string $groupField = null): void
535
    {
536
        // Loop on given elements to increments current result
537
        foreach ($elementResults as $elementResult) {
538
            foreach ($this->result as $key => $val) {
539
                if (strpos($key, 'total') !== false) {
540
                    // Get the field key, for example `without_tax_shipping` if we need to increment `without_tax_shipping_total`
541
                    $resultKey = str_replace('_total', '', $key);
542
                    if (isset($elementResult[$resultKey])) {
543
                        $this->result[$key] += (int) $elementResult[$resultKey]; // Cast in int because doctrine return string for columns with `+`, and we can have null values
544
                    }
545
                }
546
            }
547
            // Add group field value if we got one, for example, an order ID to have an average per order or a list per product variant
548
            if ($groupField !== null) {
549
                $this->elements[$elementResult[$groupField]] = $elementResult[$groupField];
550
            }
551
        }
552
    }
553
554
    /**
555
     * Make the sum of results by elements
556
     *
557
     * @param array $elementResults
558
     * @param string $groupField
559
     * @param string|null $labelField
560
     * @param array|null $extraFields
561
     */
562
    protected function addResultsByElement(array $elementResults, string $groupField, ?string $labelField = null, ?array $extraFields = null): void
563
    {
564
        // Loop on given elements to increments current result
565
        foreach ($elementResults as $elementResult) {
566
            $elementId = $elementResult[$groupField];
567
            // Init results for this element or retrieve existing one
568
            if (!isset($this->results[$elementId])) {
569
                $this->initResult();
570
            } else {
571
                $this->result = $this->results[$elementId];
572
                $this->elements = $this->elementsArray[$elementId];
573
            }
574
            // Add results by order
575
            $this->addResults([$elementResult], 'order_id');
576
            $this->result['order_id'] = $elementResult['order_id'];
577
578
            // Add extra fields
579
            $this->result[$groupField] = $elementId; // Grouped field ID
580
            if ($labelField && isset($elementResult[$labelField]) && !empty($elementResult[$labelField])) {
581
                $this->result[$labelField] = $elementResult[$labelField]; // Grouped field label if given
582
            } elseif($labelField && (!isset($elementResult[$labelField]) || empty($elementResult[$labelField]))) {
583
                $this->result[$labelField] = '';
584
            }
585
            if (!empty($extraFields)) {
586
                foreach ($extraFields as $extraField) {
587
                    if (isset($elementResult[$extraField])) {
588
                        $this->result[$extraField] = $elementResult[$extraField];
589
                    } else {
590
                        $this->result[$extraField] = '';
591
                    }
592
                }
593
            }
594
595
            // Update results for this element
596
            $this->results[$elementId] = $this->result;
597
            $this->elementsArray[$elementId] = $this->elements;
598
        }
599
600
        // Aggregate number of order per element
601
        foreach ($this->results as $key => $value) {
602
            $this->results[$key]['number_of_elements'] = count($this->elementsArray[$key]);
603
        }
604
    }
605
606
    /**
607
     * Make the average of results depending on number of elements
608
     */
609
    protected function averageResult(): void
610
    {
611
        if (!empty($this->elements)) {
612
            $numberOfElements = count($this->elements);
613
            foreach ($this->result as $key => $val) {
614
                $this->result[$key] = round($this->result[$key] / $numberOfElements);
615
            }
616
            $this->result['number_of_elements'] = count($this->elements);
617
        } else {
618
            $this->result['number_of_elements'] = 0;
619
        }
620
    }
621
}
622