OpportunityRepository   C
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 715
Duplicated Lines 16.78 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 65
lcom 1
cbo 12
dl 120
loc 715
rs 5
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getOpportunitiesByStatus() 0 7 1
B getGroupedOpportunitiesByStatusQB() 0 36 1
C getOpportunitiesDataByStatus() 5 42 7
A getForecastOfOpportunitiesData() 0 8 2
B getForecastOfOpportunitiesCurrentData() 0 43 4
D getForecastOfOpportunitiesOldData() 0 42 9
A getHistoryOldValue() 0 13 4
A isStatusOk() 0 10 2
A isOwnerOk() 0 11 2
A calculateOpportunityOldValue() 0 11 2
A getOpportunitiesCount() 10 10 1
A getNewOpportunitiesCount() 10 10 1
A createOpportunitiesCountQb() 21 21 4
B getTotalServicePipelineAmount() 0 32 4
B getTotalServicePipelineAmountInProgress() 0 26 3
A getWeightedPipelineAmount() 18 18 3
A getOpenWeightedPipelineAmount() 23 23 2
A getNewOpportunitiesAmount() 0 19 2
A getWonOpportunitiesToDateCount() 19 19 2
A getWonOpportunitiesToDateAmount() 0 21 2
A setCreationPeriod() 7 7 1
A setClosedPeriod() 7 7 1
A getOpportunitiesCountGroupByLeadSource() 0 11 1
A getOpportunitiesGroupByLeadSourceQueryBuilder() 0 19 2
A getForecastQB() 0 19 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OpportunityRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OpportunityRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Oro\Bundle\SalesBundle\Entity\Repository;
4
5
use Doctrine\ORM\EntityRepository;
6
use Doctrine\ORM\QueryBuilder;
7
8
use Oro\Bundle\DashboardBundle\Filter\DateFilterProcessor;
9
use Oro\Bundle\DataAuditBundle\Entity\AbstractAudit;
10
use Oro\Bundle\CurrencyBundle\Query\CurrencyQueryBuilderTransformerInterface;
11
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper;
12
use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper;
13
use Oro\Component\DoctrineUtils\ORM\QueryUtils;
14
use Oro\Bundle\SalesBundle\Entity\Opportunity;
15
16
/**
17
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
18
 */
19
class OpportunityRepository extends EntityRepository
20
{
21
    const OPPORTUNITY_STATE_IN_PROGRESS      = 'In Progress';
22
    const OPPORTUNITY_STATE_IN_PROGRESS_CODE = 'in_progress';
23
    const OPPORTUNITY_STATUS_CLOSED_WON_CODE = 'won';
24
25
    /**
26
     * Get opportunities by state by current quarter
27
     *
28
     * @param           $aclHelper AclHelper
29
     * @param  array    $dateRange
30
     * @param  array    $states
31
     * @param int[]     $owners
32
     *
33
     * @param AclHelper $aclHelper
34
     * @param array     $dateRange
35
     * @param array     $states
36
     * @param int[]     $owners
37
     *
38
     * @return array
39
     */
40
    public function getOpportunitiesByStatus(AclHelper $aclHelper, $dateRange, $states, $owners = [])
41
    {
42
        $dateEnd   = $dateRange['end'];
43
        $dateStart = $dateRange['start'];
44
45
        return $this->getOpportunitiesDataByStatus($aclHelper, $dateStart, $dateEnd, $states, $owners);
46
    }
47
48
    /**
49
     * @param string $alias
50
     * @param CurrencyQueryBuilderTransformerInterface $qbTransformer
51
     * @param string $orderBy
52
     * @param string $direction
53
     *
54
     * @return QueryBuilder
55
     */
56
    public function getGroupedOpportunitiesByStatusQB(
57
        $alias,
58
        CurrencyQueryBuilderTransformerInterface $qbTransformer,
59
        $orderBy = 'budget',
60
        $direction = 'DESC'
61
    ) {
62
        $statusClass = ExtendHelper::buildEnumValueClassName('opportunity_status');
63
        $repository  = $this->getEntityManager()->getRepository($statusClass);
64
65
        $qb = $repository->createQueryBuilder('s');
66
        $closeRevenueQuery = $qbTransformer->getTransformSelectQuery('closeRevenue', $qb, $alias);
67
        $budgetAmountQuery = $qbTransformer->getTransformSelectQuery('budgetAmount', $qb, $alias);
68
        $qb->select(
69
            's.name as label',
70
            sprintf('COUNT(%s.id) as quantity', $alias),
71
            // Use close revenue for calculating budget for opportunities with won statuses
72
                sprintf(
73
                    'SUM(
74
                        CASE WHEN s.id = \'won\'
75
                            THEN
76
                                (CASE WHEN %1$s.closeRevenueValue IS NOT NULL THEN (%2$s) ELSE 0 END)
77
                            ELSE
78
                                (CASE WHEN %1$s.budgetAmountValue IS NOT NULL THEN (%3$s) ELSE 0 END)
79
                        END
80
                    ) as budget',
81
                    $alias,
82
                    $closeRevenueQuery,
83
                    $budgetAmountQuery
84
                )
85
        )
86
        ->leftJoin('OroSalesBundle:Opportunity', $alias, 'WITH', sprintf('%s.status = s', $alias))
87
        ->groupBy('s.name')
88
        ->orderBy($orderBy, $direction);
89
90
        return $qb;
91
    }
