201 lines
6.7 KiB
PHP
201 lines
6.7 KiB
PHP
<?php
|
||
/**
|
||
* @package Bicycle
|
||
* @author Egor Isaev
|
||
* @description CategoryGroupsController.php
|
||
* @copyright (c) 15/08/2026
|
||
*/
|
||
|
||
namespace App\Controller;
|
||
|
||
use App\Repositories\CategoryGroupRepository;
|
||
use App\Repositories\CategoryRepository;
|
||
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 групп статей (category_groups) — самостоятельная сущность, отдельная от статей
|
||
* (см. App\Controller\CategoriesController). JSON API, вызывается из той же модалки «Статьи»
|
||
* на /budgets (см. App/media/js/categories.js).
|
||
*/
|
||
class CategoryGroupsController extends Controller
|
||
{
|
||
protected bool $_auth_protection = true;
|
||
|
||
/**
|
||
* @return string JSON
|
||
* @throws MyException
|
||
*/
|
||
public function createAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$user = $this->currentUser();
|
||
$group_repo = new CategoryGroupRepository();
|
||
|
||
$validation = $this->buildValidation($request->post());
|
||
|
||
if (!$validation->check()) {
|
||
return $this->json(['success' => false, 'errors' => $validation->errors()], 422);
|
||
}
|
||
|
||
$type = $request->post('type');
|
||
$title = $request->post('title');
|
||
|
||
if ($group_repo->findActiveDuplicate($user->id, $type, $title)) {
|
||
return $this->json(['success' => false, 'errors' => ['title' => 'Такая группа уже есть']], 422);
|
||
}
|
||
|
||
$archived_duplicate = $group_repo->findArchivedDuplicate($user->id, $type, $title);
|
||
|
||
if ($archived_duplicate) {
|
||
return $this->json([
|
||
'success' => false,
|
||
'archived' => ['id' => $archived_duplicate->id, 'title' => $archived_duplicate->title],
|
||
], 422);
|
||
}
|
||
|
||
$group_repo->create([
|
||
'user_id' => $user->id,
|
||
'title' => $title,
|
||
'type' => $type,
|
||
'is_delete' => false,
|
||
'order' => 0,
|
||
]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function editAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$user = $this->currentUser();
|
||
$group_repo = new CategoryGroupRepository();
|
||
$group = $this->ownGroup($group_repo, $user, (int)$request->param(0));
|
||
|
||
$validation = $this->buildValidation($request->post());
|
||
|
||
if (!$validation->check()) {
|
||
return $this->json(['success' => false, 'errors' => $validation->errors()], 422);
|
||
}
|
||
|
||
$type = $request->post('type');
|
||
$title = $request->post('title');
|
||
|
||
if ($group_repo->findActiveDuplicate($user->id, $type, $title, (int)$group->id)) {
|
||
return $this->json(['success' => false, 'errors' => ['title' => 'Такая группа уже есть']], 422);
|
||
}
|
||
|
||
$archived_duplicate = $group_repo->findArchivedDuplicate($user->id, $type, $title, (int)$group->id);
|
||
|
||
if ($archived_duplicate) {
|
||
return $this->json([
|
||
'success' => false,
|
||
'archived' => ['id' => $archived_duplicate->id, 'title' => $archived_duplicate->title],
|
||
], 422);
|
||
}
|
||
|
||
$group_repo->update(['id' => $group->id, 'title' => $title, 'type' => $type]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* Архивация группы. Статьи внутри — не архивируются автоматически, а просто становятся без
|
||
* группы (group_id = NULL): архивация группы не должна "тихо" утаскивать за собой все статьи —
|
||
* это уже было бы массовым удалением без явного подтверждения. ON DELETE SET NULL в схеме
|
||
* сработал бы так же, но только при физическом удалении группы, а тут — архивация (is_delete),
|
||
* поэтому group_id у статей чистим явно сами.
|
||
*
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function deleteAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$user = $this->currentUser();
|
||
$group_repo = new CategoryGroupRepository();
|
||
$group = $this->ownGroup($group_repo, $user, (int)$request->param(0));
|
||
|
||
$group_repo->update(['id' => $group->id, 'is_delete' => true]);
|
||
(new CategoryRepository())->clearGroup((int)$group->id, (int)$user->id);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* Возврат группы из архива.
|
||
*
|
||
* @return string JSON
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
public function restoreAction(): string
|
||
{
|
||
$request = Request::$current;
|
||
$user = $this->currentUser();
|
||
$group_repo = new CategoryGroupRepository();
|
||
$group = $this->ownGroup($group_repo, $user, (int)$request->param(0));
|
||
|
||
$group_repo->update(['id' => $group->id, 'is_delete' => false]);
|
||
|
||
return $this->json(['success' => true]);
|
||
}
|
||
|
||
/**
|
||
* @param array $data
|
||
* @return Validation
|
||
*/
|
||
protected function buildValidation(array $data): Validation
|
||
{
|
||
return Validation::factory($data)
|
||
->label('title', 'Название')
|
||
->label('type', 'Тип')
|
||
->rule('title', 'required')
|
||
->rule('title', 'max_length', [128])
|
||
->rule('type', 'required')
|
||
->rule('type', 'in', [['income', 'expense']]);
|
||
}
|
||
|
||
/**
|
||
* @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;
|
||
}
|
||
|
||
/**
|
||
* @param CategoryGroupRepository $group_repo
|
||
* @param object $user
|
||
* @param int $id
|
||
* @return object
|
||
* @throws HTTPException|MyException
|
||
*/
|
||
protected function ownGroup(CategoryGroupRepository $group_repo, object $user, int $id): object
|
||
{
|
||
$group = $group_repo->get($id);
|
||
|
||
if (!$group || (int)$group->user_id !== (int)$user->id) {
|
||
throw HTTPException::factory(404);
|
||
}
|
||
|
||
return $group;
|
||
}
|
||
}
|