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) { |
|
|
|
|
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) { |
|
|
|
|
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; |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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( |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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.