92
93
    /**
94
     * @param  AclHelper $aclHelper
95
     * @param            $dateStart
96
     * @param            $dateEnd
97
     * @param array      $states
98
     * @param int[]      $owners
99
     *
100
     * @return array
101
     */
102
    protected function getOpportunitiesDataByStatus(
103
        AclHelper $aclHelper,
104
        $dateStart = null,
105
        $dateEnd = null,
106
        $states = [],
107
        $owners = []
108
    ) {
109
        foreach ($states as $key => $name) {
110
            $resultData[$key] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$resultData was never initialized. Although not strictly required by PHP, it is generally a good practice to add $resultData = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
111
                'name'   => $key,
112
                'label'  => $name,
113
                'budget' => 0,
114
            ];
115
        }
116
117
        // select opportunity data
118
        $qb = $this->createQueryBuilder('opportunity');
119
        $qb->select('IDENTITY(opportunity.status) as name, SUM(opportunity.budgetAmountValue) as budget')
120
            ->groupBy('opportunity.status');
121
122 View Code Duplication
        if ($dateStart && $dateEnd) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
            $qb->where($qb->expr()->between('opportunity.createdAt', ':dateFrom', ':dateTo'))
124
                ->setParameter('dateFrom', $dateStart)
125
                ->setParameter('dateTo', $dateEnd);
126
        }
127
128
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
129
            QueryUtils::applyOptimizedIn($qb, 'opportunity.owner', $owners);
130
        }
131
132
        $groupedData = $aclHelper->apply($qb)->getArrayResult();
133
134
        foreach ($groupedData as $statusData) {
135
            $status = $statusData['name'];
136
            $budget = (float)$statusData['budget'];
137
            if ($budget) {
138
                $resultData[$status]['budget'] = $budget;
0 ignored issues
show
Bug introduced by
The variable $resultData does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
139
            }
140
        }
141
142
        return $resultData;
143
    }
144
145
    /**
146
     * @param array       $ownerIds
147
     * @param \DateTime    $date
148
     * @param AclHelper   $aclHelper
149
     *
150
     * @param string|null $start
151
     * @param string|null $end
152
     *
153
     * @return mixed
154
     */
155
    public function getForecastOfOpportunitiesData($ownerIds, $date, AclHelper $aclHelper, $start = null, $end = null)
156
    {
157
        if ($date === null) {
158
            return $this->getForecastOfOpportunitiesCurrentData($ownerIds, $aclHelper, $start, $end);
159
        }
160
161
        return $this->getForecastOfOpportunitiesOldData($ownerIds, $date, $aclHelper);
162
    }
163
164
    /**
165
     * @param array       $ownerIds
166
     * @param AclHelper   $aclHelper
167
     * @param string|null $start
168
     * @param string|null $end
169
     *
170
     * @return mixed
171
     */
