1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace OroCRM\Bundle\MagentoBundle\Entity\Repository; |
4
|
|
|
|
5
|
|
|
use Doctrine\ORM\EntityManager; |
6
|
|
|
use Doctrine\ORM\NoResultException; |
7
|
|
|
use Doctrine\ORM\QueryBuilder; |
8
|
|
|
|
9
|
|
|
use Oro\Bundle\DashboardBundle\Helper\DateHelper; |
10
|
|
|
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper; |
11
|
|
|
use Oro\Bundle\EntityBundle\Exception\InvalidEntityException; |
12
|
|
|
|
13
|
|
|
use OroCRM\Bundle\MagentoBundle\Entity\Cart; |
14
|
|
|
use OroCRM\Bundle\MagentoBundle\Entity\Customer; |
15
|
|
|
use OroCRM\Bundle\MagentoBundle\Provider\ChannelType; |
16
|
|
|
|
17
|
|
|
class OrderRepository extends ChannelAwareEntityRepository |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* @param \DateTime $start |
21
|
|
|
* @param \DateTime $end |
22
|
|
|
* @param AclHelper $aclHelper |
23
|
|
|
* @return int |
24
|
|
|
*/ |
25
|
|
View Code Duplication |
public function getRevenueValueByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper) |
|
|
|
|
26
|
|
|
{ |
27
|
|
|
$select = 'SUM( |
28
|
|
|
CASE WHEN orders.subtotalAmount IS NOT NULL THEN orders.subtotalAmount ELSE 0 END - |
29
|
|
|
CASE WHEN orders.discountAmount IS NOT NULL THEN ABS(orders.discountAmount) ELSE 0 END |
30
|
|
|
) as val'; |
31
|
|
|
$qb = $this->createQueryBuilder('orders'); |
32
|
|
|
$qb->select($select) |
33
|
|
|
->andWhere($qb->expr()->between('orders.createdAt', ':dateStart', ':dateEnd')) |
34
|
|
|
->setParameter('dateStart', $start) |
35
|
|
|
->setParameter('dateEnd', $end); |
36
|
|
|
$this->applyActiveChannelLimitation($qb); |
37
|
|
|
|
38
|
|
|
$value = $aclHelper->apply($qb)->getOneOrNullResult(); |
39
|
|
|
|
40
|
|
|
return $value['val'] ? : 0; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param \DateTime $start |
45
|
|
|
* @param \DateTime $end |
46
|
|
|
* @param AclHelper $aclHelper |
47
|
|
|
* @return int |
48
|
|
|
*/ |
49
|
|
View Code Duplication |
public function getOrdersNumberValueByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper) |
|
|
|
|
50
|
|
|
{ |
51
|
|
|
$qb = $this->createQueryBuilder('o'); |
52
|
|
|
$qb->select('count(o.id) as val') |
53
|
|
|
->andWhere($qb->expr()->between('o.createdAt', ':dateStart', ':dateEnd')) |
54
|
|
|
->setParameter('dateStart', $start) |
55
|
|
|
->setParameter('dateEnd', $end); |
56
|
|
|
$this->applyActiveChannelLimitation($qb); |
57
|
|
|
|
58
|
|
|
$value = $aclHelper->apply($qb)->getOneOrNullResult(); |
59
|
|
|
|
60
|
|
|
return $value['val'] ? : 0; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* get Average Order Amount by given period |
65
|
|
|
* |
66
|
|
|
* @param \DateTime $start |
67
|
|
|
* @param \DateTime $end |
68
|
|
|
* @param AclHelper $aclHelper |
69
|
|
|
* @return int |
70
|
|
|
*/ |
71
|
|
View Code Duplication |
public function getAOVValueByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper) |
|
|
|
|
72
|
|
|
{ |
73
|
|
|
$select = 'SUM( |
74
|
|
|
CASE WHEN o.subtotalAmount IS NOT NULL THEN o.subtotalAmount ELSE 0 END - |
75
|
|
|
CASE WHEN o.discountAmount IS NOT NULL THEN ABS(o.discountAmount) ELSE 0 END |
76
|
|
|
) as revenue, |
77
|
|
|
count(o.id) as ordersCount'; |
78
|
|
|
$qb = $this->createQueryBuilder('o'); |
79
|
|
|
$qb->select($select) |
80
|
|
|
->andWhere($qb->expr()->between('o.createdAt', ':dateStart', ':dateEnd')) |
81
|
|
|
->setParameter('dateStart', $start) |
82
|
|
|
->setParameter('dateEnd', $end); |
83
|
|
|
$this->applyActiveChannelLimitation($qb); |
84
|
|
|
|
85
|
|
|
$value = $aclHelper->apply($qb)->getOneOrNullResult(); |
86
|
|
|
|
87
|
|
|
return $value['revenue'] ? $value['revenue'] / $value['ordersCount'] : 0; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param \DateTime $start |
92
|
|
|
* @param \DateTime $end |
93
|
|
|
* @param AclHelper $aclHelper |
94
|
|
|
* @return float|int |
95
|
|
|
* @throws \Doctrine\ORM\NonUniqueResultException |
96
|
|
|
*/ |
97
|
|
View Code Duplication |
public function getDiscountedOrdersPercentByDatePeriod( |
|
|
|
|
98
|
|
|
\DateTime $start, |
99
|
|
|
\DateTime $end, |
100
|
|
|
AclHelper $aclHelper |
101
|
|
|
) { |
102
|
|
|
$qb = $this->createQueryBuilder('o'); |
103
|
|
|
$qb->select( |
104
|
|
|
'COUNT(o.id) as allOrders', |
105
|
|
|
'SUM(CASE WHEN (o.discountAmount IS NOT NULL AND o.discountAmount <> 0) THEN 1 ELSE 0 END) as discounted' |
106
|
|
|
); |
107
|
|
|
$qb->andWhere($qb->expr()->between('o.createdAt', ':dateStart', ':dateEnd')); |
108
|
|
|
$qb->setParameter('dateStart', $start); |
109
|
|
|
$qb->setParameter('dateEnd', $end); |
110
|
|
|
$this->applyActiveChannelLimitation($qb); |
111
|
|
|
|
112
|
|
|
$value = $aclHelper->apply($qb)->getOneOrNullResult(); |
113
|
|
|
return $value['allOrders'] ? $value['discounted'] / $value['allOrders'] : 0; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @param Cart|Customer $item |
118
|
|
|
* @param string $field |
119
|
|
|
* |
120
|
|
|
* @return Cart|Customer|null $item |
121
|
|
|
* @throws InvalidEntityException |
122
|
|
|
*/ |
123
|
|
|
public function getLastPlacedOrderBy($item, $field) |
124
|
|
|
{ |
125
|
|
|
if (!($item instanceof Cart) && !($item instanceof Customer)) { |
126
|
|
|
throw new InvalidEntityException(); |
127
|
|
|
} |
128
|
|
|
$qb = $this->createQueryBuilder('o'); |
129
|
|
|
$qb->where('o.' . $field . ' = :item'); |
130
|
|
|
$qb->setParameter('item', $item); |
131
|
|
|
$qb->orderBy('o.updatedAt', 'DESC'); |
132
|
|
|
$qb->setMaxResults(1); |
133
|
|
|
$this->applyActiveChannelLimitation($qb); |
134
|
|
|
|
135
|
|
|
return $qb->getQuery()->getOneOrNullResult(); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @param AclHelper $aclHelper |
140
|
|
|
* @param \DateTime $dateFrom |
141
|
|
|
* @param \DateTime $dateTo |
142
|
|
|
* @param DateHelper $dateHelper |
143
|
|
|
* @return array |
144
|
|
|
*/ |
145
|
|
|
public function getAverageOrderAmount( |
146
|
|
|
AclHelper $aclHelper, |
147
|
|
|
\DateTime $dateFrom, |
148
|
|
|
\DateTime $dateTo, |
149
|
|
|
DateHelper $dateHelper |
150
|
|
|
) { |
151
|
|
|
/** @var EntityManager $entityManager */ |
152
|
|
|
$entityManager = $this->getEntityManager(); |
153
|
|
|
$channels = $entityManager->getRepository('OroCRMChannelBundle:Channel') |
154
|
|
|
->getAvailableChannelNames($aclHelper, ChannelType::TYPE); |
155
|
|
|
|
156
|
|
|
// execute data query |
157
|
|
|
$queryBuilder = $this->createQueryBuilder('o'); |
158
|
|
|
$selectClause = ' |
159
|
|
|
IDENTITY(o.dataChannel) AS dataChannelId, |
160
|
|
|
AVG( |
161
|
|
|
CASE WHEN o.subtotalAmount IS NOT NULL THEN o.subtotalAmount ELSE 0 END - |
162
|
|
|
CASE WHEN o.discountAmount IS NOT NULL THEN ABS(o.discountAmount) ELSE 0 END |
163
|
|
|
) as averageOrderAmount'; |
164
|
|
|
|
165
|
|
|
$dates = $dateHelper->getDatePeriod($dateFrom, $dateTo); |
166
|
|
|
|
167
|
|
|
$queryBuilder->select($selectClause) |
168
|
|
|
->andWhere($queryBuilder->expr()->between('o.createdAt', ':dateStart', ':dateEnd')) |
169
|
|
|
->setParameter('dateStart', $dateFrom) |
170
|
|
|
->setParameter('dateEnd', $dateTo) |
171
|
|
|
->groupBy('dataChannelId'); |
172
|
|
|
|
173
|
|
|
$this->applyActiveChannelLimitation($queryBuilder); |
174
|
|
|
$dateHelper->addDatePartsSelect($dateFrom, $dateTo, $queryBuilder, 'o.createdAt'); |
175
|
|
|
$amountStatistics = $aclHelper->apply($queryBuilder)->getArrayResult(); |
176
|
|
|
|
177
|
|
|
$items = []; |
178
|
|
|
foreach ($amountStatistics as $row) { |
179
|
|
|
$key = $dateHelper->getKey($dateFrom, $dateTo, $row); |
180
|
|
|
$channelId = (int)$row['dataChannelId']; |
181
|
|
|
$channelName = $channels[$channelId]['name']; |
182
|
|
|
|
183
|
|
|
if (!isset($items[$channelName])) { |
184
|
|
|
$items[$channelName] = $dates; |
185
|
|
|
} |
186
|
|
|
$items[$channelName][$key]['amount'] = (float)$row['averageOrderAmount']; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
// restore default keys |
190
|
|
|
foreach ($items as $channelName => $item) { |
191
|
|
|
$items[$channelName] = array_values($item); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $items; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param AclHelper $aclHelper , |
199
|
|
|
* @param DateHelper $dateHelper |
200
|
|
|
* @param \DateTime $from |
201
|
|
|
* @param \DateTime|null $to |
202
|
|
|
* |
203
|
|
|
* @return array |
204
|
|
|
*/ |
205
|
|
View Code Duplication |
public function getOrdersOverTime( |
|
|
|
|
206
|
|
|
AclHelper $aclHelper, |
207
|
|
|
DateHelper $dateHelper, |
208
|
|
|
\DateTime $from, |
209
|
|
|
\DateTime $to = null |
210
|
|
|
) { |
211
|
|
|
$from = clone $from; |
212
|
|
|
$to = clone $to; |
213
|
|
|
|
214
|
|
|
$qb = $this->createQueryBuilder('o') |
215
|
|
|
->select('COUNT(o.id) AS cnt'); |
216
|
|
|
|
217
|
|
|
$dateHelper->addDatePartsSelect($from, $to, $qb, 'o.createdAt'); |
218
|
|
|
if ($to) { |
219
|
|
|
$qb->andWhere($qb->expr()->between('o.createdAt', ':from', ':to')) |
220
|
|
|
->setParameter('to', $to); |
221
|
|
|
} else { |
222
|
|
|
$qb->andWhere('o.createdAt > :from'); |
223
|
|
|
} |
224
|
|
|
$qb->setParameter('from', $from); |
225
|
|
|
$this->applyActiveChannelLimitation($qb); |
226
|
|
|
|
227
|
|
|
return $aclHelper->apply($qb)->getResult(); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @param AclHelper $aclHelper |
232
|
|
|
* @param DateHelper $dateHelper |
233
|
|
|
* @param \DateTime $from |
234
|
|
|
* @param \DateTime|null $to |
235
|
|
|
* |
236
|
|
|
* @return array |
237
|
|
|
*/ |
238
|
|
View Code Duplication |
public function getRevenueOverTime( |
|
|
|
|
239
|
|
|
AclHelper $aclHelper, |
240
|
|
|
DateHelper $dateHelper, |
241
|
|
|
\DateTime $from, |
242
|
|
|
\DateTime $to = null |
243
|
|
|
) { |
244
|
|
|
$from = clone $from; |
245
|
|
|
$to = clone $to; |
246
|
|
|
|
247
|
|
|
$qb = $this->createQueryBuilder('o') |
248
|
|
|
->select('SUM( |
249
|
|
|
CASE WHEN o.subtotalAmount IS NOT NULL THEN o.subtotalAmount ELSE 0 END - |
250
|
|
|
CASE WHEN o.discountAmount IS NOT NULL THEN ABS(o.discountAmount) ELSE 0 END |
251
|
|
|
) AS amount'); |
252
|
|
|
|
253
|
|
|
$dateHelper->addDatePartsSelect($from, $to, $qb, 'o.createdAt'); |
254
|
|
|
|
255
|
|
|
if ($to) { |
256
|
|
|
$qb->andWhere($qb->expr()->between('o.createdAt', ':from', ':to')) |
257
|
|
|
->setParameter('to', $to); |
258
|
|
|
} else { |
259
|
|
|
$qb->andWhere('o.createdAt > :from'); |
260
|
|
|
} |
261
|
|
|
$qb->setParameter('from', $from); |
262
|
|
|
$this->applyActiveChannelLimitation($qb); |
263
|
|
|
|
264
|
|
|
return $aclHelper->apply($qb)->getResult(); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* @param AclHelper $aclHelper |
269
|
|
|
* @param \DateTime $from |
270
|
|
|
* @param \DateTime $to |
271
|
|
|
* |
272
|
|
|
* @return int |
273
|
|
|
*/ |
274
|
|
View Code Duplication |
public function getUniqueBuyersCount(AclHelper $aclHelper, \DateTime $from = null, \DateTime $to = null) |
|
|
|
|
275
|
|
|
{ |
276
|
|
|
$qb = $this->createQueryBuilder('o'); |
277
|
|
|
|
278
|
|
|
try { |
279
|
|
|
$qb->select('COUNT(DISTINCT o.customer) + SUM(CASE WHEN o.isGuest = true THEN 1 ELSE 0 END)'); |
280
|
|
|
if ($from) { |
281
|
|
|
$qb |
282
|
|
|
->andWhere('o.createdAt > :from') |
283
|
|
|
->setParameter('from', $from); |
284
|
|
|
} |
285
|
|
|
if ($to) { |
286
|
|
|
$qb |
287
|
|
|
->andWhere('o.createdAt > :to') |
288
|
|
|
->setParameter('to', $to); |
289
|
|
|
} |
290
|
|
|
$this->applyActiveChannelLimitation($qb); |
291
|
|
|
|
292
|
|
|
return (int) $aclHelper->apply($qb)->getSingleScalarResult(); |
293
|
|
|
} catch (NoResultException $ex) { |
294
|
|
|
return 0; |
295
|
|
|
} |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* @param $alias |
300
|
|
|
* |
301
|
|
|
* @return QueryBuilder |
302
|
|
|
*/ |
303
|
|
View Code Duplication |
public function getUniqueCustomersCountQB($alias) |
|
|
|
|
304
|
|
|
{ |
305
|
|
|
$qb = $this->createQueryBuilder($alias) |
306
|
|
|
->select( |
307
|
|
|
sprintf( |
308
|
|
|
'COUNT(DISTINCT %s.customer) + SUM(CASE WHEN %s.isGuest = true THEN 1 ELSE 0 END)', |
309
|
|
|
$alias, |
310
|
|
|
$alias |
311
|
|
|
) |
312
|
|
|
); |
313
|
|
|
$this->applyActiveChannelLimitation($qb); |
314
|
|
|
|
315
|
|
|
return $qb; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* @return QueryBuilder |
320
|
|
|
*/ |
321
|
|
|
public function getRevenueValueQB() |
322
|
|
|
{ |
323
|
|
|
$select = 'SUM( |
324
|
|
|
CASE WHEN orders.subtotalAmount IS NOT NULL THEN orders.subtotalAmount ELSE 0 END - |
325
|
|
|
CASE WHEN orders.discountAmount IS NOT NULL THEN ABS(orders.discountAmount) ELSE 0 END |
326
|
|
|
) as val'; |
327
|
|
|
$qb = $this->createQueryBuilder('orders'); |
328
|
|
|
$qb->select($select); |
329
|
|
|
|
330
|
|
|
$this->applyActiveChannelLimitation($qb); |
331
|
|
|
|
332
|
|
|
return $qb; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @return QueryBuilder |
337
|
|
|
*/ |
338
|
|
|
public function getOrdersNumberValueQB() |
339
|
|
|
{ |
340
|
|
|
$qb = $this->createQueryBuilder('o'); |
341
|
|
|
$qb->select('count(o.id) as val'); |
342
|
|
|
|
343
|
|
|
$this->applyActiveChannelLimitation($qb); |
344
|
|
|
|
345
|
|
|
return $qb; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* @return QueryBuilder |
350
|
|
|
*/ |
351
|
|
|
public function getAOVValueQB() |
352
|
|
|
{ |
353
|
|
|
$select = 'SUM( |
354
|
|
|
CASE WHEN o.subtotalAmount IS NOT NULL THEN o.subtotalAmount ELSE 0 END - |
355
|
|
|
CASE WHEN o.discountAmount IS NOT NULL THEN ABS(o.discountAmount) ELSE 0 END |
356
|
|
|
) as revenue, |
357
|
|
|
count(o.id) as ordersCount'; |
358
|
|
|
$qb = $this->createQueryBuilder('o'); |
359
|
|
|
$qb->select($select); |
360
|
|
|
$this->applyActiveChannelLimitation($qb); |
361
|
|
|
|
362
|
|
|
return $qb; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* @return QueryBuilder |
367
|
|
|
*/ |
368
|
|
|
public function getDiscountedOrdersPercentQB() |
369
|
|
|
{ |
370
|
|
|
$qb = $this->createQueryBuilder('o'); |
371
|
|
|
$qb->select( |
372
|
|
|
'COUNT(o.id) as allOrders', |
373
|
|
|
'SUM(CASE WHEN (o.discountAmount IS NOT NULL AND o.discountAmount <> 0) THEN 1 ELSE 0 END) as discounted' |
374
|
|
|
); |
375
|
|
|
$this->applyActiveChannelLimitation($qb); |
376
|
|
|
|
377
|
|
|
return $qb; |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
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.