Техническое задание: Система управления складом (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. Маршруты и контроллеры
// Аутентифицированные маршруты
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
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 |
Правила
- Ветка
develop — основная ветка разработки
- Каждый участник создаёт фич-ветки от
develop
- Минимум 3 PR на участника
- Обязательный код-ревью перед мёржем
- Conventional Commits
Визуализация workflow (2 участника)
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 участника)
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. Критерии приёмки
Обязательно
Дополнительно (бонусные баллы)
9. Дополнительные задания (для продвинутых)
- Автоматические оповещения: email при достижении минимального порога остатка
- Инвентаризация: сверка фактических остатков с системными, корректировка
- Перемещения: накладные перемещения между складами (несколько складов)
- Тесты: PHPUnit Feature-тесты для транзакций БД (построение/отмена накладной)
- API: RESTful API для остатков и создания накладных
- Queue: асинхронная генерация отчётов в CSV
- Events & Listeners: событие «проведение накладной» → обновление остатков + логирование
10. Рекомендуемые материалы