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