172
    protected function getForecastOfOpportunitiesCurrentData(
173
        $ownerIds,
174
        AclHelper $aclHelper,
175
        $start = null,
176
        $end = null
177
    ) {
178
        $qb = $this->createQueryBuilder('opportunity');
179
180
        $select = "
181
            COUNT( opportunity.id ) as inProgressCount,
182
            SUM( opportunity.budgetAmount ) as budgetAmount,
183
            SUM( opportunity.budgetAmount * opportunity.probability ) as weightedForecast";
184
        $qb
185
            ->select($select)
186
            ->andWhere('opportunity.status NOT IN (:notCountedStatuses)')
187
            ->setParameter('notCountedStatuses', ['lost', 'won']);
188
        if (!empty($ownerIds)) {
189
            $qb->join('opportunity.owner', 'owner');
190
            QueryUtils::applyOptimizedIn($qb, 'owner.id', $ownerIds);
191
        }
192
193
        $probabilityCondition = $qb->expr()->orX(
194
            $qb->expr()->andX(
195
                'opportunity.probability <> 0',
196
                'opportunity.probability <> 1'
197
            ),
198
            'opportunity.probability is NULL'
199
        );
200
201
        $qb->andWhere($probabilityCondition);
202
        if ($start) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $start of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
203
            $qb
204
                ->andWhere('opportunity.closeDate >= :startDate')
205
                ->setParameter('startDate', $start);
206
        }
207
        if ($end) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $end of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
208
            $qb
209
                ->andWhere('opportunity.closeDate <= :endDate')
210
                ->setParameter('endDate', $end);
211
        }
212
213
        return $aclHelper->apply($qb)->getOneOrNullResult();
214
    }
215
216
    /**
217
     * @param array     $ownerIds
218
     * @param \DateTime $date
219
     * @param AclHelper $aclHelper
220
     *
221
     * @return mixed
222
     */
223
    protected function getForecastOfOpportunitiesOldData($ownerIds, $date, AclHelper $aclHelper)
224
    {
225
        //clone date for avoiding wrong date on printing with current locale
226
        $newDate = clone $date;
227
        $qb      = $this->createQueryBuilder('opportunity')
228
            ->where('opportunity.createdAt < :date')
229
            ->setParameter('date', $newDate);
230
231
        $opportunities = $aclHelper->apply($qb)->getResult();
232
233
        $result['inProgressCount']  = 0;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
234
        $result['budgetAmount']     = 0;
235
        $result['weightedForecast'] = 0;
236
237
        $auditRepository = $this->getEntityManager()->getRepository('OroDataAuditBundle:Audit');
238
        /** @var Opportunity $opportunity */
239
        foreach ($opportunities as $opportunity) {
240
            $auditQb = $auditRepository->getLogEntriesQueryBuilder($opportunity);
241
            $auditQb->andWhere('a.action = :action')
242
                ->andWhere('a.loggedAt > :date')
243
                ->setParameter('action', AbstractAudit::ACTION_UPDATE)
244
                ->setParameter('date', $newDate);
245
            $opportunityHistory = $aclHelper->apply($auditQb)->getResult();
246
247
            if ($oldProbability = $this->getHistoryOldValue($opportunityHistory, 'probability')) {
248
                $isProbabilityOk = $oldProbability !== 0 && $oldProbability !== 1;
249
                $probability     = $oldProbability;
250
            } else {
251
                $probability     = $opportunity->getProbability();
252
                $isProbabilityOk = !is_null($probability) && $probability !== 0 && $probability !== 1;
253
            }
254
255
            if ($isProbabilityOk
256
                && $this->isOwnerOk($ownerIds, $opportunityHistory, $opportunity)
257
                && $this->isStatusOk($opportunityHistory, $opportunity)
258
            ) {
259
                $result = $this->calculateOpportunityOldValue($result, $opportunityHistory, $opportunity, $probability);
260
            }
261
        }
262
263
        return $result;
264
    }
265
266
    /**
267
     * @param mixed  $opportunityHistory
268
     * @param string $field
269
     *
270
     * @return mixed
271
     */
