220 lines
7.6 KiB
PHP
220 lines
7.6 KiB
PHP
<?php
|
||
/**
|
||
* @package Bicycle
|
||
* @author Egor Isaev
|
||
* @description AccountsController.php
|
||
* @copyright (c) 12/08/2026
|
||
*/
|
||
|
||
namespace App\Controller;
|
||
|
||
use App\Classes\Currency;
|
||
use App\Repositories\AccountRateRepository;
|
||
use App\Repositories\AccountRepository;
|
||
use App\Repositories\UserRepository;
|
||
use Services\Auth;
|
||
use System\Classes\Controller;
|
||
use System\Classes\HTTP\HTTPException;
|
||
use System\Classes\MyException;
|
||
use System\Classes\Request;
|
||
use System\Classes\Validation;
|
||
|
||
/**
|
||
* CRUD счетов (accounts) — создание/правка/архивация. Ответы — JSON, вызывается
|
||
* ajax'ом с модалки на дашборде (см. App/view/Index/index.html, App/media/js/accounts.js) —
|
||
* своей страницы у счетов нет, см. бюджет_текущий_план.md → Goals → "UI — на дашборде".
|
||
*/
|
||
class AccountsController extends Controller
|
||
{
|
||
protected bool $_auth_protection = true;
|
||
|
||
/**
|
||
* @return string JSON
|
||
* @throws MyException
|
||
*/
|
||
public function createAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$user = $this->currentUser();
|
||
|
||
$validation = $this->buildValidation($request->post());
|
||
|
||
if (!$validation->check()) {
|
||
return $this->json(['success' => false, 'errors' => $validation->errors()], 422);
|
||
}
|
||
|
||
$summa = (float)$request->post('summa');
|
||
|
||
if ($summa < 0) {
|
||
return $this->json(['success' => false, 'errors' => ['summa' => 'Сумма не может быть отрицательной']], 422);
|
||
}
|
||
|
||
$type_acc = $request->post('type_acc');
|
||
$rate_percent = $request->post('rate_percent');
|
||
|
||
$data = [
|
||
'user_id' => $user->id,
|
||
'type_acc' => $type_acc,
|
||
'title' => $request->post('title'),
|
||
'summa' => $summa,
|
||
'currency' => strtoupper((string)$request->post('currency')),
|
||
'description' => $request->post('description') !== '' ? $request->post('description') : null,
|
||
'include_in_total' => $request->post('include_in_total') !== null,
|
||
'is_delete' => false,
|
||
'order' => 0,
|
||
];
|
||
|
||
$account_repo = new AccountRepository();
|
||
|
||
$account_repo->transaction(static function () use ($account_repo, $data, $type_acc, $rate_percent) {
|
||
$account_id = $account_repo->create($data);
|
||
|
||
if ($type_acc === 'savings' && $rate_percent !== null && $rate_percent !== '') {
|
||
(new AccountRateRepository())->create([
|
||
'account_id' => $account_id,
|
||
'rate_percent' => (float)$rate_percent,
|
||
'valid_from' => date('Y-m-d'),
|
||
'valid_to' => null,
|
||
]);
|
||
}
|
||
|
||
return $account_id;
|
||
});
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function editAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$account_repo = new AccountRepository();
|
||
$account = $this->ownAccount($account_repo, (int)$request->param(0));
|
||
|
||
$validation = $this->buildValidation($request->post());
|
||
|
||
if (!$validation->check()) {
|
||
return $this->json(['success' => false, 'errors' => $validation->errors()], 422);
|
||
}
|
||
|
||
$summa = (float)$request->post('summa');
|
||
|
||
if ($summa < 0) {
|
||
return $this->json(['success' => false, 'errors' => ['summa' => 'Сумма не может быть отрицательной']], 422);
|
||
}
|
||
|
||
$account_repo->update([
|
||
'id' => $account->id,
|
||
'type_acc' => $request->post('type_acc'),
|
||
'title' => $request->post('title'),
|
||
'summa' => $summa,
|
||
'currency' => strtoupper((string)$request->post('currency')),
|
||
'description' => $request->post('description') !== '' ? $request->post('description') : null,
|
||
'include_in_total' => $request->post('include_in_total') !== null,
|
||
]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* Архивация (is_delete = 1), не физическое удаление — см. бюджет_текущий_план.md → Accounts.
|
||
*
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function deleteAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$account_repo = new AccountRepository();
|
||
$account = $this->ownAccount($account_repo, (int)$request->param(0));
|
||
|
||
$account_repo->update(['id' => $account->id, 'is_delete' => true]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* Возврат из архива (is_delete = 0).
|
||
*
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function restoreAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$account_repo = new AccountRepository();
|
||
$account = $this->ownAccount($account_repo, (int)$request->param(0));
|
||
|
||
$account_repo->update(['id' => $account->id, 'is_delete' => false]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* Общие правила валидации формы счёта (create/edit).
|
||
*
|
||
* @param array $data
|
||
* @return Validation
|
||
*/
|
||
protected function buildValidation(array $data): Validation
|
||
{
|
||
return Validation::factory($data)
|
||
->label('title', 'Название')
|
||
->label('type_acc', 'Тип')
|
||
->label('summa', 'Сумма')
|
||
->label('currency', 'Валюта')
|
||
->rule('title', 'required')
|
||
->rule('title', 'max_length', [128])
|
||
->rule('type_acc', 'required')
|
||
->rule('type_acc', 'in', [['cash', 'bank', 'savings']])
|
||
->rule('summa', 'required')
|
||
->rule('summa', 'numeric')
|
||
->rule('currency', 'required')
|
||
->rule('currency', 'in', [array_keys(Currency::LIST)])
|
||
->rule('description', 'max_length', [255]);
|
||
}
|
||
|
||
/**
|
||
* Бюджетный пользователь (App\Repositories\UserRepository), связанный с текущим логином
|
||
* Services\Auth — см. App\Controller\IndexController за тем же паттерном.
|
||
*
|
||
* @return object
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
protected function currentUser(): object
|
||
{
|
||
$auth_user = Auth::instance()->getUser();
|
||
$user = (new UserRepository())->getByLogin($auth_user['login']);
|
||
|
||
if (!$user) {
|
||
throw HTTPException::factory(404);
|
||
}
|
||
|
||
return $user;
|
||
}
|
||
|
||
/**
|
||
* Счёт по id, принадлежащий текущему пользователю — иначе 404 (не 403, чтобы не подтверждать
|
||
* чужому пользователю сам факт существования id).
|
||
*
|
||
* @param AccountRepository $account_repo
|
||
* @param int $id
|
||
* @return object
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
protected function ownAccount(AccountRepository $account_repo, int $id): object
|
||
{
|
||
$user = $this->currentUser();
|
||
$account = $account_repo->get($id);
|
||
|
||
if (!$account || (int)$account->user_id !== (int)$user->id) {
|
||
throw HTTPException::factory(404);
|
||
}
|
||
|
||
return $account;
|
||
}
|
||
}
|