Completed
Push — 1.9 ( 30c3e5...ca8b96 )
by
unknown
42:25
created

CartRepository::getFunnelChartData()   D

Complexity

Conditions 10
Paths 25

Size

Total Lines 45
Code Lines 29

Duplication

Lines 21
Ratio 46.67 %

Importance

Changes 0
Metric Value
dl 21
loc 45
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 29
nc 25
nop 4

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace OroCRM\Bundle\MagentoBundle\Entity\Repository;
4
5
use Doctrine\ORM\NoResultException;
6
7
use Oro\Bundle\IntegrationBundle\Entity\Channel;
8
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper;
9
use Oro\Bundle\WorkflowBundle\Model\Workflow;
10
use Oro\Bundle\BatchBundle\ORM\Query\BufferedQueryResultIterator;
11
12
use OroCRM\Bundle\MagentoBundle\Entity\Cart;
13
use OroCRM\Bundle\MagentoBundle\Entity\CartStatus;
14
15
class CartRepository extends ChannelAwareEntityRepository
16
{
17
    /**
18
     * @var array
19
     */
20
    protected $excludedSteps = [
21
        'converted_to_opportunity',
22
        'abandoned'
23
    ];
24
25
    /**
26
     * @var array
27
     */
28
    protected $excludedStatuses = [
29
        CartStatus::STATUS_PURCHASED,
30
        CartStatus::STATUS_EXPIRED
31
    ];
32
33
    /**
34
     * @param \DateTime $dateFrom
35
     * @param \DateTime $dateTo
36
     * @param Workflow  $workflow
37
     * @param AclHelper $aclHelper
38
     *
39
     * @return array
40
     */
41
    public function getFunnelChartData(
42
        \DateTime $dateFrom,
43
        \DateTime $dateTo,
44
        Workflow $workflow = null,
45
        AclHelper $aclHelper = null
46
    ) {
47
        if (!$workflow) {
48
            return ['items' => [], 'nozzleSteps' => []];
49
        }
50
51
        $steps = $workflow->getStepManager()->getOrderedSteps();
52
53
        // regular and final steps should be calculated separately
54
        $regularSteps = [];
55
        $finalSteps   = [];
56 View Code Duplication
        foreach ($steps as $step) {
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...
57
            if (!in_array($step->getName(), $this->excludedSteps)) {
58
                if ($step->isFinal()) {
59
                    $finalSteps[] = $step->getName();
60
                } else {
61
                    $regularSteps[] = $step->getName();
62
                }
63
            }
64
        }
65
66
        // regular steps should be calculated for whole period, final steps - for specified period
67
        $regularStepsData = $this->getStepData($regularSteps, null, null, $aclHelper);
68
        $finalStepsData   = $this->getStepData($finalSteps, $dateFrom, $dateTo, $aclHelper);
69
70
        // final calculation
71
        $data = [];
72 View Code Duplication
        foreach ($steps as $step) {
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...
73
            $stepName = $step->getName();
74
            if (!in_array($stepName, $this->excludedSteps)) {
75
                if ($step->isFinal()) {
76
                    $stepValue = isset($finalStepsData[$stepName]) ? $finalStepsData[$stepName] : 0;
77
                    $data[]    = ['label' => $step->getLabel(), 'value' => $stepValue, 'isNozzle' => true];
78
                } else {
79
                    $stepValue = isset($regularStepsData[$stepName]) ? $regularStepsData[$stepName] : 0;
80
                    $data[]    = ['label' => $step->getLabel(), 'value' => $stepValue, 'isNozzle' => false];
81
                }
82
            }
83
        }
84
        return $data;
85
    }
86
87
    /**
88
     * @param array     $steps
89
     * @param AclHelper $aclHelper
90
     * @param \DateTime $dateFrom
91
     * @param \DateTime $dateTo
92
     *
93
     * @return array
94
     */
95
    protected function getStepData(
96
        array $steps,
97
        \DateTime $dateFrom = null,
98
        \DateTime $dateTo = null,
99
        AclHelper $aclHelper = null
100
    ) {
101
        $stepData = [];
102
103
        if (!$steps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $steps 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...
104
            return $stepData;
105
        }
106
107
        $queryBuilder = $this->createQueryBuilder('cart')
108
            ->select('workflowStep.name as workflowStepName', 'SUM(cart.grandTotal) as total')
109
            ->leftJoin('cart.status', 'status')
110
            ->join('cart.workflowStep', 'workflowStep')
111
            ->groupBy('workflowStep.name');
112
113
        $queryBuilder->where($queryBuilder->expr()->in('workflowStep.name', $steps));
114
115 View Code Duplication
        if ($dateFrom && $dateTo) {
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...
116
            $queryBuilder->andWhere($queryBuilder->expr()->between('cart.createdAt', ':dateFrom', ':dateTo'))
117
                ->setParameter('dateFrom', $dateFrom)
118
                ->setParameter('dateTo', $dateTo);
119
        }
120
121
        if ($this->excludedStatuses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->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...
122
            $queryBuilder->andWhere($queryBuilder->expr()->notIn('status.name', $this->excludedStatuses));
123
        }
124
125
        $this->applyActiveChannelLimitation($queryBuilder);
126
127
        if ($aclHelper) {
128
            $query = $aclHelper->apply($queryBuilder);
129
        } else {
130
            $query = $queryBuilder->getQuery();
131
        }
132
133
        foreach ($query->getArrayResult() as $record) {
134
            $stepData[$record['workflowStepName']] = $record['total'] ? (float)$record['total'] : 0;
135
        }
136
137
        return $stepData;
138
    }
139
140
    /**
141
     * Update statuses for carts to 'expired'
142
     *
143
     * @param array $ids
144
     */
145
    public function markExpired(array $ids)
146
    {
147
        $em = $this->getEntityManager();
148
        foreach ($ids as $id) {
149
            /** @var Cart $cart */
150
            $cart = $em->getReference($this->getEntityName(), $id);
151
            $cart->setStatus($em->getReference('OroCRMMagentoBundle:CartStatus', 'expired'));
0 ignored issues
show
Documentation introduced by
$em->getReference('OroCR...CartStatus', 'expired') is of type object|null, but the function expects a object<OroCRM\Bundle\Mag...ndle\Entity\CartStatus>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
152
        }
153
154
        $em->flush();
155
    }
156
157
    /**
158
     * Returns iterator for fetching IDs pairs by channel and given status
159
     * Each item in iteration will be array with following data:
160
     * [
161
     *      'id'        => ENTITY_ID,
162
     *      'originId'  => ENTITY_ORIGIN_ID
163
     * ]
164
     *
165
     * @param Channel $channel
166
     * @param string  $status
167
     *
168
     * @return \Doctrine\ORM\QueryBuilder
169
     */
170
    public function getCartsByChannelIdsIterator(Channel $channel, $status = 'open')
171
    {
172
        $qb = $this->createQueryBuilder('c')
173
            ->select('c.id, c.originId')
174
            ->leftJoin('c.status', 'cstatus')
175
            ->andWhere('c.channel = :channel')
176
            ->andWhere('cstatus.name = :statusName')
177
            ->setParameter('channel', $channel)
178
            ->setParameter('statusName', $status);
179
180
        return new BufferedQueryResultIterator($qb);
181
    }
182
183
    /**
184
     * @param \DateTime $start
185
     * @param \DateTime $end
186
     * @param AclHelper $aclHelper
187
     * @return int
188
     * @throws \Doctrine\ORM\NonUniqueResultException
189
     */
190 View Code Duplication
    public function getAbandonedRevenueByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper)
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...
191
    {
192
        $qb = $this->getAbandonedQB($start, $end);
193
        $qb->select('SUM(cart.grandTotal) as val');
194
        $this->applyActiveChannelLimitation($qb);
195
        $value = $aclHelper->apply($qb)->getOneOrNullResult();
196
197
        return $value['val'] ? : 0;
198
    }