272
    protected function getHistoryOldValue($opportunityHistory, $field)
273
    {
274
        $result = null;
275
276
        $opportunityHistory = is_array($opportunityHistory) ? $opportunityHistory : [$opportunityHistory];
277
        foreach ($opportunityHistory as $item) {
278
            if ($item->getField($field)) {
279
                $result = $item->getField($field)->getOldValue();
280
            }
281
        }
282
283
        return $result;
284
    }
285
286
    /**
287
     * @param array       $opportunityHistory
288
     * @param Opportunity $opportunity
289
     *
290
     * @return bool
291
     */
292
    protected function isStatusOk($opportunityHistory, $opportunity)
293
    {
294
        if ($oldStatus = $this->getHistoryOldValue($opportunityHistory, 'status')) {
295
            $isStatusOk = $oldStatus === self::OPPORTUNITY_STATE_IN_PROGRESS;
296
        } else {
297
            $isStatusOk = $opportunity->getStatus()->getName() === self::OPPORTUNITY_STATE_IN_PROGRESS_CODE;
298
        }
299
300
        return $isStatusOk;
301
    }
302
303
    /**
304
     * @param array       $ownerIds
305
     * @param array       $opportunityHistory
306
     * @param Opportunity $opportunity
307
     *
308
     * @return bool
309
     */
310
    protected function isOwnerOk($ownerIds, $opportunityHistory, $opportunity)
311
    {
312
        $userRepository = $this->getEntityManager()->getRepository('OroUserBundle:User');
313
        if ($oldOwner = $this->getHistoryOldValue($opportunityHistory, 'owner')) {
314
            $isOwnerOk = in_array($userRepository->findOneByUsername($oldOwner)->getId(), $ownerIds);
315
        } else {
316
            $isOwnerOk = in_array($opportunity->getOwner()->getId(), $ownerIds);
317
        }
318
319
        return $isOwnerOk;
320
    }
321
322
    /**
323
     * @param array       $result
324
     * @param array       $opportunityHistory
325
     * @param Opportunity $opportunity
326
     * @param mixed       $probability
327
     *
328
     * @return array
329
     */
330
    protected function calculateOpportunityOldValue($result, $opportunityHistory, $opportunity, $probability)
331
    {
332
        ++$result['inProgressCount'];
333
        $oldBudgetAmount = $this->getHistoryOldValue($opportunityHistory, 'budgetAmount');
334
335
        $budget = $oldBudgetAmount !== null ? $oldBudgetAmount : $opportunity->getBudgetAmount();
336
        $result['budgetAmount'] += $budget;
337
        $result['weightedForecast'] += $budget * $probability;
338
339
        return $result;
340
    }
341
342
    /**
343
     * @param AclHelper $aclHelper
344
     * @param \DateTime  $start
345
     * @param \DateTime  $end
346
     * @param int[]     $owners
347
     *
348
     * @return int
349
     */
350 View Code Duplication
    public function getOpportunitiesCount(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
        AclHelper $aclHelper,
352
        \DateTime $start = null,
353
        \DateTime $end = null,
354
        $owners = []
355
    ) {
356
        $qb = $this->createOpportunitiesCountQb($start, $end, $owners);
357
358
        return $aclHelper->apply($qb)->getSingleScalarResult();
359
    }
360
361
    /**
362
     * @param AclHelper $aclHelper
363
     * @param \DateTime  $start
364
     * @param \DateTime  $end
365
     * @param int[]     $owners
366
     *
367
     * @return int
368
     */
369 View Code Duplication
    public function getNewOpportunitiesCount(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
370
        AclHelper $aclHelper,
371
        \DateTime $start = null,
372
        \DateTime $end = null,
373
        $owners = []
374
    ) {
375
        $qb = $this->createOpportunitiesCountQb($start, $end, $owners);
376
377
        return $aclHelper->apply($qb)->getSingleScalarResult();
378
    }
379
380
    /**
381
     * @param \DateTime $start
382
     * @param \DateTime $end
383
     * @param int[]    $owners
384
     *
385
     * @return QueryBuilder
386
     */
