Completed
Push — 1.9 ( 482137...30c3e5 )
by
unknown
43:10
created

OpportunityRepository::getOpportunitiesCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 3
1
<?php
2
3
namespace OroCRM\Bundle\SalesBundle\Entity\Repository;
4
5
use DateTime;
6
7
use Doctrine\ORM\EntityRepository;
8
use Doctrine\ORM\QueryBuilder;
9
10
use Oro\Bundle\DataAuditBundle\Loggable\LoggableManager;
11
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper;
12
use Oro\Bundle\WorkflowBundle\Entity\WorkflowStep;
13
use Oro\Component\DoctrineUtils\ORM\QueryUtils;
14
15
use OroCRM\Bundle\SalesBundle\Entity\Opportunity;
16
17
class OpportunityRepository extends EntityRepository
18
{
19
    /**
20
     * @var WorkflowStep[]
21
     */
22
    protected $workflowStepsByName;
23
24
    /**
25
     * Get opportunities by state by current quarter
26
     *
27
     * @param $aclHelper AclHelper
28
     * @param  array     $dateRange
29
     * @return array
30
     */
31
    public function getOpportunitiesByStatus(AclHelper $aclHelper, $dateRange)
32
    {
33
        $dateEnd = $dateRange['end'];
34
        $dateStart = $dateRange['start'];
35
36
        return $this->getOpportunitiesDataByStatus($aclHelper, $dateStart, $dateEnd);
37
    }
38
39
    /**
40
     * @param  AclHelper $aclHelper
41
     * @param $dateStart
42
     * @param $dateEnd
43
     * @return array
44
     */
45
    protected function getOpportunitiesDataByStatus(AclHelper $aclHelper, $dateStart = null, $dateEnd = null)
46
    {
47
        // select statuses
48
        $qb = $this->getEntityManager()->createQueryBuilder();
49
        $qb->select('status.name, status.label')
50
            ->from('OroCRMSalesBundle:OpportunityStatus', 'status')
51
            ->orderBy('status.name', 'ASC');
52
53
        $resultData = array();
54
        $data = $qb->getQuery()->getArrayResult();
55
        foreach ($data as $status) {
56
            $name = $status['name'];
57
            $label = $status['label'];
58
            $resultData[$name] = array(
59
                'name' => $name,
60
                'label' => $label,
61
                'budget' => 0,
62
            );
63
        }
64
65
        // select opportunity data
66
        $qb = $this->createQueryBuilder('opportunity');
67
        $qb->select('IDENTITY(opportunity.status) as name, SUM(opportunity.budgetAmount) as budget')
68
            ->groupBy('opportunity.status');
69
70 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...
71
            $qb->where($qb->expr()->between('opportunity.createdAt', ':dateFrom', ':dateTo'))
72
                ->setParameter('dateFrom', $dateStart)
73
                ->setParameter('dateTo', $dateEnd);
74
        }
75
        $groupedData = $aclHelper->apply($qb)->getArrayResult();
76
77
        foreach ($groupedData as $statusData) {
78
            $status = $statusData['name'];
79
            $budget = (float)$statusData['budget'];
80
            if ($budget) {
81
                $resultData[$status]['budget'] = $budget;
82
            }
83
        }
84
85
        return $resultData;
86
    }
87
88
    /**
89
     * @param array     $ownerIds
90
     * @param DateTime  $date
91
     * @param AclHelper $aclHelper
92
     *
93
     * @return mixed
94
     */
95
    public function getForecastOfOpporunitiesData($ownerIds, $date, AclHelper $aclHelper)
96
    {
97
        if (!$ownerIds) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ownerIds 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...
98
            return [
99
                'inProgressCount' => 0,
100
                'budgetAmount' => 0,
101
                'weightedForecast' => 0,
102
            ];
103
        }
104
105
        if ($date === null) {
106
            return $this->getForecastOfOpporunitiesCurrentData($ownerIds, $aclHelper);
107
        }
108
109
        return $this->getForecastOfOpporunitiesOldData($ownerIds, $date, $aclHelper);
110
    }
111
112
    /**
113
     * @param array $ownerIds
114
     * @param AclHelper $aclHelper
115
     * @return mixed
116
     */
117
    protected function getForecastOfOpporunitiesCurrentData($ownerIds, AclHelper $aclHelper)
118
    {
119
        $qb = $this->createQueryBuilder('opportunity');
120
121
        $select = "
122
            SUM( (CASE WHEN (opportunity.status='in_progress') THEN 1 ELSE 0 END) ) as inProgressCount,
123
            SUM( opportunity.budgetAmount ) as budgetAmount,
124
            SUM( opportunity.budgetAmount * opportunity.probability ) as weightedForecast";
125
        $qb->select($select);
126
127
        if (!empty($ownerIds)) {
128
            $qb->join('opportunity.owner', 'owner');
129
            QueryUtils::applyOptimizedIn($qb, 'owner.id', $ownerIds);
130
        }
131
132
        $probabilityCondition = $qb->expr()->orX(
133
            $qb->expr()->andX(
134
                'opportunity.probability <> 0',
135
                'opportunity.probability <> 1'
136
            ),
137
            'opportunity.probability is NULL'
138
        );
139
140
        $qb->andWhere($probabilityCondition);
141
142
        return $aclHelper->apply($qb)->getOneOrNullResult();
143
    }