199
200
    /**
201
     * @param \DateTime $start
202
     * @param \DateTime $end
203
     * @param AclHelper $aclHelper
204
     * @return int
205
     * @throws \Doctrine\ORM\NonUniqueResultException
206
     */
207 View Code Duplication
    public function getAbandonedCountByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper)
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...
208
    {
209
        $qb = $this->getAbandonedQB($start, $end);
210
        $qb->select('COUNT(cart.grandTotal) as val');
211
        $this->applyActiveChannelLimitation($qb);
212
        $value = $aclHelper->apply($qb)->getOneOrNullResult();
213
214
        return $value['val'] ? : 0;
215
    }
216
217
    /**
218
     * @param \DateTime $start
219
     * @param \DateTime $end
220
     * @param AclHelper $aclHelper
221
     * @return float|null
222
     * @throws \Doctrine\ORM\NonUniqueResultException
223
     */
224
    public function getAbandonRateByPeriod(\DateTime $start, \DateTime $end, AclHelper $aclHelper)
225
    {
226
        $result = null;
227
228
        $qb = $this->createQueryBuilder('cart');
229
        $qb->join('cart.status', 'cstatus')
230
            ->select('SUM(cart.grandTotal) as val')
231
            ->andWhere('cstatus.name = :statusName')
232
            ->setParameter('statusName', 'open')
233
            ->andWhere($qb->expr()->between('cart.createdAt', ':dateStart', ':dateEnd'))
234
            ->setParameter('dateStart', $start)
235
            ->setParameter('dateEnd', $end);
236
        $this->applyActiveChannelLimitation($qb);
237
        $allCards = $aclHelper->apply($qb)->getOneOrNullResult();
238
        $allCards = (int)$allCards['val'];
239
240
        if (0 !== $allCards) {
241
            $abandonedCartsCount = $this->getAbandonedCountByPeriod($start, $end, $aclHelper);
242
243
            $result = $abandonedCartsCount / $allCards;
244
        }
245
246
        return $result;
247
    }