387 View Code Duplication
    public function createOpportunitiesCountQb(\DateTime $start = null, \DateTime $end = null, $owners = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
388
    {
389
        $qb = $this->createQueryBuilder('o');
390
        $qb->select('COUNT(o.id)');
391
        if ($start) {
392
            $qb
393
                ->andWhere('o.createdAt > :start')
394
                ->setParameter('start', $start);
395
        }
396
        if ($end) {
397
            $qb
398
                ->andWhere('o.createdAt < :end')
399
                ->setParameter('end', $end);
400
        }
401
402
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
403
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
404
        }
405
406
        return $qb;
407
    }
408
409
    /**
410
     * @param AclHelper $aclHelper
411
     * @param \DateTime  $start
412
     * @param \DateTime  $end
413
     * @param int[]     $owners
414
     *
415
     * @return double
416
     */
417
    public function getTotalServicePipelineAmount(
418
        AclHelper $aclHelper,
419
        \DateTime $start = null,
420
        \DateTime $end = null,
421
        $owners = []
422
    ) {
423
        $qb = $this->createQueryBuilder('o');
424
425
        $qb
426
            ->select('SUM(o.budgetAmount)')
427
            ->andWhere('o.closeDate IS NULL')
428
            ->andWhere('o.status = :status')
429
            ->andWhere('o.probability != 0')
430
            ->andWhere('o.probability != 1')
431
            ->setParameter('status', self::OPPORTUNITY_STATE_IN_PROGRESS_CODE);
432
        if ($start) {
433
            $qb
434
                ->andWhere('o.createdAt > :start')
435
                ->setParameter('start', $start);
436
        }
437
        if ($end) {
438
            $qb
439
                ->andWhere('o.createdAt < :end')
440
                ->setParameter('end', $end);
441
        }
442
443
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
444
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
445
        }
446
447
        return $aclHelper->apply($qb)->getSingleScalarResult();
448
    }
449
450
    /**
451
     * @param AclHelper $aclHelper
452
     * @param \DateTime  $start
453
     * @param \DateTime  $end
454
     *
455
     * @return double
456
     */
457
    public function getTotalServicePipelineAmountInProgress(
458
        AclHelper $aclHelper,
459
        \DateTime $start = null,
460
        \DateTime $end = null
461
    ) {
462
        $qb = $this->createQueryBuilder('o');
463
464
        $qb
465
            ->select('SUM(o.budgetAmount)')
466
            ->andWhere('o.status = :status')
467
            ->andWhere('o.probability != 0')
468
            ->andWhere('o.probability != 1')
469
            ->setParameter('status', self::OPPORTUNITY_STATE_IN_PROGRESS_CODE);
470
        if ($start) {
471
            $qb
472
                ->andWhere('o.createdAt > :start')
473
                ->setParameter('start', $start);
474
        }
475
        if ($end) {
476
            $qb
477
                ->andWhere('o.createdAt < :end')
478
                ->setParameter('end', $end);
479
        }
480
481
        return $aclHelper->apply($qb)->getSingleScalarResult();
482
    }
483
484
    /**
485
     * @param AclHelper $aclHelper
486
     * @param \DateTime  $start
487
     * @param \DateTime  $end
488
     *
489
     * @return double
490
     */
