laravel-12-tech-specs/task08-inventory-management.md

444 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Техническое задание: Система управления складом (Inventory Management)
## 1. Описание проекта
Веб-приложение для учёта товаров на складе с контролем прихода и расхода, управлением поставщиками и формированием отчётов. Проект закрепляет навыки CRUD, вложенных моделей (накладная → позиции), транзакций БД, разграничения прав и совместной работы через Git.
**Команда:** 23 человека
---
## 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