248
249
    protected function getAbandonedQB(\DateTime $start, \DateTime $end)
250
    {
251
        $qb = $this->createQueryBuilder('cart');
252
        return $qb ->join('cart.status', 'cstatus')
253
            ->andWhere('cstatus.name = :statusName')
254
            ->setParameter('statusName', 'open')
255
            ->andWhere($qb->expr()->between('cart.createdAt', ':dateStart', ':dateEnd'))
256
            ->setParameter('dateStart', $start)
257
            ->setParameter('dateEnd', $end)
258
            ->andWhere(
259
                $qb->expr()->not(
260
                    $qb->expr()->exists(
261
                        $this->_em->getRepository('OroCRMMagentoBundle:Order')
262
                            ->createQueryBuilder('mOrder')
263
                            ->where('mOrder.cart = cart')
264
                    )
265
                )
266
            );
267
    }
268
269
    /**
270
     * @param AclHelper $aclHelper
271
     * @param \DateTime $from
272
     * @param \DateTime $to
273
     *
274
     * @return int
275
     */
276 View Code Duplication
    public function getCustomersCountWhatMakeCarts(AclHelper $aclHelper, \DateTime $from, \DateTime $to)
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...
277
    {
278
        $qb = $this->createQueryBuilder('c');
279
280
        try {
281
            $qb
282
                ->select('COUNT(DISTINCT c.customer) + SUM(CASE WHEN c.isGuest = true THEN 1 ELSE 0 END)')
283
                ->andWhere($qb->expr()->between('c.createdAt', ':from', ':to'))
284
                ->setParameters([
285
                    'from' => $from,
286
                    'to'   => $to
287
                ]);
288
            $this->applyActiveChannelLimitation($qb);
289
290
            return (int) $aclHelper->apply($qb)->getSingleScalarResult();
291
        } catch (NoResultException $ex) {
292
            return 0;
293
        }
294
    }
295
}
296