144
145
    /**
146
     * @param array     $ownerIds
147
     * @param \DateTime  $date
148
     * @param AclHelper $aclHelper
149
     * @return mixed
150
     */
151
    protected function getForecastOfOpporunitiesOldData($ownerIds, $date, AclHelper $aclHelper)
152
    {
153
        //clone date for avoiding wrong date on printing with current locale
154
        $newDate = clone $date;
155
        $newDate->setTime(23, 59, 59);
156
        $qb = $this->createQueryBuilder('opportunity')
157
            ->where('opportunity.createdAt < :date')
158
            ->setParameter('date', $newDate);
159
160
        $opportunities = $aclHelper->apply($qb)->getResult();
161
162
        $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...
163
        $result['budgetAmount'] = 0;
164
        $result['weightedForecast'] = 0;
165
166
        $auditRepository = $this->getEntityManager()->getRepository('OroDataAuditBundle:Audit');
167
        /** @var Opportunity $opportunity */
168
        foreach ($opportunities as $opportunity) {
169
            $auditQb = $auditRepository->getLogEntriesQueryBuilder($opportunity);
170
            $auditQb->andWhere('a.action = :action')
171
                ->andWhere('a.loggedAt > :date')
172
                ->setParameter('action', LoggableManager::ACTION_UPDATE)
173
                ->setParameter('date', $newDate);
174
            $opportunityHistory =  $aclHelper->apply($auditQb)->getResult();
175
176
            if ($oldProbability = $this->getHistoryOldValue($opportunityHistory, 'probability')) {
177
                $isProbabilityOk = $oldProbability !== 0 && $oldProbability !== 1;
178
                $probability = $oldProbability;
179
            } else {
180
                $probability = $opportunity->getProbability();
181
                $isProbabilityOk = !is_null($probability) && $probability !== 0 && $probability !== 1;
182
            }
183
184
            if ($isProbabilityOk
185
                && $this->isOwnerOk($ownerIds, $opportunityHistory, $opportunity)
186
                && $this->isStatusOk($opportunityHistory, $opportunity)
187
            ) {
188
                $result = $this->calculateOpportunityOldValue($result, $opportunityHistory, $opportunity, $probability);
189
            }
190
        }
191
192
        return $result;
193
    }
194
195
    /**
196
     * @param mixed  $opportunityHistory
197
     * @param string $field
198
     * @return mixed
199
     */
200
    protected function getHistoryOldValue($opportunityHistory, $field)
