Completed
Push — master ( f5691f...6e3cf0 )
by Andrii
07:25
created

PlanController::actionUpdatePrices()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 0
cts 33
cp 0
rs 8.0835
c 0
b 0
f 0
cc 8
nc 20
nop 2
crap 72
1
<?php
2
3
namespace hipanel\modules\finance\controllers;
4
5
use hipanel\actions\Action;
6
use hipanel\actions\IndexAction;
7
use hipanel\actions\SmartCreateAction;
8
use hipanel\actions\SmartDeleteAction;
9
use hipanel\actions\SmartPerformAction;
10
use hipanel\actions\SmartUpdateAction;
11
use hipanel\actions\ValidateFormAction;
12
use hipanel\actions\ViewAction;
13
use hipanel\base\CrudController;
14
use hipanel\helpers\ArrayHelper;
15
use hipanel\modules\finance\collections\PricesCollection;
16
use hipanel\modules\finance\helpers\PlanInternalsGrouper;
17
use hipanel\modules\finance\helpers\PriceChargesEstimator;
18
use hipanel\modules\finance\helpers\PriceSort;
19
use hipanel\modules\finance\models\factories\PriceModelFactory;
20
use hipanel\modules\finance\models\Plan;
21
use hipanel\modules\server\models\Server;
22
use hipanel\filters\EasyAccessControl;
23
use hipanel\modules\finance\models\Price;
24
use hipanel\modules\finance\models\TargetObject;
25
use hiqdev\hiart\ResponseErrorException;
26
use Yii;
27
use yii\base\Event;
28
use yii\base\Module;
29
use yii\web\NotFoundHttpException;
30
use yii\web\Response;
31
use yii\web\UnprocessableEntityHttpException;
32
33
class PlanController extends CrudController
34
{
35
    /**
36
     * @var PriceModelFactory
37
     */
38
    public $priceModelFactory;
39
40
    /**
41
     * PlanController constructor.
42
     * @param string $id
43
     * @param Module $module
44
     * @param PriceModelFactory $priceModelFactory
45
     * @param array $config
46
     */
47
    public function __construct(string $id, Module $module, PriceModelFactory $priceModelFactory, array $config = [])
48
    {
49
        parent::__construct($id, $module, $config);
50
51
        $this->priceModelFactory = $priceModelFactory;
52
    }
53
54
    public function behaviors()
55
    {
56
        return array_merge(parent::behaviors(), [
57
            [
58
                'class' => EasyAccessControl::class,
59
                'actions' => [
60
                    'create' => 'plan.create',
61
                    'update' => 'plan.update',
62
                    'update-prices' => 'plan.update',
63
                    'templates' => 'plan.create',
64
                    'create-prices' => 'plan.create',
65
                    '*' => 'plan.read',
66
                ],
67
            ],
68
        ]);
69
    }
70
71
    public function actions()
72
    {
73
        return array_merge(parent::actions(), [
74
            'create' => [
75
                'class' => SmartCreateAction::class,
76
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully created'),
77
            ],
78
            'update' => [
79
                'class' => SmartUpdateAction::class,
80
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully updated'),
81
            ],
82
            'index' => [
83
                'class' => IndexAction::class,
84
            ],
85
            'view' => [
86
                'class' => ViewAction::class,
87
                'on beforePerform' => function (Event $event) {
88
                    $action = $event->sender;
89
                    $action->getDataProvider()->query
90
                        ->joinWith('sales')
91
                        ->andWhere(['state' => ['ok', 'deleted']])
92
                        ->withPrices();
93
                },
94
                'data' => function (Action $action, array $data) {
95
                    $object_ids = [];
96
                    foreach ($data['models'] as $key => $model) {
97
                        foreach ($model->prices as $price) {
98
                            $object_ids[$price->object_id] = $price->object_id;
99
                        }
100
                    }
101
                    if ($object_ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object_ids 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...
102
                        $rawServers = Server::find()->withHardwareSettings()->andWhere(['ids' => implode(',', $object_ids)])->all();
103
                        $servers = [];
104
                        foreach ($rawServers as $server) {
105
                            $servers[$server->id] = $server;
106
                        }
107
                        foreach ($data['models'] as $key => &$model) {
108
                            foreach ($model->prices as $price) {
109
                                $server_id = $price->object_id;
110
                                if (isset($servers[$server_id])) {
111
                                    $model->servers[$server_id] = $servers[$server_id];
112
                                }
113
                            }
114
                        }
115
                    }
116
117
                    return array_merge($data, [
118
                        'grouper' => new PlanInternalsGrouper($data['model']),
119
                        'parentPrices' => $this->getParentPrices($data['model']['id'])
120
                    ]);
121
                },
122
            ],
123
            'set-note' => [
124
                'class' => SmartUpdateAction::class,
125
                'success' => Yii::t('hipanel', 'Note changed'),
126
            ],
127
            'validate-form' => [
128
                'class' => ValidateFormAction::class,
129
            ],
130
            'validate-single-form' => [
131
                'class' => ValidateFormAction::class,
132
                'validatedInputId' => false,
133
            ],
134
            'delete' => [
135
                'class' => SmartDeleteAction::class,
136
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully deleted'),
137
            ],
138
            'restore' => [
139
                'class' => SmartPerformAction::class,
140
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully restored'),
141
            ],
142
            'copy' => [
143
                'class' => SmartUpdateAction::class,
144
                'view' => 'modals/copy',
145
                'queryOptions' => ['batch' => false],
146
            ],
147
        ]);
148
    }
149
150
    public function actionCreatePrices(int $plan_id, int $template_plan_id)
151
    {
152
        $plan = Plan::findOne(['id' => $template_plan_id]);
153
154
        $suggestions = (new Price)->batchQuery('suggest', [
155
            'object_id' => $plan_id,
156
            'plan_id' => $plan_id,
157
            'template_plan_id' => $template_plan_id,
158
            'type' => $plan->type,
159
        ]);
160
161
        $this->populateWithPrices($plan, $suggestions);
0 ignored issues
show
Documentation introduced by
$plan is of type object<yii\db\ActiveRecordInterface>|array, but the function expects a object<hipanel\modules\finance\models\Plan>.

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...
162
        $parentPrices = $this->getParentPrices($plan_id);
163
164
        $targetPlan = Plan::findOne(['id' => $plan_id]);
165
        $grouper = new PlanInternalsGrouper($plan);
0 ignored issues
show
Documentation introduced by
$plan is of type object<yii\db\ActiveRecordInterface>|array, but the function expects a object<hipanel\modules\finance\models\Plan>.

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...
166
        [$plan->name, $plan->id] = [$targetPlan->name, $targetPlan->id];
167
        $action = ['@plan/update-prices', 'id' => $plan->id, 'scenario' => 'create'];
168
169
        return $this->render($plan->type . '/' . 'createPrices',
170
            compact('plan', 'grouper', 'parentPrices', 'action', 'plan_id'));
171
    }
172
173
    public function actionSuggestPricesModal($id)
174
    {
175
        $plan = Plan::findOne(['id' => $id]);
176
        if ($plan === null) {
177
            throw new NotFoundHttpException('Not found');
178
        }
179
        $this->layout = false;
180
181
        return $this->renderAjax('_suggestPricesModal', ['plan' => $plan]);
182
    }
183
184
    /**
185
     * @param string $plan_id
186
     * @param string|null $object_id Object ID or `null`
187
     * when the desired templates are not related to a specific object
188
     * @param string $name_ilike
189
     * @return array
190
     */
191
    public function actionTemplates($plan_id, $object_id = null, string $name_ilike = null)
192
    {
193
        $templates = (new Plan())->query('search-templates', [
194
            'id' => $plan_id,
195
            'object_id' => $object_id ?? $plan_id,
196
            'name_ilike' => $name_ilike
197
        ]);
198
199
        Yii::$app->response->format = Response::FORMAT_JSON;
200
201
        return $templates;
202
    }
203
204
    public function actionCalculateCharges()
205
    {
206
        Yii::$app->response->format = Response::FORMAT_JSON;
207
        $request = Yii::$app->request;
208
209
        $periods = ['now', 'first day of +1 month', 'first day of +1 year'];
210
        $calculations = Plan::perform('calculate-charges', [
211
            'actions' => $request->post('actions'),
212
            'prices' => $request->post('prices'),
213
            'times' => $periods,
214
        ]);
215
        /** @var PriceChargesEstimator $calculator */
216
        $calculator = Yii::$container->get(PriceChargesEstimator::class, [$calculations]);
217
218
        try {
219
            return $calculator->calculateForPeriods($periods);
220
        } catch (ResponseErrorException $exception) {
221
            Yii::$app->response->setStatusCode(412, $exception->getMessage());
222
            return [
223
                'formula' => $exception->getResponse()->getData()['_error_ops']['formula'] ?? null
224
            ];
225
        }
226
    }
227
228
    public function actionCalculateValues($planId)
229
    {
230
        Yii::$app->response->format = Response::FORMAT_JSON;
231
        $periods = ['now', 'first day of +1 month', 'first day of +1 year'];
232
        try {
233
            $calculations = Plan::perform('calculate-values', ['id' => $planId, 'times' => $periods]);
234
            $calculator = Yii::$container->get(PriceChargesEstimator::class, [$calculations]);
235
236
            return $calculator->calculateForPeriods($periods);
237
        } catch (ResponseErrorException $exception) {
238
            Yii::$app->response->setStatusCode(412, $exception->getMessage());
239
240
            return [
241
                'formula' => $exception->getResponse()->getData()['_error_ops']['formula'] ?? null
242
            ];
243
        }
244
245
    }
246
247
    public function actionUpdatePrices(int $id, string $scenario = 'update')
248
    {
249
        $plan = Plan::find()
250
            ->byId($id)
251
            ->withPrices()
252
            ->one();
253
254
        $request = Yii::$app->request;
255
        if ($request->isPost) {
256
            try {
257
                $collection = new PricesCollection($this->priceModelFactory, ['scenario' => $scenario]);
258
                $collection->load();
259
                if ($collection->save() === false) {
260
                    if ($scenario === 'create') {
261
                        Yii::$app->session->addFlash('error', Yii::t('hipanel.finance.price', 'Error occurred during creation of prices'));
262
                    } elseif ($scenario === 'update') {
263
                        Yii::$app->session->addFlash('error', Yii::t('hipanel.finance.price', 'Error occurred during prices update'));
264
                    }
265
                } else {
266
                    if ($scenario === 'create') {
267
                        Yii::$app->session->addFlash('success', Yii::t('hipanel.finance.price', 'Prices were successfully created'));
268
                    } elseif ($scenario === 'update') {
269
                        Yii::$app->session->addFlash('success', Yii::t('hipanel.finance.price', 'Prices were successfully updated'));
270
                    }
271
                }
272
                return $this->redirect(['@plan/view', 'id' => $id]);
273
            } catch (\Exception $e) {
274
                throw new UnprocessableEntityHttpException($e->getMessage(), 0, $e);
275
            }
276
        }
277
278
        $grouper = new PlanInternalsGrouper($plan);
0 ignored issues
show
Documentation introduced by
$plan is of type object<hiqdev\hiart\ActiveRecord>|array|null, but the function expects a object<hipanel\modules\finance\models\Plan>.

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...
279
        $parentPrices = $this->getParentPrices($id);
280
281
        return $this->render($plan->type . '/' . 'updatePrices',
282
            compact('plan', 'grouper', 'parentPrices'));
283
    }
284
285
    /**
286
     * @param int $plan_id
287
     * @return array | null
288
     */
289
    private function getParentPrices(int $plan_id)
290
    {
291
        $parent_id = (new Plan())->query('get-parent-id', [
292
            'id' => $plan_id,
293
        ]);
294
        $parent_id = $parent_id['parent_id'];
295
        if ($parent_id === null) {
296
            return null;
297
        }
298
299
        $parent = Plan::find()
300
            ->byId($parent_id)
301
            ->withPrices()
302
            ->one();
303
304
        return $parent ? (new PlanInternalsGrouper($parent))->group() : null;
0 ignored issues
show
Documentation introduced by
$parent is of type object<hiqdev\hiart\ActiveRecord>|array, but the function expects a object<hipanel\modules\finance\models\Plan>.

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...
305
    }
306
307
    /**
308
     * @param Plan $plan
309
     * @param array $pricesData
310
     */
311
    private function populateWithPrices(Plan $plan, $pricesData): void
312
    {
313
        $prices = [];
314
        foreach ($pricesData as $priceData) {
315
            $object = ArrayHelper::remove($priceData, 'object');
316
            if (isset($priceData['plan_type']) &&
317
                $priceData['plan_type'] === 'certificate') {
318
                $priceData['class'] = 'CertificatePrice';
319
            }
320
321
            /** @var Price $price */
322
            $price = Price::instantiate($priceData);
323
            $price->setScenario('create');
324
            $price->setAttributes($priceData);
325
            $price->populateRelation('object', new TargetObject($object));
326
            $price->trigger(Price::EVENT_AFTER_FIND);
327
            $prices[] = $price;
328
        }
329
        $prices = PriceSort::anyPrices()->values($prices, true);
330
331
        $plan->populateRelation('prices', $prices);
332
    }
333
}
334