Completed
Push — master ( 4b4ef3...b565c2 )
by Dmitry
13:19
created

PlanController::findTemplatePlan()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 10
cp 0
rs 9.7
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 6
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\finance\models\PriceSuggestionRequestForm;
22
use hipanel\modules\server\models\Server;
23
use hipanel\filters\EasyAccessControl;
24
use hipanel\modules\finance\models\Price;
25
use hipanel\modules\finance\models\TargetObject;
26
use hiqdev\hiart\ResponseErrorException;
27
use Yii;
28
use yii\base\Event;
29
use yii\base\Module;
30
use yii\web\NotFoundHttpException;
31
use yii\web\Response;
32
use yii\web\UnprocessableEntityHttpException;
33
34
class PlanController extends CrudController
35
{
36
    /**
37
     * @var PriceModelFactory
38
     */
39
    public $priceModelFactory;
40
41
    /**
42
     * PlanController constructor.
43
     * @param string $id
44
     * @param Module $module
45
     * @param PriceModelFactory $priceModelFactory
46
     * @param array $config
47
     */
48
    public function __construct(string $id, Module $module, PriceModelFactory $priceModelFactory, array $config = [])
49
    {
50
        parent::__construct($id, $module, $config);
51
52
        $this->priceModelFactory = $priceModelFactory;
53
    }
54
55
    public function behaviors()
56
    {
57
        return array_merge(parent::behaviors(), [
58
            [
59
                'class' => EasyAccessControl::class,
60
                'actions' => [
61
                    'create' => 'plan.create',
62
                    'update' => 'plan.update',
63
                    'update-prices' => 'plan.update',
64
                    'templates' => 'plan.create',
65
                    'create-prices' => 'plan.create',
66
                    'delete' => 'plan.delete',
67
                    '*' => 'plan.read',
68
                ],
69
            ],
70
        ]);
71
    }
72
73
    public function actions()
74
    {
75
        return array_merge(parent::actions(), [
76
            'create' => [
77
                'class' => SmartCreateAction::class,
78
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully created'),
79
            ],
80
            'update' => [
81
                'class' => SmartUpdateAction::class,
82
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully updated'),
83
            ],
84
            'index' => [
85
                'class' => IndexAction::class,
86
            ],
87
            'view' => [
88
                'class' => ViewAction::class,
89
                'on beforePerform' => function (Event $event) {
90
                    $action = $event->sender;
91
                    $action->getDataProvider()->query
92
                        ->joinWith('sales')
93
                        ->andWhere(['state' => ['ok', 'deleted']])
94
                        ->withPrices();
95
                },
96
                'data' => function (Action $action, array $data) {
97
                    return array_merge($data, [
98
                        'grouper' => new PlanInternalsGrouper($data['model']),
99
                        'parentPrices' => $this->getParentPrices($data['model']['id'])
100
                    ]);
101
                },
102
            ],
103
            'set-note' => [
104
                'class' => SmartUpdateAction::class,
105
                'success' => Yii::t('hipanel', 'Note changed'),
106
            ],
107
            'validate-form' => [
108
                'class' => ValidateFormAction::class,
109
            ],
110
            'validate-single-form' => [
111
                'class' => ValidateFormAction::class,
112
                'validatedInputId' => false,
113
            ],
114
            'delete' => [
115
                'class' => SmartDeleteAction::class,
116
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully deleted'),
117
            ],
118
            'restore' => [
119
                'class' => SmartPerformAction::class,
120
                'success' => Yii::t('hipanel.finance.plan', 'Plan was successfully restored'),
121
            ],
122
            'copy' => [
123
                'class' => SmartUpdateAction::class,
124
                'view' => 'modals/copy',
125
                'queryOptions' => ['batch' => false],
126
            ],
127
        ]);
128
    }
129
130
    public function actionCreatePrices(int $plan_id, int $template_plan_id)
131
    {
132
        $plan = $this->findTemplatePlan($plan_id, $plan_id, $template_plan_id);
133
134
        $suggestions = (new Price)->batchQuery('suggest', [
135
            'object_id' => $plan_id,
136
            'plan_id' => $plan_id,
137
            'template_plan_id' => $template_plan_id,
138
            'type' => $plan->type,
139
        ]);
140
        $this->populateWithPrices($plan, $suggestions);
141
142
        $parentPrices = $this->getParentPrices($plan_id);
143
144
        $targetPlan = Plan::findOne(['id' => $plan_id]);
145
146
        $grouper = new PlanInternalsGrouper($plan);
147
        [$plan->name, $plan->id] = [$targetPlan->name, $targetPlan->id];
148
        $action = ['@plan/update-prices', 'id' => $plan->id, 'scenario' => 'create'];
149
150
        return $this->render($plan->type . '/' . 'createPrices',
151
            compact('plan', 'grouper', 'parentPrices', 'action', 'plan_id'));
152
    }
153
154
    public function actionSuggestPricesModal($id)
155
    {
156
        /** @var Plan $plan */
157
        $plan = $this->findPlan($id);
158
        $model = new PriceSuggestionRequestForm([
159
            'plan_id' => $plan->id,
160
            'plan_type' => $plan->type,
161
        ]);
162
163
        return $this->renderAjax('modals/suggestPrices', compact('plan', 'model'));
164
    }
165
166
    public function actionSuggestGroupingPricesModal($id)
167
    {
168
        /** @var Plan $plan */
169
        $plan = $this->findPlan($id);
170
        $model = new PriceSuggestionRequestForm([
171
            'plan_id' => $plan->id,
172
            'plan_type' => $plan->type,
173
            'object_id' => $plan->id,
174
            'scenario' => PriceSuggestionRequestForm::SCENARIO_PREDEFINED_OBJECT,
175
        ]);
176
177
        return $this->renderAjax('modals/suggestPrices', compact('plan', 'model'));
178
    }
179
180
    public function actionSuggestSharedPricesModal($id)
181
    {
182
        /** @var Plan $plan */
183
        $plan = $this->findPlan($id);
184
        $model = new PriceSuggestionRequestForm([
185
            'plan_id' => $plan->id,
186
            'plan_type' => $plan->type,
187
            'scenario' => PriceSuggestionRequestForm::SCENARIO_PREDEFINED_OBJECT,
188
        ]);
189
190
        return $this->renderAjax('modals/suggestPrices', compact('plan', 'model'));
191
    }
192
193
    private function findTemplatePlan(int $targetPlan, int $object_id, int $expectedTemplateId): Plan
194
    {
195
        $result = Plan::perform( 'search-templates', [
196
            'id' => $targetPlan,
197
            'object_id' => $object_id,
198
        ]);
199
        $plans = ArrayHelper::index( $result, 'id');
200
201
        if (!isset($plans[$expectedTemplateId])) {
202
            throw new NotFoundHttpException('Requested template plan not found');
203
        }
204
205
        $plan = Plan::instantiate($plans[$expectedTemplateId]);
206
        Plan::populateRecord($plan, $plans[$expectedTemplateId]);
207
208
        return $plan;
209
    }
210
211
    /**
212
     * @param $id integer
213
     * @return Plan|null
214
     * @throws NotFoundHttpException
215
     */
216
    private function findPlan(int $id): ?Plan
217
    {
218
        $plan = Plan::findOne(['id' => $id]);
219
        if ($plan === null) {
220
            throw new NotFoundHttpException('Not found');
221
        }
222
223
        return $plan;
224
    }
225
226
    /**
227
     * @param string $plan_id
228
     * @param string|null $object_id Object ID or `null`
229
     * when the desired templates are not related to a specific object
230
     * @param string $name_ilike
231
     * @return array
232
     */
233
    public function actionTemplates($plan_id, $object_id = null, string $name_ilike = null)
234
    {
235
        $templates = (new Plan())->query('search-templates', [
236
            'id' => $plan_id,
237
            'object_id' => $object_id ?? $plan_id,
238
            'name_ilike' => $name_ilike
239
        ]);
240
241
        Yii::$app->response->format = Response::FORMAT_JSON;
242
243
        return $templates;
244
    }
245
246
    public function actionCalculateCharges()
247
    {
248
        Yii::$app->response->format = Response::FORMAT_JSON;
249
        $request = Yii::$app->request;
250
251
        $periods = ['now', 'first day of +1 month', 'first day of +1 year'];
252
        $calculations = Plan::perform('calculate-charges', [
253
            'actions' => $request->post('actions'),
254
            'prices' => $request->post('prices'),
255
            'times' => $periods,
256
        ]);
257
        /** @var PriceChargesEstimator $calculator */
258
        $calculator = Yii::$container->get(PriceChargesEstimator::class, [$calculations]);
259
260
        try {
261
            return $calculator->calculateForPeriods($periods);
262
        } catch (ResponseErrorException $exception) {
263
            Yii::$app->response->setStatusCode(412, $exception->getMessage());
264
            return [
265
                'formula' => $exception->getResponse()->getData()['_error_ops']['formula'] ?? null
266
            ];
267
        }
268
    }
269
270
    public function actionCalculateValues($planId)
271
    {
272
        Yii::$app->response->format = Response::FORMAT_JSON;
273
        $periods = ['now', 'first day of +1 month', 'first day of +1 year'];
274
        try {
275
            $calculations = Plan::perform('calculate-values', ['id' => $planId, 'times' => $periods]);
276
            $calculator = Yii::$container->get(PriceChargesEstimator::class, [$calculations]);
277
278
            return $calculator->calculateForPeriods($periods);
279
        } catch (ResponseErrorException $exception) {
280
            Yii::$app->response->setStatusCode(412, $exception->getMessage());
281
282
            return [
283
                'formula' => $exception->getResponse()->getData()['_error_ops']['formula'] ?? null
284
            ];
285
        }
286
287
    }
288
289
    public function actionUpdatePrices(int $id, string $scenario = 'update')
290
    {
291
        $plan = Plan::find()
292
            ->byId($id)
293
            ->withPrices()
294
            ->one();
295
296
        $request = Yii::$app->request;
297
        if ($request->isPost) {
298
            try {
299
                $collection = new PricesCollection($this->priceModelFactory, ['scenario' => $scenario]);
300
                $collection->load();
301
                if ($collection->save() === false) {
302
                    if ($scenario === 'create') {
303
                        Yii::$app->session->addFlash('error', Yii::t('hipanel.finance.price', 'Error occurred during creation of prices'));
304
                    } elseif ($scenario === 'update') {
305
                        Yii::$app->session->addFlash('error', Yii::t('hipanel.finance.price', 'Error occurred during prices update'));
306
                    }
307
                } else {
308
                    if ($scenario === 'create') {
309
                        Yii::$app->session->addFlash('success', Yii::t('hipanel.finance.price', 'Prices were successfully created'));
310
                    } elseif ($scenario === 'update') {
311
                        Yii::$app->session->addFlash('success', Yii::t('hipanel.finance.price', 'Prices were successfully updated'));
312
                    }
313
                }
314
                return $this->redirect(['@plan/view', 'id' => $id]);
315
            } catch (\Exception $e) {
316
                throw new UnprocessableEntityHttpException($e->getMessage(), 0, $e);
317
            }
318
        }
319
320
        $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...
321
        $parentPrices = $this->getParentPrices($id);
322
323
        return $this->render($plan->type . '/' . 'updatePrices',
324
            compact('plan', 'grouper', 'parentPrices'));
325
    }
326
327
    /**
328
     * @param int $plan_id
329
     * @return array | null
330
     */
331
    private function getParentPrices(int $plan_id)
332
    {
333
        $plan = Plan::find()
334
            ->addAction('get-parent')
335
            ->where(['id' => $plan_id])
336
            ->joinWithPrices()
337
            ->one();
338
339
        return $plan ? (new PlanInternalsGrouper($plan))->group() : null;
0 ignored issues
show
Documentation introduced by
$plan 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...
340
    }
341
342
    /**
343
     * @param Plan $plan
344
     * @param array $pricesData
345
     */
346
    private function populateWithPrices(Plan $plan, $pricesData): void
347
    {
348
        $prices = [];
349
        foreach ($pricesData as $priceData) {
350
            $object = ArrayHelper::remove($priceData, 'object');
351
            if (isset($priceData['plan_type']) &&
352
                $priceData['plan_type'] === 'certificate') {
353
                $priceData['class'] = 'CertificatePrice';
354
            }
355
356
            /** @var Price $price */
357
            $price = Price::instantiate($priceData);
358
            $price->setScenario('create');
359
            $price->setAttributes($priceData);
360
            $price->populateRelation('object', new TargetObject($object));
361
            $price->trigger(Price::EVENT_AFTER_FIND);
362
            $prices[] = $price;
363
        }
364
        $prices = PriceSort::anyPrices()->values($prices, true);
365
366
        $plan->populateRelation('prices', $prices);
367
    }
368
}
369