491 View Code Duplication
    public function getWeightedPipelineAmount(AclHelper $aclHelper, \DateTime $start = null, \DateTime $end = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
492
    {
493
        $qb = $this->createQueryBuilder('o');
494
495
        $qb->select('SUM(o.budgetAmount * o.probability)');
496
        if ($start) {
497
            $qb
498
                ->andWhere('o.createdAt > :start')
499
                ->setParameter('start', $start);
500
        }
501
        if ($end) {
502
            $qb
503
                ->andWhere('o.createdAt < :end')
504
                ->setParameter('end', $end);
505
        }
506
507
        return $aclHelper->apply($qb)->getSingleScalarResult();
508
    }
509
510
    /**
511
     * @param AclHelper $aclHelper
512
     * @param \DateTime  $start
513
     * @param \DateTime  $end
514
     * @param int[]     $owners
515
     *
516
     * @return double
517
     */
518 View Code Duplication
    public function getOpenWeightedPipelineAmount(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
519
        AclHelper $aclHelper,
520
        \DateTime $start = null,
521
        \DateTime $end = null,
522
        $owners = []
523
    ) {
524
        $qb = $this->createQueryBuilder('o');
525
526
        $qb
527
            ->select('SUM(o.budgetAmount * o.probability)')
528
            ->andWhere('o.status = :status')
529
            ->andWhere('o.probability != 0')
530
            ->andWhere('o.probability != 1')
531
            ->setParameter('status', self::OPPORTUNITY_STATE_IN_PROGRESS_CODE);
532
533
        $this->setCreationPeriod($qb, $start, $end);
534
535
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
536
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
537
        }
538
539
        return $aclHelper->apply($qb)->getSingleScalarResult();
540
    }
541
542
    /**
543
     * @param AclHelper $aclHelper
544
     * @param CurrencyQueryBuilderTransformerInterface $qbTransformer
545
     * @param \DateTime  $start
546
     * @param \DateTime  $end
547
     * @param int[]     $owners
548
     *
549
     * @return double
550
     */
551
    public function getNewOpportunitiesAmount(
552
        AclHelper $aclHelper,
553
        CurrencyQueryBuilderTransformerInterface $qbTransformer,
554
        \DateTime $start = null,
555
        \DateTime $end = null,
556
        $owners = []
557
    ) {
558
        $qb = $this->createQueryBuilder('o');
559
        $baTransformedQuery = $qbTransformer->getTransformSelectQuery('budgetAmount', $qb);
560
        $qb->select(sprintf('SUM(%s)', $baTransformedQuery));
561
562
        $this->setCreationPeriod($qb, $start, $end);
563
564
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
565
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
566
        }
567
568
        return $aclHelper->apply($qb)->getSingleScalarResult();
569
    }
570
571
    /**
572
     * @param AclHelper $aclHelper
573
     * @param \DateTime  $start
574
     * @param \DateTime  $end
575
     * @param int[]     $owners
576
     *
577
     * @return int
578
     */
579 View Code Duplication
    public function getWonOpportunitiesToDateCount(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
580
        AclHelper $aclHelper,
581
        \DateTime $start = null,
582
        \DateTime $end = null,
583
        $owners = []
584
    ) {
585
        $qb = $this->createQueryBuilder('o');
586
        $qb->select('COUNT(o.id)')
587
            ->andWhere('o.status = :status')
588
            ->setParameter('status', self::OPPORTUNITY_STATUS_CLOSED_WON_CODE);
589
590
        $this->setClosedPeriod($qb, $start, $end);
591
592
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
593
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
594
        }
595
596
        return $aclHelper->apply($qb)->getSingleScalarResult();
597
    }
598
599
    /**
600
     * @param AclHelper $aclHelper
601
     * @param CurrencyQueryBuilderTransformerInterface $qbTransformer
602
     * @param \DateTime  $start
603
     * @param \DateTime  $end
604
     * @param int[]     $owners
605
     *
606
     * @return double
607
     */
608
    public function getWonOpportunitiesToDateAmount(
609
        AclHelper $aclHelper,
610
        CurrencyQueryBuilderTransformerInterface $qbTransformer,
611
        \DateTime $start = null,
612
        \DateTime $end = null,
613
        $owners = []
614
    ) {
615
        $qb = $this->createQueryBuilder('o');
616
        $crTransformedQuery = $qbTransformer->getTransformSelectQuery('closeRevenue', $qb);
617
        $qb->select(sprintf('SUM(%s)', $crTransformedQuery))
618
            ->andWhere('o.status = :status')
619
            ->setParameter('status', self::OPPORTUNITY_STATUS_CLOSED_WON_CODE);
620
621
        $this->setClosedPeriod($qb, $start, $end);
622
623
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
624
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
625
        }
626
627
        return $aclHelper->apply($qb)->getSingleScalarResult();
628
    }
