Completed
Push — master ( 4bde9c...94e19b )
by Andrii
04:39
created

src/controllers/PlanController.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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