201
    {
202
        $result = null;
203
204
        $opportunityHistory = is_array($opportunityHistory) ? $opportunityHistory : [$opportunityHistory];
205
        foreach ($opportunityHistory as $item) {
206
            if ($item->getField($field)) {
207
                $result = $item->getField($field)->getOldValue();
208
            }
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * @param array $opportunityHistory
216
     * @param Opportunity $opportunity
217
     * @return bool
218
     */
219
    protected function isStatusOk($opportunityHistory, $opportunity)
220
    {
221
        if ($oldStatus = $this->getHistoryOldValue($opportunityHistory, 'status')) {
222
            $isStatusOk = $oldStatus === 'In Progress';
223
        } else {
224
            $isStatusOk = $opportunity->getStatus()->getName() === 'in_progress';
225
        }
226
227
        return $isStatusOk;
228
    }
229
230
    /**
231
     * @param array $ownerIds
232
     * @param array $opportunityHistory
233
     * @param Opportunity $opportunity
234
     *
235
     * @return bool
236
     */
237
    protected function isOwnerOk($ownerIds, $opportunityHistory, $opportunity)
238
    {
239
        $userRepository = $this->getEntityManager()->getRepository('OroUserBundle:User');
240
        if ($oldOwner = $this->getHistoryOldValue($opportunityHistory, 'owner')) {
241
            $isOwnerOk = in_array($userRepository->findOneByUsername($oldOwner)->getId(), $ownerIds);
242
        } else {
243
            $isOwnerOk = in_array($opportunity->getOwner()->getId(), $ownerIds);
244
        }
245
246
        return $isOwnerOk;
247
    }
248
249
    /**
250
     * @param array $result
251
     * @param array $opportunityHistory
252
     * @param Opportunity $opportunity
253
     * @param mixed $probability
254
     *
255
     * @return array
256
     */
257
    protected function calculateOpportunityOldValue($result, $opportunityHistory, $opportunity, $probability)
258
    {
259
        ++$result['inProgressCount'];
260
        $oldBudgetAmount = $this->getHistoryOldValue($opportunityHistory, 'budgetAmount');
261
262
        $budget = $oldBudgetAmount !== null ? $oldBudgetAmount : $opportunity->getBudgetAmount();
263
        $result['budgetAmount'] += $budget;
264
        $result['weightedForecast'] += $budget * $probability;
265
266
        return $result;
267
    }
268
269
    /**
270
     * @param AclHelper $aclHelper
271
     * @param DateTime $start
272
     * @param DateTime $end
273
     *
274
     * @return int
275
     */
276
    public function getOpportunitiesCount(AclHelper $aclHelper, DateTime $start, DateTime $end)
277
    {
278
        $qb = $this->createOpportunitiesCountQb($start, $end);
279
280
        return $aclHelper->apply($qb)->getSingleScalarResult();
281
    }
282
283
    /**
284
     * @param AclHelper $aclHelper
285
     * @param DateTime $start
286
     * @param DateTime $end
287
     *
288
     * @return int
289
     */
290
    public function getNewOpportunitiesCount(AclHelper $aclHelper, DateTime $start, DateTime $end)
291
    {
292
        $qb = $this->createOpportunitiesCountQb($start, $end)
293
            ->andWhere('o.closeDate IS NULL');
294
295
        return $aclHelper->apply($qb)->getSingleScalarResult();
296
    }
297
298
    /**
299
     * @param DateTime $start
300
     * @param DateTime $end
301
     *
302
     * @return QueryBuilder
303
     */
304 View Code Duplication
    public function createOpportunitiesCountQb(DateTime $start, DateTime $end)
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...
305
    {
306
        $qb = $this->createQueryBuilder('o');
307
308
        $qb
309
            ->select('COUNT(o.id)')
310
            ->andWhere($qb->expr()->between('o.createdAt', ':start', ':end'))
311
            ->setParameter('start', $start)
312
            ->setParameter('end', $end);
313
314
        return $qb;
315
    }
316
317
    /**
318
     * @param AclHelper $aclHelper
319
     * @param DateTime $start
320
     * @param DateTime $end
321
     *
322
     * @return double
323
     */
324 View Code Duplication
    public function getTotalServicePipelineAmount(AclHelper $aclHelper, DateTime $start, DateTime $end)
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...
325
    {
326
        $qb = $this->createQueryBuilder('o');
327
328
        $qb
329
            ->select('SUM(o.budgetAmount)')
330
            ->andWhere($qb->expr()->between('o.createdAt', ':start', ':end'))
331
            ->andWhere('o.closeDate IS NULL')
332
            ->andWhere('o.status = :status')
333
            ->andWhere('o.probability != 0')
334
            ->andWhere('o.probability != 1')
335
            ->setParameter('start', $start)
336
            ->setParameter('end', $end)
337
            ->setParameter('status', 'in_progress');
338
339
        return $aclHelper->apply($qb)->getSingleScalarResult();
340
    }
341
342
    /**
343
     * @param AclHelper $aclHelper
344
     * @param DateTime $start
345
     * @param DateTime $end
346
     *
347
     * @return double
348
     */
349 View Code Duplication
    public function getTotalServicePipelineAmountInProgress(
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...
350
        AclHelper $aclHelper,
351
        DateTime $start,
352
        DateTime $end
353
    ) {
354
        $qb = $this->createQueryBuilder('o');
355
356
        $qb
357
            ->select('SUM(o.budgetAmount)')
358
            ->andWhere($qb->expr()->between('o.createdAt', ':start', ':end'))
359
            ->andWhere('o.status = :status')
360
            ->andWhere('o.probability != 0')
361
            ->andWhere('o.probability != 1')
362
            ->setParameter('start', $start)
363
            ->setParameter('end', $end)
364
            ->setParameter('status', 'in_progress');
365
366
        return $aclHelper->apply($qb)->getSingleScalarResult();
367
    }
368
369
    /**
370
     * @param AclHelper $aclHelper
371
     * @param DateTime $start
372
     * @param DateTime $end
373
     *
374
     * @return double
375
     */
376 View Code Duplication
    public function getWeightedPipelineAmount(AclHelper $aclHelper, DateTime $start, DateTime $end)
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...
377
    {
378
        $qb = $this->createQueryBuilder('o');
379
380
        $qb
381
            ->select('SUM(o.budgetAmount * o.probability)')
382
            ->andWhere($qb->expr()->between('o.createdAt', ':start', ':end'))
383
            ->setParameter('start', $start)
384
            ->setParameter('end', $end);
385
386
        return $aclHelper->apply($qb)->getSingleScalarResult();
387
    }
388
389
    /**
390
     * @param AclHelper $aclHelper
391
     * @param DateTime $start
392
     * @param DateTime $end
393
     *
394
     * @return double
395
     */
396 View Code Duplication
    public function getOpenWeightedPipelineAmount(AclHelper $aclHelper, DateTime $start, DateTime $end)
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...
397
    {
398
        $qb = $this->createQueryBuilder('o');
399
400
        $qb
401
            ->select('SUM(o.budgetAmount * o.probability)')
402
            ->andWhere($qb->expr()->between('o.createdAt', ':start', ':end'))
403
            ->andWhere('o.status = :status')
404
            ->andWhere('o.probability != 0')
405
            ->andWhere('o.probability != 1')
406
            ->setParameter('start', $start)
407
            ->setParameter('end', $end)
408
            ->setParameter('status', 'in_progress');
409
410
        return $aclHelper->apply($qb)->getSingleScalarResult();
411
    }
412
}
413