444 lines
22 KiB
Markdown
444 lines
22 KiB
Markdown
# Техническое задание: Система управления складом (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
|