Completed
Push — master ( a18731...e2e070 )
by
unknown
99:54 queued 47:45
created

ForecastOfOpportunities::getMoment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 2
1
<?php
2
3
namespace OroCRM\Bundle\SalesBundle\Provider;
4
5
use Symfony\Component\Translation\TranslatorInterface;
6
7
use Oro\Bundle\DashboardBundle\Model\WidgetOptionBag;
8
use Oro\Bundle\DashboardBundle\Provider\Converters\FilterDateRangeConverter;
9
use Oro\Bundle\LocaleBundle\Formatter\DateTimeFormatter;
10
use Oro\Bundle\LocaleBundle\Formatter\NumberFormatter;
11
use Oro\Bundle\DashboardBundle\Helper\DateHelper;
12
use Oro\Bundle\UserBundle\Dashboard\OwnerHelper;
13
14
use OroCRM\Bundle\SalesBundle\Provider\Opportunity\ForecastProvider;
15
16
/**
17
 * Class ForecastOfOpportunities
18
 * @package OroCRM\Bundle\SalesBundle\Provider
19
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
20
 */
21
class ForecastOfOpportunities
22
{
23
    /** @var NumberFormatter */
24
    protected $numberFormatter;
25
26
    /** @var DateTimeFormatter */
27
    protected $dateTimeFormatter;
28
29
    /** @var TranslatorInterface */
30
    protected $translator;
31
32
    /** @var DateHelper */
33
    protected $dateHelper;
34
35
    /** @var ForecastProvider */
36
    protected $provider;
37
38
    /** @var FilterDateRangeConverter */
39
    protected $filterDateRangeConverter;
40
41
    /** @var array */
42
    protected $moments = [];
43
44
    /**
45
     * @param NumberFormatter          $numberFormatter
46
     * @param DateTimeFormatter        $dateTimeFormatter
47
     * @param TranslatorInterface      $translator
48
     * @param DateHelper               $dateHelper
49
     * @param OwnerHelper              $ownerHelper
50
     * @param ForecastProvider         $provider
51
     * @param FilterDateRangeConverter $filterDateRangeConverter
52
     */
53
    public function __construct(
54
        NumberFormatter $numberFormatter,
55
        DateTimeFormatter $dateTimeFormatter,
56
        TranslatorInterface $translator,
57
        DateHelper $dateHelper,
58
        OwnerHelper $ownerHelper,
59
        ForecastProvider $provider,
60
        FilterDateRangeConverter $filterDateRangeConverter
61
    ) {
62
        $this->numberFormatter          = $numberFormatter;
63
        $this->dateTimeFormatter        = $dateTimeFormatter;
64
        $this->translator               = $translator;
65
        $this->dateHelper               = $dateHelper;
66
        $this->ownerHelper              = $ownerHelper;
0 ignored issues
show
Bug introduced by
The property ownerHelper does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
67
        $this->provider                 = $provider;
68
        $this->filterDateRangeConverter = $filterDateRangeConverter;
69
    }
70
71
    /**
72
     * @param WidgetOptionBag $widgetOptions
73
     * @param string          $dataKey
74
     * @param string          $dataType
75
     * @param bool            $lessIsBetter
76
     *
77
     * @return array
78
     */
79
    public function getForecastOfOpportunitiesValues(
80
        WidgetOptionBag $widgetOptions,
81
        $dataKey,
82
        $dataType,
83
        $lessIsBetter = false
84
    ) {
85
        $lessIsBetter = (bool)$lessIsBetter;
86
        $result       = [];
87
88
        $ownerIds        = $this->ownerHelper->getOwnerIds($widgetOptions);
89
        $compareToDate   = $widgetOptions->get('compareToDate');
90
        $usePrevious     = !empty($compareToDate['useDate']);
91
        $dateData        = $this->prepareDateRange($widgetOptions->get('dateRange'), $usePrevious);
92
        $queryFilter     = $widgetOptions->get('queryFilter', []);
93
        $value           = $this->provider
94
            ->getForecastData($ownerIds, $dateData['start'], $dateData['end'], null, $queryFilter);
95
        $result['value'] = $this->formatValue($value[$dataKey], $dataType);
96
        if (!empty($dateData['prev_start'])
97
            && !empty($dateData['prev_end'])
98
            && !empty($dateData['prev_moment'])
99
        ) {
100
            $pastResult              = $this->provider->getForecastData(
101
                $ownerIds,
102
                $dateData['prev_start'],
103
                $dateData['prev_end'],
104
                $dateData['prev_moment'],
105
                $queryFilter
106
            );
107
            $result['deviation']     = $this->translator
108
                ->trans('orocrm.sales.dashboard.forecast_of_opportunities.no_changes');
109
            $result                  = $this->prepareData(
110
                $dataType,
111
                $lessIsBetter,
112
                $pastResult[$dataKey],
113
                $value[$dataKey] - $pastResult[$dataKey],
114
                $result
115
            );
116
            $result['previousRange'] = $this->filterDateRangeConverter->getViewValue([
117
                'start' => $dateData['prev_start'],
118
                'end'   => $dateData['prev_end'],
119
                'type'  => $widgetOptions->get('dateRange')['type']
120
            ]);
121
        }
122
123
        return $result;
124
    }
125
126
    /**
127
     * @param mixed  $value
128
     * @param string $type
129
     * @param bool   $isDeviant
130
     *
131
     * @return string
132
     */
133
    protected function formatValue($value, $type = '', $isDeviant = false)
134
    {
135
        $sign      = null;
136
        $precision = 2;
137
138
        if ($isDeviant) {
139
            if ($value !== 0) {
140
                $sign  = $value > 0 ? '+' : '&minus;';
141
                $value = abs($value);
142
            }
143
            $precision = 0;
144
        }
145
146
        if ($type === 'currency') {
147
            $formattedValue = $this->numberFormatter->formatCurrency($value);
148
        } elseif ($type === 'percent') {
149
            $value          = round(($value) * 100, $precision) / 100;
150
            $formattedValue = $this->numberFormatter->formatPercent($value);
151
        } else {
152
            $formattedValue = $this->numberFormatter->formatDecimal($value);
153
        }
154
155
        if ($sign) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sign 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...
156
            $formattedValue = sprintf('%s%s', $sign, $formattedValue);
157
        }
158
159
        return $formattedValue;
160
    }
161
162
    /**
163
     * @param $dataType
164
     * @param $lessIsBetter
165
     * @param $pastResult
166
     * @param $deviation
167
     * @param $result
168
     *
169
     * @return array
170
     */
171
    protected function prepareData($dataType, $lessIsBetter, $pastResult, $deviation, $result)
172
    {
173
        if ($pastResult != 0 && $dataType !== 'percent') {
174
            if ($deviation != 0) {
175
                $deviationPercent     = $deviation / $pastResult;
176
                $result['deviation']  = sprintf(
177
                    '%s (%s)',
178
                    $this->formatValue($deviation, $dataType, true),
179
                    $this->formatValue($deviationPercent, 'percent', true)
180
                );
181
                $result['isPositive'] = $this->isPositive($lessIsBetter, $deviation);
182
            }
183
        } else {
184
            if (round(($deviation) * 100, 0) != 0) {
185
                $result['deviation']  = $this->formatValue($deviation, $dataType, true);
186
                $result['isPositive'] = $this->isPositive($lessIsBetter, $deviation);
187
            }
188
        }
189
190
        return $result;
191
    }
192
193
    /**
194
     * Get is positive value
195
     *
196
     * @param $lessIsBetter
197
     * @param $deviation
198
     *
199
     * @return bool
200
     */
201
    protected function isPositive($lessIsBetter, $deviation)
202
    {
203
        if (!$lessIsBetter) {
204
            $result = $deviation > 0;
205
        } else {
206
            $result = !($deviation > 0);
207
        }
208
209
        return $result;
210
    }
211
212
    /**
213
     * @param array $dateRange
214
     * @param bool  $usePrevious
215
     *
216
     * @return array
217
     */
218
    protected function prepareDateRange(array $dateRange, $usePrevious)
219
    {
220
        /** @var \DateTime $start */
221
        /** @var \DateTime $end */
222
        $start = $dateRange['start'];
223
        $end   = $dateRange['end'];
224
        $data  = [
225
            'start' => $start,
226
            'end'   => $end
227
        ];
228
        if ($usePrevious
229
            && !empty($dateRange['prev_start'])
230
            && !empty($dateRange['prev_end'])
231
        ) {
232
            $data = array_merge(
233
                $data,
234
                [
235
                    'prev_start'     => $dateRange['prev_start'],
236
                    'prev_end'       => $dateRange['prev_end'],
237
                    'prev_moment'    => $this->getMoment($dateRange, $start)
238
                ]
239
            );
240
        }
241
242
        return $data;
243
    }
244
245
    /**
246
     * @param array     $dateRange
247
     * @param \DateTime $start
248
     *
249
     * @return array
250
     */
251
    protected function getMoment(array $dateRange, \DateTime $start)
252
    {
253
        /** @var \DateTime $prevStart */
254
        /** @var \DateTime $prevEnd */
255
        $prevStart = $dateRange['prev_start'];
256
        $key = md5(serialize([$dateRange['prev_start'], $start]));
257
        if (!isset($this->moments[$key])) {
258
            // current moment
259
            $now        = $this->dateHelper->getCurrentDateTime();
260
            $diff       = $start->diff($now);
261
            $prevMoment = clone $prevStart;
262
            $prevMoment->add($diff);
263
            $this->moments[$key] = $prevMoment;
264
        }
265
266
        return $this->moments[$key];
267
    }
268
}
269