Загрузить файлы в «/»
parent
d86abfe469
commit
b7c9d8491f
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue