Bicycle/App/Controller/CategoryGroupsController.php
2026-08-15 10:39:22 +03:00

201 lines
6.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}