From b7c9d8491f5cce6644ee34352b4c32d4d5cc5dd9 Mon Sep 17 00:00:00 2001 From: MadHowl Date: Wed, 8 Apr 2026 04:59:53 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- task06-event-manager.md | 347 ++++++++++++++++++++++++++ task07-recipe-catalog.md | 400 +++++++++++++++++++++++++++++ task08-inventory-management.md | 443 +++++++++++++++++++++++++++++++++ task09-book-exchange.md | 388 +++++++++++++++++++++++++++++ task10-pet-shelter.md | 432 ++++++++++++++++++++++++++++++++ 5 files changed, 2010 insertions(+) create mode 100644 task06-event-manager.md create mode 100644 task07-recipe-catalog.md create mode 100644 task08-inventory-management.md create mode 100644 task09-book-exchange.md create mode 100644 task10-pet-shelter.md diff --git a/task06-event-manager.md b/task06-event-manager.md new file mode 100644 index 0000000..5d3a37f --- /dev/null +++ b/task06-event-manager.md @@ -0,0 +1,347 @@ +# Техническое задание: Система управления мероприятиями (Event Manager) + +## 1. Описание проекта + +Веб-платформа для создания и продвижения мероприятий с регистрацией участников, расписанием и обсуждениями. Проект закрепляет навыки CRUD, валидации дат и вместимости, разграничения прав и совместной работы через Git. + +**Команда:** 2–3 человека + +--- + +## 2. Функциональные требования + +### 2.1. Аутентификация и регистрация +- Регистрация, вход, выход (Laravel Breeze) + +### 2.2. Каталог мероприятий +- Просмотр списка мероприятий с пагинацией +- Фильтрация по категориям (конференции, воркшопы, вебинары, митапы) +- Фильтрация по дате (сегодня, неделя, месяц, будущие) +- Поиск по названию и описанию +- Детальная страница мероприятия (обложка, описание, место, дата, организатор, список участников, комментарии) + +### 2.3. Управление мероприятиями (организатор/админ) +- CRUD мероприятий: название, описание, обложка, категория, дата начала/окончания, место (адрес или онлайн-ссылка), вместимость, стоимость (бесплатно/платно) +- Загрузка обложки мероприятия (валидация: jpg/png, макс. 2MB) +- Управление участниками (просмотр списка, отмена регистрации участника) + +### 2.4. Регистрация на мероприятие +- Кнопка «Зарегистрироваться» на странице мероприятия +- **Валидация:** проверка на наличие свободных мест (если вместимость ограничена) +- **Валидация:** пользователь не может зарегистрироваться дважды +- Отмена регистрации (пользователь — до начала мероприятия) +- Список участников мероприятия (публичный) + +### 2.5. Комментарии и обсуждения +- Добавление комментариев к мероприятию +- Удаление своего комментария +- Удаление любого комментария (админ/организатор) +- Пагинация комментариев + +### 2.6. Панель организатора +- Мои мероприятия (созданные мной) +- Статистика по каждому: количество зарегистрированных, свободные места +- Список участников с возможностью экспорта +- Управление комментариями + +### 2.7. Панель участника +- Мои мероприятия (на которые зарегистрирован) +- Прошедшие мероприятия (история) +- Рекомендации (похожие мероприятия) + +### 2.8. Панель администратора +- Управление пользователями и ролями +- Модерация мероприятий (скрыть/показать) +- Общая статистика платформы + +--- + +## 3. Структура базы данных + +### Таблица `users` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Имя | +| email | VARCHAR(255) | Email (unique) | +| password | VARCHAR(255) | Хеш пароля | +| role | ENUM('admin', 'organizer', 'participant') | Роль | +| avatar | VARCHAR(255) \| NULL | Аватар | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `categories` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название категории | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `events` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| title | VARCHAR(255) | Название мероприятия | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| description | TEXT | Описание | +| cover_image | VARCHAR(255) \| NULL | Обложка | +| category_id | BIGINT UNSIGNED | Категория (FK → categories) | +| organizer_id | BIGINT UNSIGNED | Организатор (FK → users) | +| start_datetime | DATETIME | Дата и время начала | +| end_datetime | DATETIME | Дата и время окончания | +| location | VARCHAR(255) | Место проведения (адрес или «Онлайн») | +| location_url | VARCHAR(255) \| NULL | Ссылка для онлайн-мероприятий | +| capacity | INT \| NULL | Вместимость (NULL = без ограничений) | +| price | DECIMAL(10, 2) \| NULL | Стоимость (NULL = бесплатно) | +| is_published | BOOLEAN | Опубликован ли | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `event_user` (регистрация на мероприятие) +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| event_id | BIGINT UNSIGNED | FK → events | +| user_id | BIGINT UNSIGNED | FK → users | +| registered_at | TIMESTAMP | Дата регистрации | +| attended | BOOLEAN | Присутствовал ли | + +### Таблица `comments` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| body | TEXT | Текст комментария | +| user_id | BIGINT UNSIGNED | Автор (FK → users) | +| event_id | BIGINT UNSIGNED | Мероприятие (FK → events) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +--- + +## 4. Маршруты и контроллеры + +```php +// Публичные маршруты +Route::get('/', [EventController::class, 'index'])->name('events.index'); +Route::get('/events/{event:slug}', [EventController::class, 'show'])->name('events.show'); + +// Аутентифицированные маршруты +Route::middleware('auth')->group(function () { + // Регистрация на мероприятие + Route::post('/events/{event}/register', [EventController::class, 'register'])->name('events.register'); + Route::post('/events/{event}/unregister', [EventController::class, 'unregister'])->name('events.unregister'); + + // Комментарии + Route::resource('events.comments', CommentController::class)->scoped(['events' => 'event'])->only(['store', 'destroy']); + + // Мои мероприятия (участник) + Route::get('/my-events', [ParticipantController::class, 'index'])->name('participant.events'); + + // Панель организатора + Route::prefix('organizer')->name('organizer.')->middleware('can:be-organizer')->group(function () { + Route::resource('events', OrganizerEventController::class); + Route::get('events/{event}/attendees', [OrganizerEventController::class, 'attendees'])->name('events.attendees'); + Route::post('events/{event}/attendees/{user}/remove', [OrganizerEventController::class, 'removeAttendee'])->name('events.attendees.remove'); + Route::get('dashboard', [OrganizerDashboardController::class, 'index'])->name('dashboard'); + }); + + // Админка + Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { + Route::resource('users', AdminUserController::class); + Route::patch('events/{event}/toggle-publish', [AdminEventController::class, 'togglePublish'])->name('events.publish'); + Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); + }); +}); +``` + +### Контроллеры +- `EventController` — публичный каталог, регистрация/отмена +- `CommentController` — создание/удаление комментариев +- `ParticipantController` — мои мероприятия (участник) +- `OrganizerEventController` — CRUD мероприятий организатором +- `OrganizerDashboardController` — панель организатора +- `AdminUserController`, `AdminEventController`, `AdminDashboardController` — админка + +--- + +## 5. Роли, Gates и Policies + +### Роли +| Роль | Описание | +|---|---| +| `admin` | Полный доступ, модерация мероприятий, управление пользователями | +| `organizer` | Создание/редактирование своих мероприятий, просмотр участников | +| `participant` | Регистрация на мероприятия, комментарии, просмотр расписания | + +### Gates + +```php +Gate::define('be-organizer', function (User $user) { + return in_array($user->role, ['admin', 'organizer']); +}); + +Gate::define('access-admin-panel', function (User $user) { + return $user->role === 'admin'; +}); +``` + +### Policies + +**EventPolicy:** +- `view` — все (если опубликован), организатор/admin (если не опубликован) +- `create` — organizer или admin +- `update`, `delete` — организатор мероприятия или admin +- `register` — participant (не зарегистрирован на это мероприятие, есть свободные места) + +**CommentPolicy:** +- `create` — все аутентифицированные +- `delete` — автор комментария, организатор мероприятия или admin + +--- + +## 6. Требования к интерфейсу (Bootstrap 5) + +### Компоненты +- **Навбар** — логотип, каталог, «Мои мероприятия», профиль +- **Карточки мероприятий** — обложка, название, дата (бейдж), категория (бейдж), вместимость +- **Страница мероприятия** — большая обложка, описание, место, дата, кнопка регистрации, список участников, комментарии +- **Модальное окно** — быстрая регистрация (подтверждение) +- **Таблица участников** — имя, email, дата регистрации, кнопка удаления (организатор) +- **Форма мероприятия** — Bootstrap-форма с загрузкой обложки, datetime-local для дат +- **Дашборд организатора** — статистика (карточки), таблица мероприятий +- **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента и сайдбара + +### Цветовая схема категорий +- Конференция — `bg-primary` +- Воркшоп — `bg-success` +- Вебинар — `bg-info` +- Митап — `bg-warning` + +--- + +## 7. Git-workflow для команды + +### Распределение модулей (для 2 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Мероприятия, категории, обложки, регистрация | `feature/events-crud`, `feature/categories`, `feature-event-covers` | +| **Участник B** | Участники, комментарии, панель организатора, UI | `feature/event-registration`, `feature-attendees`, `feature-capacity-validation` | + +### Распределение модулей (для 3 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Мероприятия, категории, загрузка обложек | `feature/events-crud`, `feature/categories`, `feature-event-covers` | +| **Участник B** | Регистрация, участники, валидация вместимости | `feature/event-registration`, `feature-attendees`, `feature-capacity-validation` | +| **Участник C** | Комментарии, панель организатора, каталог, UI | `feature/comments`, `feature-organizer-panel`, `feature-event-catalog-ui` | + +### Правила +1. Ветка `develop` — основная ветка разработки +2. Каждый участник создаёт фич-ветки от `develop` +3. Минимум 3 PR на участника +4. Обязательный код-ревью перед мёржем +5. Conventional Commits + +### Визуализация workflow (2 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/events-crud + checkout develop + branch feature/event-registration + checkout feature/events-crud + commit id: "feat: add events CRUD" + commit id: "feat: add event categories" + checkout feature/event-registration + commit id: "feat: add event registration" + commit id: "feat: add attendees management" + checkout develop + merge feature/events-crud tag: "PR + review" + merge feature/event-registration tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +### Визуализация workflow (3 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/events-crud + checkout develop + branch feature/event-registration + checkout develop + branch feature/comments + checkout feature/events-crud + commit id: "feat: add events CRUD" + commit id: "feat: add event covers" + checkout feature/event-registration + commit id: "feat: add event registration" + commit id: "feat: add attendees management" + checkout feature/comments + commit id: "feat: add event comments" + commit id: "feat: add organizer panel" + checkout develop + merge feature/events-crud tag: "PR + review" + merge feature/event-registration tag: "PR + review" + merge feature/comments tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +--- + +## 8. Критерии приёмки + +### Обязательно +- [ ] CRUD мероприятий с загрузкой обложки +- [ ] CRUD категорий мероприятий +- [ ] Регистрация на мероприятие с валидацией вместимости +- [ ] Отмена регистрации (до начала мероприятия) +- [ ] Список участников мероприятия +- [ ] Комментарии к мероприятиям (создание + удаление) +- [ ] Панель организатора (мои мероприятия, статистика, участники) +- [ ] Policies: участник не может редактировать мероприятия, организатор не может редактировать чужие мероприятия +- [ ] Адаптивная Bootstrap-вёрстка +- [ ] Flash-сообщения +- [ ] Пагинация каталога и комментариев +- [ ] Git-история: минимум 3 ветки на участника, PR с ревью + +### Дополнительно (бонусные баллы) +- [ ] Поиск мероприятий по названию/описанию +- [ ] Фильтрация по дате (сегодня, неделя, месяц) +- [ ] Экспорт списка участников в CSV +- [ ] Рейтинг мероприятия (после завершения) +- [ ] Похожие мероприятия (рекомендации) +- [ ] Уведомления (email при регистрации / напоминание) +- [ ] Soft Deletes для мероприятий + +--- + +## 9. Дополнительные задания (для продвинутых) + +1. **Билеты:** генерация PDF-билета с QR-кодом при регистрации +2. **Уведомления:** email при регистрации и напоминание за день до мероприятия +3. **Онлайн-мероприятия:** интеграция с Zoom/Jitsi (сохранение ссылки, автоматическая отправка) +4. **Тесты:** PHPUnit Feature-тесты для EventController (регистрация, валидация вместимости) +5. **API:** RESTful API для каталога мероприятий +6. **Queue:** асинхронная отправка email-уведомлений +7. **Events & Listeners:** событие «регистрация на мероприятие» → отправка email + +--- + +## 10. Рекомендуемые материалы + +- Laravel Docs: https://laravel.com/docs/12.x +- Laravel File Uploads: https://laravel.com/docs/12.x/filesystem#file-uploads +- Bootstrap 5 Cards: https://getbootstrap.com/docs/5.3/components/card/ +- Bootstrap 5 Modal: https://getbootstrap.com/docs/5.3/components/modal/ +- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies diff --git a/task07-recipe-catalog.md b/task07-recipe-catalog.md new file mode 100644 index 0000000..9f3ee9b --- /dev/null +++ b/task07-recipe-catalog.md @@ -0,0 +1,400 @@ +# Техническое задание: Каталог рецептов с рейтингом + +## 1. Описание проекта + +Кулинарная платформа для публикации рецептов, комментирования и оценки блюд. Проект закрепляет навыки CRUD, отношений многие-ко-многим (рецепт ↔ ингредиенты), расчёта среднего рейтинга, разграничения прав и совместной работы через Git. + +**Команда:** 2–3 человека + +--- + +## 2. Функциональные требования + +### 2.1. Аутентификация и регистрация +- Регистрация, вход, выход (Laravel Breeze) + +### 2.2. Каталог рецептов +- Просмотр списка рецептов с пагинацией +- Фильтрация по категориям (завтрак, обед, ужин, выпечка, десерты, напитки) +- Поиск по названию и ингредиентам +- Сортировка по дате добавления, рейтингу, времени приготовления +- Детальная страница рецепта + +### 2.3. Управление рецептами (автор/админ) +- CRUD рецептов: название, описание, категория, время приготовления, количество порций, изображение, сложность +- Пошаговое приготовление (несколько шагов, каждое — отдельная запись) +- Привязка ингредиентов с количеством и единицей измерения +- Загрузка изображения рецепта (валидация: jpg/png, макс. 2MB) + +### 2.4. Ингредиенты +- Справочник ингредиентов (название, единица измерения по умолчанию) +- CRUD ингредиентов (админ) +- Привязка к рецептам (многие-ко-многим) с указанием количества и единицы + +### 2.5. Комментарии +- Добавление комментариев к рецепту +- Удаление своего комментария +- Удаление любого комментария (админ/автор рецепта) +- Пагинация комментариев + +### 2.6. Оценки и рейтинг +- Оценка рецепта (1–5 звёзд) — один голос на пользователя +- Отображение среднего рейтинга на карточке и странице рецепта +- Автоматический пересчёт среднего рейтинга + +### 2.7. Избранное +- Добавление рецепта в избранное +- Список избранных рецептов (личный кабинет) + +### 2.8. Личный кабинет автора +- Мои рецепты +- Статистика: количество рецептов, средний рейтинг, количество комментариев +- Избранные рецепты + +### 2.9. Панель администратора +- Управление пользователями и ролями +- CRUD ингредиентов (справочник) +- Модерация рецептов и комментариев +- Общая статистика + +--- + +## 3. Структура базы данных + +### Таблица `users` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Имя | +| email | VARCHAR(255) | Email (unique) | +| password | VARCHAR(255) | Хеш пароля | +| role | ENUM('admin', 'author', 'user') | Роль | +| avatar | VARCHAR(255) \| NULL | Аватар | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `categories` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название категории | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `recipes` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| title | VARCHAR(255) | Название рецепта | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| description | TEXT | Краткое описание | +| image | VARCHAR(255) \| NULL | Изображение | +| category_id | BIGINT UNSIGNED | Категория (FK → categories) | +| author_id | BIGINT UNSIGNED | Автор (FK → users) | +| cooking_time | INT | Время приготовления (минуты) | +| servings | INT | Количество порций | +| difficulty | ENUM('easy', 'medium', 'hard') | Сложность | +| rating | DECIMAL(3, 2) \| NULL | Средний рейтинг (0.00–5.00) | +| rating_count | INT | Количество оценок | +| is_published | BOOLEAN | Опубликован ли | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `recipe_steps` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| recipe_id | BIGINT UNSIGNED | Рецепт (FK → recipes) | +| step_number | INT | Порядковый номер шага | +| description | TEXT | Описание шага | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `ingredients` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название ингредиента (unique) | +| default_unit | VARCHAR(50) | Единица измерения по умолчанию (г, мл, шт, ст.л.) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `ingredient_recipe` (многие-ко-многим) +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| recipe_id | BIGINT UNSIGNED | FK → recipes | +| ingredient_id | BIGINT UNSIGNED | FK → ingredients | +| quantity | DECIMAL(10, 2) | Количество | +| unit | VARCHAR(50) | Единица измерения | + +### Таблица `comments` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| body | TEXT | Текст комментария | +| user_id | BIGINT UNSIGNED | Автор (FK → users) | +| recipe_id | BIGINT UNSIGNED | Рецепт (FK → recipes) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `ratings` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| user_id | BIGINT UNSIGNED | FK → users | +| recipe_id | BIGINT UNSIGNED | FK → recipes | +| rating | TINYINT | Оценка (1–5) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | + +### Таблица `recipe_user` (избранное) +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| user_id | BIGINT UNSIGNED | FK → users | +| recipe_id | BIGINT UNSIGNED | FK → recipes | +| added_at | TIMESTAMP | | + +--- + +## 4. Маршруты и контроллеры + +```php +// Публичные маршруты +Route::get('/', [RecipeController::class, 'index'])->name('recipes.index'); +Route::get('/recipes/{recipe:slug}', [RecipeController::class, 'show'])->name('recipes.show'); + +// Аутентифицированные маршруты +Route::middleware('auth')->group(function () { + // Оценки + Route::post('/recipes/{recipe}/rate', [RatingController::class, 'store'])->name('recipes.rate'); + + // Избранное + Route::get('/favorites', [FavoriteController::class, 'index'])->name('favorites.index'); + Route::post('/recipes/{recipe}/favorite', [FavoriteController::class, 'toggle'])->name('recipes.favorite'); + + // Комментарии + Route::resource('recipes.comments', CommentController::class)->scoped(['recipes' => 'recipe'])->only(['store', 'destroy']); + + // Мои рецепты (автор) + Route::prefix('my-recipes')->name('my-recipes.')->middleware('can:be-author')->group(function () { + Route::resource('recipes', AuthorRecipeController::class); + Route::get('dashboard', [AuthorDashboardController::class, 'index'])->name('dashboard'); + }); + + // Админка + Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { + Route::resource('users', AdminUserController::class); + Route::resource('ingredients', AdminIngredientController::class); + Route::patch('recipes/{recipe}/toggle-publish', [AdminRecipeController::class, 'togglePublish'])->name('recipes.publish'); + Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); + }); +}); +``` + +### Контроллеры +- `RecipeController` — публичный каталог и детальный просмотр +- `AuthorRecipeController` — CRUD рецептов автором +- `RatingController` — добавление/обновление оценки +- `FavoriteController` — управление избранным +- `CommentController` — создание/удаление комментариев +- `AuthorDashboardController` — панель автора +- `AdminUserController`, `AdminIngredientController`, `AdminRecipeController`, `AdminDashboardController` — админка + +--- + +## 5. Роли, Gates и Policies + +### Роли +| Роль | Описание | +|---|---| +| `admin` | Полный доступ, модерация, CRUD ингредиентов, управление пользователями | +| `author` | Создание/редактирование своих рецептов, управление комментариями к своим рецептам | +| `user` | Просмотр каталога, комментирование, оценка, добавление в избранное | + +### Gates + +```php +Gate::define('be-author', function (User $user) { + return in_array($user->role, ['admin', 'author']); +}); + +Gate::define('access-admin-panel', function (User $user) { + return $user->role === 'admin'; +}); +``` + +### Policies + +**RecipePolicy:** +- `view` — все (если опубликован), автор/admin (если не опубликован) +- `create` — author или admin +- `update`, `delete` — автор рецепта или admin +- `rate` — user (ещё не оценивал этот рецепт) + +**CommentPolicy:** +- `create` — все аутентифицированные +- `delete` — автор комментария, автор рецепта или admin + +**IngredientPolicy:** +- `view` — все +- `create`, `update`, `delete` — только admin + +--- + +## 6. Требования к интерфейсу (Bootstrap 5) + +### Компоненты +- **Навбар** — логотип, каталог, «Избранное», «Мои рецепты», профиль +- **Карточки рецептов** — изображение, название, категория (бейдж), время (иконка + текст), рейтинг (звёзды) +- **Страница рецепта** — большое изображение, описание, ингредиенты (таблица), шаги приготовления (нумерованный список), комментарии, форма оценки +- **Звёздный рейтинг** — интерактивные звёзды (radio buttons + стилизация) +- **Форма рецепта** — Bootstrap-форма с загрузкой изображения, динамическое добавление шагов и ингредиентов +- **Таблица ингредиентов** — название, количество, единица измерения +- **Дашборд автора** — статистика (карточки), список моих рецептов +- **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента и сайдбара + +### Цветовая схема сложности +- Лёгкий — `bg-success` +- Средний — `bg-warning` +- Сложный — `bg-danger` + +### Цветовая схема категорий +- Завтрак — `bg-info` +- Обед — `bg-primary` +- Ужин — `bg-dark` +- Выпечка — `bg-warning` +- Десерты — `bg-pink` +- Напитки — `bg-light text-dark` + +--- + +## 7. Git-workflow для команды + +### Распределение модулей (для 2 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Рецепты, категории, шаги, ингредиенты, каталог | `feature/recipes-crud`, `feature/categories`, `feature-recipe-steps` | +| **Участник B** | Оценки, комментарии, избранное, дашборд, UI | `feature-ingredients`, `feature-recipe-ingredients-m2m`, `feature-ratings` | + +### Распределение модулей (для 3 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Рецепты, категории, шаги приготовления, загрузка изображений | `feature/recipes-crud`, `feature/categories`, `feature-recipe-steps` | +| **Участник B** | Ингредиенты, многие-ко-многим, оценки, расчёт рейтинга | `feature-ingredients`, `feature-recipe-ingredients-m2m`, `feature-ratings` | +| **Участник C** | Комментарии, избранное, каталог, UI, дашборд | `feature-comments`, `feature-favorites`, `feature-recipe-catalog-ui` | + +### Правила +1. Ветка `develop` — основная ветка разработки +2. Каждый участник создаёт фич-ветки от `develop` +3. Минимум 3 PR на участника +4. Обязательный код-ревью перед мёржем +5. Conventional Commits + +### Визуализация workflow (2 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/recipes-crud + checkout develop + branch feature-ingredients + checkout feature/recipes-crud + commit id: "feat: add recipes CRUD" + commit id: "feat: add recipe steps" + checkout feature-ingredients + commit id: "feat: add ingredients m2m" + commit id: "feat: add ratings system" + checkout develop + merge feature/recipes-crud tag: "PR + review" + merge feature-ingredients tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +### Визуализация workflow (3 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/recipes-crud + checkout develop + branch feature-ingredients + checkout develop + branch feature-comments + checkout feature/recipes-crud + commit id: "feat: add recipes CRUD" + commit id: "feat: add recipe categories" + checkout feature-ingredients + commit id: "feat: add ingredients m2m" + commit id: "feat: add ratings system" + checkout feature-comments + commit id: "feat: add recipe comments" + commit id: "feat: add favorites system" + checkout develop + merge feature/recipes-crud tag: "PR + review" + merge feature-ingredients tag: "PR + review" + merge feature-comments tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +--- + +## 8. Критерии приёмки + +### Обязательно +- [ ] CRUD рецептов с загрузкой изображений +- [ ] CRUD категорий рецептов +- [ ] Шаги приготовления (CRUD внутри рецепта) +- [ ] Справочник ингредиентов (CRUD, только админ) +- [ ] Привязка ингредиентов к рецептам (многие-ко-многим с количеством и единицей) +- [ ] Оценка рецепта (1–5 звёзд, один голос на пользователя) +- [ ] Автоматический расчёт среднего рейтинга +- [ ] Комментарии к рецептам (создание + удаление) +- [ ] Избранное (добавление/удаление) +- [ ] Панель автора (мои рецепты, статистика) +- [ ] Policies: пользователь не может редактировать чужие рецепты +- [ ] Адаптивная Bootstrap-вёрстка +- [ ] Flash-сообщения +- [ ] Пагинация каталога и комментариев +- [ ] Git-история: минимум 3 ветки на участника, PR с ревью + +### Дополнительно (бонусные баллы) +- [ ] Поиск рецептов по названию и ингредиентам +- [ ] Фильтрация по времени приготовления и сложности +- [ ] Сортировка по рейтингу, дате, времени приготовления +- [ ] Печать рецепта (кнопка «Распечатать» → print-friendly страница) +- [ ] Мягкое удаление рецептов и комментариев +- [ ] Тесты: PHPUnit Feature-тесты для RatingController +- [ ] API: RESTful API для каталога рецептов + +--- + +## 9. Дополнительные задания (для продвинутых) + +1. **Масштабирование рецепта:** калькулятор для пересчёта ингредиентов на другое количество порций +2. **Планировщик питания:** календарь с рецептами на неделю +3. **Коллекции:** пользовательские подборки рецептов (как плейлисты) +4. **Тесты:** PHPUnit Feature-тесты для RecipeController и RatingController +5. **API:** RESTful API для каталога и создания рецептов +6. **Queue:** асинхронный пересчёт рейтинга при большом количестве оценок +7. **Events & Listeners:** событие «новая оценка» → пересчёт рейтинга + +--- + +## 10. Рекомендуемые материалы + +- Laravel Docs: https://laravel.com/docs/12.x +- Laravel Many-to-Many with Pivot: https://laravel.com/docs/12.x/eloquent-relationships#many-to-many +- Bootstrap 5 Cards: https://getbootstrap.com/docs/5.3/components/card/ +- Laravel Aggregates: https://laravel.com/docs/12.x/queries#aggregates +- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies diff --git a/task08-inventory-management.md b/task08-inventory-management.md new file mode 100644 index 0000000..e5f593a --- /dev/null +++ b/task08-inventory-management.md @@ -0,0 +1,443 @@ +# Техническое задание: Система управления складом (Inventory Management) + +## 1. Описание проекта + +Веб-приложение для учёта товаров на складе с контролем прихода и расхода, управлением поставщиками и формированием отчётов. Проект закрепляет навыки CRUD, вложенных моделей (накладная → позиции), транзакций БД, разграничения прав и совместной работы через Git. + +**Команда:** 2–3 человека + +--- + +## 2. Функциональные требования + +### 2.1. Аутентификация и регистрация +- Регистрация, вход, выход (Laravel Breeze) +- Регистрация только через администратора (без публичной регистрации) + +### 2.2. Справочники +- **Товары:** артикул, название, описание, категория, единица измерения, текущий остаток +- **Категории товаров:** название, описание +- **Поставщики:** название, контакт, телефон, email, адрес +- **Единицы измерения:** название (шт, кг, л, м, уп.) + +### 2.3. Управление товарами +- CRUD товаров: артикул (уникальный), название, описание, категория, единица измерения +- Привязка к категории +- Просмотр текущего остатка по каждому товару +- Поиск по артикулу и названию + +### 2.4. Приходные накладные +- Создание приходной накладной: дата, поставщик, комментарий +- Позиции приходной накладной: товар, количество, цена за единицу +- **Автоматическое увеличение остатков** товаров при проведении накладной +- Статусы накладной: черновик, проведена, отменена +- Отмена приходной накладной (уменьшение остатков) + +### 2.5. Расходные накладные +- Создание расходной накладной: дата, получатель (отдел/сотрудник), комментарий +- Позиции расходной накладной: товар, количество +- **Валидация:** количество не превышает текущий остаток на складе +- **Автоматическое уменьшение остатков** при проведении накладной +- Статусы накладной: черновик, проведена, отменена +- Отмена расходной накладной (возврат остатков) + +### 2.6. Остатки на складе +- Таблица остатков: товар, категория, текущее количество, единица измерения +- Фильтрация по категории +- Индикатор критического остатка (ниже минимального порога) +- Сортировка по названию, количеству + +### 2.7. История операций +- Журнал всех приходных и расходных накладных +- Фильтрация по типу (приход/расход), дате, поставщику/получателю +- Детали накладной (список позиций) + +### 2.8. Отчёты +- Отчёт по остаткам (сводная таблица) +- Отчёт по приходам за период +- Отчёт по расходам за период +- Отчёт по поставщикам (сумма закупок) + +### 2.9. Панель кладовщика +- Быстрое создание приходной/расходной накладной +- Текущие остатки с индикаторами +- Последние операции + +### 2.10. Панель менеджера +- Просмотр каталога товаров и остатков +- Просмотр истории операций +- Формирование отчётов (без права создания накладных) + +### 2.11. Панель администратора +- Управление пользователями и ролями +- Управление справочниками (категории, поставщики, единицы измерения) +- Полный доступ ко всем операциям + +--- + +## 3. Структура базы данных + +### Таблица `users` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Имя | +| email | VARCHAR(255) | Email (unique) | +| password | VARCHAR(255) | Хеш пароля | +| role | ENUM('admin', 'storekeeper', 'manager') | Роль | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `categories` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название категории | +| description | TEXT \| NULL | Описание | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `units` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(50) | Название единицы (шт, кг, л, м, уп.) | +| abbreviation | VARCHAR(10) | Сокращение (unique) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `products` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| sku | VARCHAR(50) | Артикул (unique) | +| name | VARCHAR(255) | Название | +| description | TEXT \| NULL | Описание | +| category_id | BIGINT UNSIGNED | Категория (FK → categories) | +| unit_id | BIGINT UNSIGNED | Единица измерения (FK → units) | +| min_stock | INT \| NULL | Минимальный порог остатка | +| stock_quantity | INT | Текущий остаток (по умолчанию 0) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `suppliers` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название поставщика | +| contact_person | VARCHAR(255) \| NULL | Контактное лицо | +| phone | VARCHAR(20) \| NULL | Телефон | +| email | VARCHAR(255) \| NULL | Email | +| address | TEXT \| NULL | Адрес | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `incoming_invoices` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| invoice_number | VARCHAR(50) | Номер накладной (unique) | +| invoice_date | DATE | Дата накладной | +| supplier_id | BIGINT UNSIGNED | Поставщик (FK → suppliers) | +| user_id | BIGINT UNSIGNED | Создатель (FK → users) | +| comment | TEXT \| NULL | Комментарий | +| status | ENUM('draft', 'posted', 'cancelled') | Статус | +| total_amount | DECIMAL(12, 2) | Общая сумма | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `incoming_invoice_items` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| incoming_invoice_id | BIGINT UNSIGNED | FK → incoming_invoices | +| product_id | BIGINT UNSIGNED | FK → products | +| quantity | DECIMAL(10, 2) | Количество | +| unit_price | DECIMAL(10, 2) | Цена за единицу | +| total | DECIMAL(10, 2) | Сумма позиции | + +### Таблица `outgoing_invoices` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| invoice_number | VARCHAR(50) | Номер накладной (unique) | +| invoice_date | DATE | Дата накладной | +| recipient | VARCHAR(255) | Получатель (отдел/сотрудник) | +| user_id | BIGINT UNSIGNED | Создатель (FK → users) | +| comment | TEXT \| NULL | Комментарий | +| status | ENUM('draft', 'posted', 'cancelled') | Статус | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `outgoing_invoice_items` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| outgoing_invoice_id | BIGINT UNSIGNED | FK → outgoing_invoices | +| product_id | BIGINT UNSIGNED | FK → products | +| quantity | DECIMAL(10, 2) | Количество | + +--- + +## 4. Маршруты и контроллеры + +```php +// Аутентифицированные маршруты +Route::middleware('auth')->group(function () { + // Дашборд (по умолчанию) + Route::get('/', [DashboardController::class, 'index'])->name('dashboard'); + + // Остатки (все авторизованные) + Route::get('/stock', [StockController::class, 'index'])->name('stock.index'); + + // Справочники (все авторизованные) + Route::get('/products', [ProductController::class, 'index'])->name('products.index'); + Route::get('/products/{product}', [ProductController::class, 'show'])->name('products.show'); + + // Приходные накладные (кладовщик + админ) + Route::prefix('incoming')->name('incoming.')->middleware('can:manage-inventory')->group(function () { + Route::resource('invoices', IncomingInvoiceController::class); + Route::post('invoices/{invoice}/post', [IncomingInvoiceController::class, 'post'])->name('invoices.post'); + Route::post('invoices/{invoice}/cancel', [IncomingInvoiceController::class, 'cancel'])->name('invoices.cancel'); + }); + + // Расходные накладные (кладовщик + админ) + Route::prefix('outgoing')->name('outgoing.')->middleware('can:manage-inventory')->group(function () { + Route::resource('invoices', OutgoingInvoiceController::class); + Route::post('invoices/{invoice}/post', [OutgoingInvoiceController::class, 'post'])->name('invoices.post'); + Route::post('invoices/{invoice}/cancel', [OutgoingInvoiceController::class, 'cancel'])->name('invoices.cancel'); + }); + + // История операций (все авторизованные) + Route::get('/history', [HistoryController::class, 'index'])->name('history.index'); + Route::get('/history/incoming/{invoice}', [HistoryController::class, 'incomingDetail'])->name('history.incoming'); + Route::get('/history/outgoing/{invoice}', [HistoryController::class, 'outgoingDetail'])->name('history.outgoing'); + + // Отчёты (менеджер + админ) + Route::prefix('reports')->name('reports.')->middleware('can:view-reports')->group(function () { + Route::get('/stock', [ReportController::class, 'stock'])->name('stock'); + Route::get('/incoming', [ReportController::class, 'incoming'])->name('incoming'); + Route::get('/outgoing', [ReportController::class, 'outgoing'])->name('outgoing'); + Route::get('/suppliers', [ReportController::class, 'suppliers'])->name('suppliers'); + }); + + // Админка + Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { + Route::resource('users', AdminUserController::class); + Route::resource('categories', AdminCategoryController::class); + Route::resource('suppliers', AdminSupplierController::class); + Route::resource('units', AdminUnitController::class); + Route::resource('products', AdminProductController::class); + Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); + }); +}); +``` + +### Контроллеры +- `DashboardController` — общий дашборд +- `StockController` — таблица остатков +- `ProductController` — просмотр каталога товаров +- `IncomingInvoiceController` — CRUD приходных накладных + проведение/отмена +- `OutgoingInvoiceController` — CRUD расходных накладных + проведение/отмена +- `HistoryController` — журнал операций +- `ReportController` — отчёты +- `AdminUserController`, `AdminCategoryController`, `AdminSupplierController`, `AdminUnitController`, `AdminProductController`, `AdminDashboardController` — админка + +--- + +## 5. Роли, Gates и Policies + +### Роли +| Роль | Описание | +|---|---| +| `admin` | Полный доступ, управление пользователями и справочниками | +| `storekeeper` | Создание/проведение приходных и расходных накладных, управление остатками | +| `manager` | Просмотр каталога, остатков, истории, формирование отчётов (без права создания накладных) | + +### Gates + +```php +Gate::define('manage-inventory', function (User $user) { + return in_array($user->role, ['admin', 'storekeeper']); +}); + +Gate::define('view-reports', function (User $user) { + return in_array($user->role, ['admin', 'manager', 'storekeeper']); +}); + +Gate::define('access-admin-panel', function (User $user) { + return $user->role === 'admin'; +}); +``` + +### Policies + +**IncomingInvoicePolicy:** +- `view` — все авторизованные +- `create`, `update` — storekeeper или admin +- `post`, `cancel` — storekeeper или admin +- `delete` — только admin + +**OutgoingInvoicePolicy:** +- `view` — все авторизованные +- `create`, `update` — storekeeper или admin +- `post`, `cancel` — storekeeper или admin +- `delete` — только admin + +**ProductPolicy:** +- `view` — все авторизованные +- `create`, `update`, `delete` — только admin + +**SupplierPolicy, CategoryPolicy, UnitPolicy:** +- `view` — все авторизованные +- `create`, `update`, `delete` — только admin + +--- + +## 6. Требования к интерфейсу (Bootstrap 5) + +### Компоненты +- **Навбар** — логотип, остатки, приход, расход, история, отчёты, профиль +- **Таблица остатков** — товар, категория, количество (цветовой индикатор: зелёный/жёлтый/красный), единица +- **Форма накладной** — дата, поставщик/получатель, динамическое добавление позиций (JS), итоговая сумма +- **Таблица позиций** — товар, количество, цена, сумма (для прихода) +- **Карточки дашборда** — последние операции, критические остатки, быстрые действия +- **Бейджи статусов** — черновик (`bg-secondary`), проведена (`bg-success`), отменена (`bg-danger`) +- **Дашборд** — `row` + `col-md-*` карточки статистики +- **Адаптивная сетка** — таблицы с `table-responsive` + +### Цветовая схема остатков +- Норма — `table-success` (зелёный фон) +- Ниже минимума — `table-warning` (жёлтый фон) +- Критический (0) — `table danger` (красный фон) + +--- + +## 7. Git-workflow для команды + +### Распределение модулей (для 2 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Справочники, товары, поставщики, остатки | `feature-products`, `feature-categories`, `feature-suppliers` | +| **Участник B** | Приходные/расходные накладные, история, отчёты | `feature-incoming-invoices`, `feature-stock-update`, `feature-db-transactions` | + +### Распределение модулей (для 3 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Справочники (товары, категории, поставщики, единицы) | `feature-products`, `feature-categories`, `feature-suppliers` | +| **Участник B** | Приходные накладные, обновление остатков, транзакции | `feature-incoming-invoices`, `feature-stock-update`, `feature-db-transactions` | +| **Участник C** | Расходные накладные, история, отчёты, UI | `feature-outgoing-invoices`, `feature-history`, `feature-reports` | + +### Правила +1. Ветка `develop` — основная ветка разработки +2. Каждый участник создаёт фич-ветки от `develop` +3. Минимум 3 PR на участника +4. Обязательный код-ревью перед мёржем +5. Conventional Commits + +### Визуализация workflow (2 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature-products + checkout develop + branch feature-incoming-invoices + checkout feature-products + commit id: "feat: add products CRUD" + commit id: "feat: add suppliers CRUD" + checkout feature-incoming-invoices + commit id: "feat: add incoming invoices" + commit id: "feat: add stock transactions" + checkout develop + merge feature-products tag: "PR + review" + merge feature-incoming-invoices tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +### Визуализация workflow (3 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature-products + checkout develop + branch feature-incoming-invoices + checkout develop + branch feature-outgoing-invoices + checkout feature-products + commit id: "feat: add products CRUD" + commit id: "feat: add categories CRUD" + checkout feature-incoming-invoices + commit id: "feat: add incoming invoices" + commit id: "feat: add stock transactions" + checkout feature-outgoing-invoices + commit id: "feat: add outgoing invoices" + commit id: "feat: add reports" + checkout develop + merge feature-products tag: "PR + review" + merge feature-incoming-invoices tag: "PR + review" + merge feature-outgoing-invoices tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +--- + +## 8. Критерии приёмки + +### Обязательно +- [ ] CRUD товаров с уникальным артикулом +- [ ] CRUD категорий, поставщиков, единиц измерения +- [ ] Создание приходной накладной с позициями (товар + количество + цена) +- [ ] Создание расходной накладной с позициями (товар + количество) +- [ ] **Валидация расхода:** количество ≤ текущий остаток +- [ ] **Автоматическое обновление остатков** при проведении приходной/расходной накладной +- [ ] **Транзакции БД:** обновление остатков и создание позиций — атомарная операция +- [ ] Отмена накладной с обратным пересчётом остатков +- [ ] Таблица остатков с фильтрацией по категории +- [ ] История всех операций +- [ ] Отчёты (остатки, приходы, расходы, поставщики) +- [ ] Policies: менеджер не может создавать накладные +- [ ] Адаптивная Bootstrap-вёрстка +- [ ] Flash-сообщения +- [ ] Пагинация таблиц +- [ ] Git-история: минимум 3 ветки на участника, PR с ревью + +### Дополнительно (бонусные баллы) +- [ ] Печать накладной (print-friendly страница) +- [ ] Экспорт отчётов в CSV +- [ ] Индикатор критических остатков на дашборде +- [ ] Поиск товаров по артикулу/названию +- [ ] Мягкое удаление товаров и накладных +- [ ] Тесты: PHPUnit Feature-тесты для IncomingInvoiceController +- [ ] API: RESTful API для остатков и товаров + +--- + +## 9. Дополнительные задания (для продвинутых) + +1. **Автоматические оповещения:** email при достижении минимального порога остатка +2. **Инвентаризация:** сверка фактических остатков с системными, корректировка +3. **Перемещения:** накладные перемещения между складами (несколько складов) +4. **Тесты:** PHPUnit Feature-тесты для транзакций БД (построение/отмена накладной) +5. **API:** RESTful API для остатков и создания накладных +6. **Queue:** асинхронная генерация отчётов в CSV +7. **Events & Listeners:** событие «проведение накладной» → обновление остатков + логирование + +--- + +## 10. Рекомендуемые материалы + +- Laravel Docs: https://laravel.com/docs/12.x +- Laravel Database Transactions: https://laravel.com/docs/12.x/database#database-transactions +- Laravel Form Collections (динамические поля): https://laravel.com/docs/12.x/requests +- Bootstrap 5 Tables: https://getbootstrap.com/docs/5.3/content/tables/ +- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies diff --git a/task09-book-exchange.md b/task09-book-exchange.md new file mode 100644 index 0000000..b5f1137 --- /dev/null +++ b/task09-book-exchange.md @@ -0,0 +1,388 @@ +# Техническое задание: Платформа для обмена книгами (Book Exchange) + +## 1. Описание проекта + +Веб-сервис для обмена книгами между пользователями с системой Wishlist, обменными предложениями и отзывами. Проект закрепляет навыки CRUD, статусных переходов, работы с отношениями многие-ко-многим, разграничения прав и совместной работы через Git. + +**Команда:** 2–3 человека + +--- + +## 2. Функциональные требования + +### 2.1. Аутентификация и регистрация +- Регистрация, вход, выход (Laravel Breeze) + +### 2.2. Каталог книг +- Просмотр списка доступных для обмена книг с пагинацией +- Фильтрация по жанрам/категориям +- Поиск по названию, автору, ISBN +- Детальная страница книги (обложка, описание, владелец, состояние, кнопка «Предложить обмен») + +### 2.3. Управление книгами (владелец) +- CRUD книг: название, автор, ISBN, описание, жанр, обложка, состояние (новая, хорошая, удовлетворительная) +- Загрузка обложки книги (валидация: jpg/png, макс. 2MB) +- Статус книги: доступна для обмена / зарезервирована / обмен состоялась +- Мои книги (личный кабинет владельца) + +### 2.4. Wishlist (список желаемого) +- Добавление книг в wishlist: название, автор (свободный ввод) +- Уведомление владельцу книги из wishlist, если кто-то её добавил +- Страница «Мой Wishlist» + +### 2.5. Обменные предложения +- Пользователь создаёт предложение обмена: книга владельца → книга взамен (из книг отправителя) + комментарий +- Статусы обмена: предложено, принято, отклонено, завершено, отменено +- Владелец книги принимает или отклоняет предложение +- При принятии — обе книги получают статус «обмен состоялась» +- При отмене — возврат статусов «доступна для обмена» +- Отзывы после завершения обмена + +### 2.6. Отзывы об обмене +- Оба участника оставляют отзыв друг другу после завершения обмена +- Поля отзыва: текст, рейтинг (1–5 звёзд) +- Средний рейтинг пользователя +- Отзывы на странице профиля + +### 2.7. Панель пользователя +- Мои книги (созданные мной) +- Мои обменные предложения (отправленные и полученные) +- Мой Wishlist +- Мои отзывы (полученные и оставленные) +- Профиль и рейтинг + +### 2.8. Панель администратора +- Управление пользователями и ролями +- Модерация книг и отзывов +- Общая статистика платформы + +--- + +## 3. Структура базы данных + +### Таблица `users` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Имя | +| email | VARCHAR(255) | Email (unique) | +| password | VARCHAR(255) | Хеш пароля | +| role | ENUM('admin', 'owner', 'requester') | Роль | +| avatar | VARCHAR(255) \| NULL | Аватар | +| rating | DECIMAL(3, 2) \| NULL | Средний рейтинг (0.00–5.00) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `genres` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название жанра | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `books` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| title | VARCHAR(255) | Название книги | +| author | VARCHAR(255) | Автор | +| isbn | VARCHAR(20) \| NULL | ISBN | +| description | TEXT \| NULL | Описание | +| cover_image | VARCHAR(255) \| NULL | Обложка | +| genre_id | BIGINT UNSIGNED | Жанр (FK → genres) | +| owner_id | BIGINT UNSIGNED | Владелец (FK → users) | +| condition | ENUM('new', 'good', 'fair') | Состояние | +| status | ENUM('available', 'reserved', 'exchanged') | Статус | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `wishlists` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| user_id | BIGINT UNSIGNED | FK → users | +| title | VARCHAR(255) | Желаемая книга (название) | +| author | VARCHAR(255) \| NULL | Желаемый автор | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `exchange_offers` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| requester_id | BIGINT UNSIGNED | Инициатор обмена (FK → users) | +| owner_book_id | BIGINT UNSIGNED | Книга владельца (FK → books) | +| offered_book_id | BIGINT UNSIGNED | Предлагаемая книга взамен (FK → books) | +| message | TEXT \| NULL | Комментарий инициатора | +| status | ENUM('proposed', 'accepted', 'rejected', 'completed', 'cancelled') | Статус | +| response_message | TEXT \| NULL | Ответ владельца | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `reviews` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| exchange_offer_id | BIGINT UNSIGNED | Связанный обмен (FK → exchange_offers) | +| author_id | BIGINT UNSIGNED | Автор отзыва (FK → users) | +| target_user_id | BIGINT UNSIGNED | Получатель отзыва (FK → users) | +| rating | TINYINT | Рейтинг (1–5) | +| comment | TEXT | Текст отзыва | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +--- + +## 4. Маршруты и контроллеры + +```php +// Публичные маршруты +Route::get('/', [BookController::class, 'index'])->name('books.index'); +Route::get('/books/{book}', [BookController::class, 'show'])->name('books.show'); + +// Аутентифицированные маршруты +Route::middleware('auth')->group(function () { + // Wishlist + Route::resource('wishlist', WishlistController::class); + + // Обменные предложения + Route::prefix('exchanges')->name('exchanges.')->group(function () { + Route::post('/offer/{book}', [ExchangeController::class, 'store'])->name('offer'); + Route::get('/sent', [ExchangeController::class, 'sent'])->name('sent'); + Route::get('/received', [ExchangeController::class, 'received'])->name('received'); + Route::get('/{exchange}', [ExchangeController::class, 'show'])->name('show'); + Route::post('/{exchange}/accept', [ExchangeController::class, 'accept'])->name('accept'); + Route::post('/{exchange}/reject', [ExchangeController::class, 'reject'])->name('reject'); + Route::post('/{exchange}/complete', [ExchangeController::class, 'complete'])->name('complete'); + Route::post('/{exchange}/cancel', [ExchangeController::class, 'cancel'])->name('cancel'); + }); + + // Отзывы + Route::resource('reviews', ReviewController::class)->only(['store', 'destroy']); + + // Мои книги (владелец) + Route::prefix('my-books')->name('my-books.')->group(function () { + Route::resource('books', BookController::class); + Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard'); + }); + + // Админка + Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { + Route::resource('users', AdminUserController::class); + Route::resource('genres', AdminGenreController::class); + Route::patch('books/{book}/toggle-status', [AdminBookController::class, 'toggleStatus'])->name('books.status'); + Route::patch('reviews/{review}/toggle-publish', [AdminReviewController::class, 'togglePublish'])->name('reviews.publish'); + Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); + }); +}); +``` + +### Контроллеры +- `BookController` — публичный каталог + CRUD книг для владельца +- `WishlistController` — CRUD желаемых книг +- `ExchangeController` — создание и управление обменными предложениями +- `ReviewController` — создание/удаление отзывов +- `DashboardController` — панель пользователя +- `AdminUserController`, `AdminGenreController`, `AdminBookController`, `AdminReviewController`, `AdminDashboardController` — админка + +--- + +## 5. Роли, Gates и Policies + +### Роли +| Роль | Описание | +|---|---| +| `admin` | Полный доступ, модерация книг и отзывов, управление пользователями | +| `owner` | Добавление книг, управление обменными предложениями, подтверждение обмена | +| `requester` | Просмотр каталога, создание запросов на обмен, Wishlist | + +> **Примечание:** при регистрации пользователь получает обе роли `owner` и `requester` (фактически — роль `user`). Разделение логическое, а не через middleware. + +### Gates + +```php +Gate::define('access-admin-panel', function (User $user) { + return $user->role === 'admin'; +}); +``` + +### Policies + +**BookPolicy:** +- `view` — все (публичный каталог) +- `create` — все аутентифицированные +- `update`, `delete` — владелец книги или admin +- `offer` — не владелец этой книги (и не предлагает свою же) + +**ExchangeOfferPolicy:** +- `create` — все аутентифицированные (не владелец целевой книги) +- `view` — инициатор, владелец книги или admin +- `accept`, `reject` — владелец целевой книги +- `complete`, `cancel` — инициатор или владелец (до завершения) + +**WishlistPolicy:** +- `create`, `update`, `delete` — владелец wishlist или admin +- `view` — только владелец wishlist + +**ReviewPolicy:** +- `create` — участник завершённого обмена (ещё не оставил отзыв) +- `view` — все (если опубликован), автор или admin (если не опубликован) +- `delete` — автор отзыва или admin + +--- + +## 6. Требования к интерфейсу (Bootstrap 5) + +### Компоненты +- **Навбар** — логотип, каталог, «Мои книги», «Обмены», «Wishlist», профиль +- **Карточки книг** — обложка, название, автор, жанр (бейдж), состояние (бейдж), владелец +- **Страница книги** — большая обложка, описание, автор, ISBN, состояние, кнопка «Предложить обмен» +- **Форма обменного предложения** — выбор своей книги взамен (dropdown), комментарий +- **Таблица обменных предложений** — книги, статус (цветные бейджи), действия +- **Звёздный рейтинг** — для отзывов +- **Wishlist** — список желаемых книг (таблица/карточки) +- **Дашборд** — статистика (карточки), активные обмены, wishlist-совпадения +- **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента + +### Цветовая схема статусов обмена +- Предложено — `bg-info` +- Принято — `bg-success` +- Отклонено — `bg-danger` +- Завершено — `bg-primary` +- Отменено — `bg-secondary` + +### Цветовая схема состояния книги +- Новая — `bg-success` +- Хорошая — `bg-primary` +- Удовлетворительная — `bg-warning` + +--- + +## 7. Git-workflow для команды + +### Распределение модулей (для 2 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Книги, жанры, обложки, обменные предложения | `feature/books-crud`, `feature-genres`, `feature-book-covers` | +| **Участник B** | Wishlist, отзывы, рейтинги, дашборд, UI | `feature-exchange-offers`, `feature-offer-statuses`, `feature-offer-validation` | + +### Распределение модулей (для 3 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Книги, жанры, загрузка обложек, каталог | `feature/books-crud`, `feature-genres`, `feature-book-covers` | +| **Участник B** | Обменные предложения, статусы, валидация | `feature-exchange-offers`, `feature-offer-statuses`, `feature-offer-validation` | +| **Участник C** | Wishlist, отзывы, рейтинги, UI, дашборд | `feature-wishlist`, `feature-reviews`, `feature-dashboard-ui` | + +### Правила +1. Ветка `develop` — основная ветка разработки +2. Каждый участник создаёт фич-ветки от `develop` +3. Минимум 3 PR на участника +4. Обязательный код-ревью перед мёржем +5. Conventional Commits + +### Визуализация workflow (2 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/books-crud + checkout develop + branch feature-exchange-offers + checkout feature/books-crud + commit id: "feat: add books CRUD" + commit id: "feat: add book covers" + checkout feature-exchange-offers + commit id: "feat: add exchange offers" + commit id: "feat: add offer statuses" + checkout develop + merge feature/books-crud tag: "PR + review" + merge feature-exchange-offers tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +### Визуализация workflow (3 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/books-crud + checkout develop + branch feature-exchange-offers + checkout develop + branch feature-wishlist + checkout feature/books-crud + commit id: "feat: add books CRUD" + commit id: "feat: add genres CRUD" + checkout feature-exchange-offers + commit id: "feat: add exchange offers" + commit id: "feat: add offer statuses" + checkout feature-wishlist + commit id: "feat: add wishlist CRUD" + commit id: "feat: add reviews system" + checkout develop + merge feature/books-crud tag: "PR + review" + merge feature-exchange-offers tag: "PR + review" + merge feature-wishlist tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +--- + +## 8. Критерии приёмки + +### Обязательно +- [ ] CRUD книг с загрузкой обложек +- [ ] CRUD жанров +- [ ] Каталог книг с пагинацией и фильтрацией по жанрам +- [ ] Wishlist (добавление, просмотр, удаление) +- [ ] Создание обменного предложения (книга владельца → книга взамен) +- [ ] Принятие/отклонение предложения владельцем +- [ ] При принятии — обновление статусов обеих книг на «exchanged» +- [ ] При отмене — возврат статусов «available» +- [ ] Отзывы после завершения обмена (оба участника) +- [ ] Расчёт среднего рейтинга пользователя +- [ ] Policies: пользователь не может управлять чужими книгами и предложениями +- [ ] Адаптивная Bootstrap-вёрстка +- [ ] Flash-сообщения +- [ ] Git-история: минимум 3 ветки на участника, PR с ревью + +### Дополнительно (бонусные баллы) +- [ ] Поиск книг по названию, автору, ISBN +- [ ] Уведомления владельцу книги из wishlist +- [ ] Уведомления (email при новом предложении / ответе) +- [ ] Рекомендации книг (по жанру, wishlist) +- [ ] Мягкое удаление книг и отзывов +- [ ] Тесты: PHPUnit Feature-тесты для ExchangeController +- [ ] API: RESTful API для каталога книг + +--- + +## 9. Дополнительные задания (для продвинутых) + +1. **Уведомления:** email и on-site уведомления при новых предложениях, ответах, wishlist-совпадениях +2. **Совпадения Wishlist:** автоматическое определение, когда книга одного пользователя есть в wishlist другого +3. **Множественный обмен:** предложение нескольких книг взамен +4. **Тесты:** PHPUnit Feature-тесты для ExchangeController (статусные переходы) +5. **API:** RESTful API для каталога и создания предложений +6. **Queue:** асинхронная отправка email-уведомлений +7. **Events & Listeners:** событие «обмен завершён» → напоминание оставить отзыв + +--- + +## 10. Рекомендуемые материалы + +- Laravel Docs: https://laravel.com/docs/12.x +- Laravel Many-to-Many: https://laravel.com/docs/12.x/eloquent-relationships#many-to-many +- Bootstrap 5 Badges: https://getbootstrap.com/docs/5.3/components/badge/ +- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies +- State Machines (статусные переходы): https://github.com/brentroose/laravel-state-machine diff --git a/task10-pet-shelter.md b/task10-pet-shelter.md new file mode 100644 index 0000000..5af1bab --- /dev/null +++ b/task10-pet-shelter.md @@ -0,0 +1,432 @@ +# Техническое задание: Система помощи питомцам (Pet Shelter Management) + +## 1. Описание проекта + +Веб-платформа для приюта животных: каталог питомцев, заявки на усыновление, управление волонтёрами и историями лечения. Проект закрепляет навыки CRUD, загрузки файлов, статусных переходов, разграничения прав и совместной работы через Git. + +**Команда:** 2–3 человека + +--- + +## 2. Функциональные требования + +### 2.1. Аутентификация и регистрация +- Регистрация, вход, выход (Laravel Breeze) + +### 2.2. Каталог питомцев +- Просмотр списка питомцев с пагинацией +- Фильтрация по видам (кошки, собаки, грызуны, птицы, рептилии) +- Поиск по кличке, породе +- Детальная страница питомца (фото, описание, вид, порода, возраст, пол, статус, истории лечения) + +### 2.3. Управление питомцами (волонтёр/админ) +- CRUD питомцев: кличка, вид, порода, возраст (примерный), пол, описание, фото (до 3), статус (доступен для усыновления, на лечении, усыновлён) +- Загрузка фотографий питомца (валидация: jpg/png, макс. 2MB на каждое фото) +- Особенности характера (свободный текст или теги) + +### 2.4. Категории питомцев +- CRUD категорий (видов): кошки, собаки, грызуны, птицы, рептилии +- Описание вида, особенности содержания + +### 2.5. Заявки на усыновление +- Форма заявки: данные заявителя (ФИО, телефон, email, адрес), опыт содержания, цель усыновления, условия содержания +- Привязка заявки к питомцу +- Статусы заявки: новая, на рассмотрении, одобрена, отклонена, завершена +- Волонтёр меняет статус заявки (новая → на рассмотрении → одобрена/отклонена) +- При одобрении — питомец получает статус «усыновлён» +- При отклонении — комментарий с причиной + +### 2.6. Волонтёры +- Профиль волонтёра: имя, телефон, email, дата начала волонтёрства, статус (активен/неактивен) +- Расписание волонтёра: дата, время, тип деятельности (выгул, кормление, уборка) +- CRUD расписания (волонтёр создаёт свои смены) + +### 2.7. Истории питомцев +- Записи о здоровье питомца: дата, описание (лечение, вакцинация, осмотр, операция) +- Привязка к питомцу +- Хронология на странице питомца + +### 2.8. Панель волонтёра +- Мои питомцы (за которыми закреплён) +- Входящие заявки на усыновление (управление статусами) +- Моё расписание +- Добавление историй питомцев + +### 2.9. Панель потенциального хозяина +- Каталог питомцев +- Мои заявки на усыновление (статусы) +- Создание новой заявки + +### 2.10. Панель администратора +- Управление пользователями и ролями +- Управление категориями питомцев +- Модерация заявок +- Общая статистика приюта + +--- + +## 3. Структура базы данных + +### Таблица `users` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Имя | +| email | VARCHAR(255) | Email (unique) | +| password | VARCHAR(255) | Хеш пароля | +| role | ENUM('admin', 'volunteer', 'adopter') | Роль | +| phone | VARCHAR(20) \| NULL | Телефон | +| avatar | VARCHAR(255) \| NULL | Аватар | +| volunteer_start_date | DATE \| NULL | Дата начала волонтёрства (для волонтёров) | +| is_active | BOOLEAN | Активен ли (для волонтёров) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `pet_categories` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Название вида (кошки, собаки, ...) | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| description | TEXT \| NULL | Описание вида | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `pets` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| name | VARCHAR(255) | Кличка питомца | +| slug | VARCHAR(255) | URL-идентификатор (unique) | +| species_id | BIGINT UNSIGNED | Вид (FK → pet_categories) | +| breed | VARCHAR(255) \| NULL | Порода | +| age_estimate | VARCHAR(50) | Примерный возраст (например, «2 года», «6 месяцев») | +| gender | ENUM('male', 'female') | Пол | +| description | TEXT | Описание, особенности характера | +| status | ENUM('available', 'treatment', 'adopted', 'reserved') | Статус | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `pet_photos` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| pet_id | BIGINT UNSIGNED | FK → pets | +| photo_path | VARCHAR(255) | Путь к изображению | +| is_primary | BOOLEAN | Главное фото (true/false) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `adoption_applications` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| pet_id | BIGINT UNSIGNED | Питомец (FK → pets) | +| applicant_id | BIGINT UNSIGNED | Заявитель (FK → users) | +| full_name | VARCHAR(255) | ФИО заявителя | +| phone | VARCHAR(20) | Телефон | +| email | VARCHAR(255) | Email | +| address | TEXT | Адрес проживания | +| experience | TEXT | Опыт содержания животных | +| purpose | TEXT | Цель усыновления | +| living_conditions | TEXT | Условия содержания | +| status | ENUM('new', 'under_review', 'approved', 'rejected', 'completed') | Статус | +| rejection_reason | TEXT \| NULL | Причина отклонения | +| volunteer_notes | TEXT \| NULL | Заметки волонтёра | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `volunteer_schedules` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| volunteer_id | BIGINT UNSIGNED | Волонтёр (FK → users) | +| pet_id | BIGINT UNSIGNED \| NULL | Питомец (FK → pets), NULL если общая смена | +| schedule_date | DATE | Дата смены | +| start_time | TIME | Время начала | +| end_time | TIME | Время окончания | +| activity_type | ENUM('walking', 'feeding', 'cleaning', 'grooming', 'other') | Тип деятельности | +| notes | TEXT \| NULL | Заметки | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +### Таблица `pet_histories` +| Поле | Тип | Описание | +|---|---|---| +| id | BIGINT UNSIGNED | Первичный ключ | +| pet_id | BIGINT UNSIGNED | FK → pets | +| history_date | DATE | Дата записи | +| type | ENUM('vaccination', 'treatment', 'examination', 'surgery', 'other') | Тип записи | +| description | TEXT | Описание | +| veterinarian | VARCHAR(255) \| NULL | Ветеринар | +| created_by | BIGINT UNSIGNED | Создатель записи (FK → users) | +| created_at | TIMESTAMP | | +| updated_at | TIMESTAMP | | + +--- + +## 4. Маршруты и контроллеры + +```php +// Публичные маршруты +Route::get('/', [PetController::class, 'index'])->name('pets.index'); +Route::get('/pets/{pet:slug}', [PetController::class, 'show'])->name('pets.show'); + +// Аутентифицированные маршруты +Route::middleware('auth')->group(function () { + // Заявки на усыновление + Route::prefix('applications')->name('applications.')->group(function () { + Route::post('/apply/{pet}', [ApplicationController::class, 'store'])->name('apply'); + Route::get('/my-applications', [ApplicationController::class, 'myApplications'])->name('my'); + Route::get('/{application}', [ApplicationController::class, 'show'])->name('show'); + }); + + // Расписание волонтёра + Route::prefix('volunteer')->name('volunteer.')->middleware('can:be-volunteer')->group(function () { + Route::resource('schedules', VolunteerScheduleController::class); + Route::get('dashboard', [VolunteerDashboardController::class, 'index'])->name('dashboard'); + }); + + // Управление питомцами (волонтёр/админ) + Route::prefix('shelter')->name('shelter.')->middleware('can:manage-pets')->group(function () { + Route::resource('pets', ShelterPetController::class); + Route::resource('pets.photos', PetPhotoController::class)->scoped(['pets' => 'pet']); + Route::resource('pets.histories', PetHistoryController::class)->scoped(['pets' => 'pet']); + + // Управление заявками + Route::get('applications', [ShelterApplicationController::class, 'index'])->name('applications.index'); + Route::get('applications/{application}', [ShelterApplicationController::class, 'show'])->name('applications.show'); + Route::post('applications/{application}/review', [ShelterApplicationController::class, 'startReview'])->name('applications.review'); + Route::post('applications/{application}/approve', [ShelterApplicationController::class, 'approve'])->name('applications.approve'); + Route::post('applications/{application}/reject', [ShelterApplicationController::class, 'reject'])->name('applications.reject'); + Route::post('applications/{application}/complete', [ShelterApplicationController::class, 'complete'])->name('applications.complete'); + }); + + // Админка + Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { + Route::resource('users', AdminUserController::class); + Route::resource('categories', AdminCategoryController::class); + Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); + }); +}); +``` + +### Контроллеры +- `PetController` — публичный каталог и детальный просмотр +- `ApplicationController` — подача и просмотр своих заявок +- `VolunteerScheduleController` — CRUD расписания волонтёра +- `ShelterPetController` — CRUD питомцев (волонтёр/админ) +- `PetPhotoController` — загрузка/удаление фото питомца +- `PetHistoryController` — CRUD историй питомца +- `ShelterApplicationController` — управление заявками (смена статусов) +- `VolunteerDashboardController`, `AdminUserController`, `AdminCategoryController`, `AdminDashboardController` — панели + +--- + +## 5. Роли, Gates и Policies + +### Роли +| Роль | Описание | +|---|---| +| `admin` | Полный доступ, управление пользователями, категориями, модерация | +| `volunteer` | Управление питомцами, заявками, расписанием, историями питомцев | +| `adopter` | Просмотр каталога, подача заявок на усыновление, просмотр своих заявок | + +### Gates + +```php +Gate::define('be-volunteer', function (User $user) { + return in_array($user->role, ['admin', 'volunteer']); +}); + +Gate::define('manage-pets', function (User $user) { + return in_array($user->role, ['admin', 'volunteer']); +}); + +Gate::define('access-admin-panel', function (User $user) { + return $user->role === 'admin'; +}); +``` + +### Policies + +**PetPolicy:** +- `view` — все (публичный каталог) +- `create`, `update`, `delete` — volunteer или admin + +**PetPhotoPolicy:** +- `create`, `delete` — volunteer или admin + +**PetHistoryPolicy:** +- `create`, `update`, `delete` — volunteer или admin +- `view` — все + +**AdoptionApplicationPolicy:** +- `create` — adopter (не подавал одобренную заявку на этого питомца) +- `view` — заявитель, волонтёр/admin +- `review`, `approve`, `reject`, `complete` — volunteer или admin + +**VolunteerSchedulePolicy:** +- `create`, `update`, `delete` — владелец расписания (волонтёр) или admin +- `view` — volunteer или admin + +--- + +## 6. Требования к интерфейсу (Bootstrap 5) + +### Компоненты +- **Навбар** — логотип, каталог питомцев, «Мои заявки», панель волонтёра, профиль +- **Карточки питомцев** — главное фото, кличка, вид (бейдж), порода, возраст, статус (бейдж) +- **Страница питомца** — галерея фото (карусель или грид), описание, характеристики, кнопка «Усыновить», истории питомца (хронология) +- **Карусель фото** — Bootstrap Carousel для галереи питомца +- **Форма заявки** — Bootstrap-форма с валидацией, textarea для опыта и условий +- **Таблица заявок** — питомец, заявитель, статус (цветные бейджи), действия +- **Календарь/таблица расписания** — дата, время, тип деятельности +- **Хронология историй** — вертикальный timeline (Bootstrap + кастомные стили) +- **Дашборд волонтёра** — статистика (карточки), входящие заявки, ближайшее расписание +- **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента + +### Цветовая схема статусов питомца +- Доступен — `bg-success` +- На лечении — `bg-warning` +- Усыновлён — `bg-primary` +- Зарезервирован — `bg-info` + +### Цветовая схема статусов заявки +- Новая — `bg-info` +- На рассмотрении — `bg-warning` +- Одобрена — `bg-success` +- Отклонена — `bg-danger` +- Завершена — `bg-secondary` + +--- + +## 7. Git-workflow для команды + +### Распределение модулей (для 2 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Питомцы, категории, фото, заявки на усыновление | `feature/pets-crud`, `feature-categories`, `feature-pet-photos` | +| **Участник B** | Расписание волонтёра, истории, дашборд, UI | `feature/applications`, `feature-application-statuses`, `feature-approval-flow` | + +### Распределение модулей (для 3 человек) + +| Участник | Модуль | Ветки | +|---|---|---| +| **Участник A** | Питомцы, категории, загрузка фото, каталог | `feature/pets-crud`, `feature-categories`, `feature-pet-photos` | +| **Участник B** | Заявки на усыновление, статусные переходы | `feature/applications`, `feature-application-statuses`, `feature-approval-flow` | +| **Участник C** | Расписание волонтёра, истории питомцев, UI, дашборд | `feature-volunteer-schedules`, `feature-pet-histories`, `feature-dashboard-ui` | + +### Правила +1. Ветка `develop` — основная ветка разработки +2. Каждый участник создаёт фич-ветки от `develop` +3. Минимум 3 PR на участника +4. Обязательный код-ревью перед мёржем +5. Conventional Commits + +### Визуализация workflow (2 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/pets-crud + checkout develop + branch feature/applications + checkout feature/pets-crud + commit id: "feat: add pets CRUD" + commit id: "feat: add pet photos" + checkout feature/applications + commit id: "feat: add adoption applications" + commit id: "feat: add application statuses" + checkout develop + merge feature/pets-crud tag: "PR + review" + merge feature/applications tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +### Визуализация workflow (3 участника) + +```mermaid +gitGraph + commit id: "init" tag: "v1.0" + branch develop + checkout develop + branch feature/pets-crud + checkout develop + branch feature/applications + checkout develop + branch feature-volunteer-schedules + checkout feature/pets-crud + commit id: "feat: add pets CRUD" + commit id: "feat: add pet categories" + checkout feature/applications + commit id: "feat: add adoption applications" + commit id: "feat: add application statuses" + checkout feature-volunteer-schedules + commit id: "feat: add volunteer schedules" + commit id: "feat: add pet histories" + checkout develop + merge feature/pets-crud tag: "PR + review" + merge feature/applications tag: "PR + review" + merge feature-volunteer-schedules tag: "PR + review" + checkout main + merge develop tag: "release v1.1" +``` + +--- + +## 8. Критерии приёмки + +### Обязательно +- [ ] CRUD питомцев с загрузкой фото (до 3 фото на питомца) +- [ ] CRUD категорий питомцев +- [ ] Каталог питомцев с пагинацией и фильтрацией по видам +- [ ] Подача заявки на усыновление (форма с валидацией всех полей) +- [ ] Просмотр своих заявок (заявитель) +- [ ] Управление статусами заявок волонтёром (новая → на рассмотрении → одобрена/отклонена) +- [ ] При одобрении заявки — обновление статуса питомца на «adopted» +- [ ] При отклонении — обязательная причина +- [ ] CRUD расписания волонтёра +- [ ] CRUD историй питомцев (лечение, вакцинация, осмотр) +- [ ] Панель волонтёра (мои питомцы, заявки, расписание) +- [ ] Policies: adoptter не может управлять питомцами или заявками +- [ ] Адаптивная Bootstrap-вёрстка +- [ ] Flash-сообщения +- [ ] Git-история: минимум 3 ветки на участника, PR с ревью + +### Дополнительно (бонусные баллы) +- [ ] Поиск питомцев по кличке и породе +- [ ] Фильтрация по возрасту и полу +- [ ] Галерея фото (Bootstrap Carousel) +- [ ] Экспорт расписания волонтёра в PDF +- [ ] Уведомления (email при изменении статуса заявки) +- [ ] Мягкое удаление питомцев и историй +- [ ] Тесты: PHPUnit Feature-тесты для ApplicationController +- [ ] API: RESTful API для каталога питомцев + +--- + +## 9. Дополнительные задания (для продвинутых) + +1. **Уведомления:** email заявителю при изменении статуса заявки +2. **Совместимость:** рекомендации питомцев заявителю на основе его опыта и условий +3. **Статистика приюта:** дашборд с графиками (Chart.js) — усыновления по месяцам, популярные виды +4. **Тесты:** PHPUnit Feature-тесты для статусных переходов заявок +5. **API:** RESTful API для каталога питомцев и подачи заявок +6. **Queue:** асинхронная отправка email-уведомлений +7. **Events & Listeners:** событие «заявка одобрена» → обновление статуса питомца + email + +--- + +## 10. Рекомендуемые материалы + +- Laravel Docs: https://laravel.com/docs/12.x +- Laravel File Uploads (multiple): https://laravel.com/docs/12.x/filesystem#file-uploads +- Bootstrap 5 Carousel: https://getbootstrap.com/docs/5.3/components/carousel/ +- Bootstrap 5 Badges: https://getbootstrap.com/docs/5.3/components/badge/ +- Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies +- State Machines (статусные переходы): https://github.com/brentroose/laravel-state-machine