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) { |
|
|
|
|
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); |
|
|
|
|
162
|
|
|
$parentPrices = $this->getParentPrices($plan_id); |
163
|
|
|
|
164
|
|
|
$targetPlan = Plan::findOne(['id' => $plan_id]); |
165
|
|
|
$grouper = new PlanInternalsGrouper($plan); |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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
|
|
|
|
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.