629
630
    /**
631
     * @param QueryBuilder $qb
632
     * @param \DateTime|null $start
633
     * @param \DateTime|null $end
634
     */
635 View Code Duplication
    protected function setCreationPeriod(QueryBuilder $qb, \DateTime $start = null, \DateTime $end = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
636
    {
637
        $qb
638
            ->andWhere($qb->expr()->between('o.createdAt', ':dateStart', ':dateEnd'))
639
            ->setParameter('dateStart', $start)
640
            ->setParameter('dateEnd', $end);
641
    }
642
643
    /**
644
     * @param QueryBuilder $qb
645
     * @param \DateTime|null $start
646
     * @param \DateTime|null $end
647
     */
648 View Code Duplication
    protected function setClosedPeriod(QueryBuilder $qb, \DateTime $start = null, \DateTime $end = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
649
    {
650
        $qb
651
            ->andWhere($qb->expr()->between('o.closeDate', ':dateStart', ':dateEnd'))
652
            ->setParameter('dateStart', $start)
653
            ->setParameter('dateEnd', $end);
654
    }
655
656
    /**
657
     * Returns count of opportunities grouped by lead source
658
     *
659
     * @param AclHelper $aclHelper
660
     * @param DateFilterProcessor $dateFilterProcessor
661
     * @param array $dateRange
662
     * @param array $owners
663
     *
664
     * @return array [value, source]
665
     */
666
    public function getOpportunitiesCountGroupByLeadSource(
667
        AclHelper $aclHelper,
668
        DateFilterProcessor $dateFilterProcessor,
669
        array $dateRange = [],
670
        array $owners = []
671
    ) {
672
        $qb = $this->getOpportunitiesGroupByLeadSourceQueryBuilder($dateFilterProcessor, $dateRange, $owners);
673
        $qb->addSelect('count(o.id) as value');
674
675
        return $aclHelper->apply($qb)->getArrayResult();
676
    }
677
678
    /**
679
     * Returns opportunities QB grouped by lead source filtered by $dateRange and $owners
680
     *
681
     * @param DateFilterProcessor $dateFilterProcessor
682
     * @param array $dateRange
683
     * @param array $owners
684
     *
685
     * @return QueryBuilder
686
     */
687
    public function getOpportunitiesGroupByLeadSourceQueryBuilder(
688
        DateFilterProcessor $dateFilterProcessor,
689
        array $dateRange = [],
690
        array $owners = []
691
    ) {
692
        $qb = $this->createQueryBuilder('o')
693
            ->select('s.id as source')
694
            ->leftJoin('o.lead', 'l')
695
            ->leftJoin('l.source', 's')
696
            ->groupBy('source');
697
698
        $dateFilterProcessor->process($qb, $dateRange, 'o.createdAt');
699
700
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
701
            QueryUtils::applyOptimizedIn($qb, 'o.owner', $owners);
702
        }
703
704
        return $qb;
705
    }
706
707
    /**
708
     * @param string $alias
709
     * @param CurrencyQueryBuilderTransformerInterface $qbTransformer
710
     * @param array $excludedStatuses
711
     *
712
     * @return QueryBuilder
713
     */
714
    public function getForecastQB(
715
        CurrencyQueryBuilderTransformerInterface $qbTransformer,
716
        $alias = 'o',
717
        array $excludedStatuses = ['lost', 'won']
718
    ) {
719
        $qb     = $this->createQueryBuilder($alias);
720
        $baBaseCurrencyQuery = $qbTransformer->getTransformSelectQuery('budgetAmount', $qb, $alias);
721
        $qb->select([
722
            sprintf('COUNT(%s.id) as inProgressCount', $alias),
723
            sprintf('SUM(%s) as budgetAmount', $baBaseCurrencyQuery),
724
            sprintf('SUM((%s) * %s.probability) as weightedForecast', $baBaseCurrencyQuery, $alias)
725
        ]);
726
727
        if ($excludedStatuses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $excludedStatuses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
728
            $qb->andWhere($qb->expr()->notIn(sprintf('%s.status', $alias), $excludedStatuses));
729
        }
730
731
        return $qb;
732
    }
733
}
734