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

22 KiB
Raw Permalink Blame History

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

// Аутентифицированные маршруты
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

Правила

  1. Ветка develop — основная ветка разработки
  2. Каждый участник создаёт фич-ветки от develop
  3. Минимум 3 PR на участника
  4. Обязательный код-ревью перед мёржем
  5. 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. Критерии приёмки

Обязательно

  • 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. Рекомендуемые материалы