Документация ЛК
Модули

Каталог

Модуль "Каталог"

Версия: 1.0
Дата: 2025-11-21
Статус: Реализован


Содержание

  1. Общее описание
  2. Текущая реализация
  3. Описание полей
  4. Права доступа
  5. UI/UX по ролям
  6. Бизнес-логика
  7. Выявленные проблемы
  8. Рекомендации по улучшению

1. Общее описание

1.1 Предназначение модуля

Модуль "Каталог" предназначен для:

  • Управления каталогом товаров (продукции компании)
  • Организации товаров по категориям и подкатегориям
  • Отображения товаров дилерам для оформления заказов
  • Редактирования цен, характеристик, изображений товаров

1.2 Сущности модуля

СущностьТаблицаОписание
КатегорияcategoriesГруппировка товаров (ТВЕРЬ LITE, КЕССОНЫ и т.д.)
ТоварproductsКонкретный продукт (КЕССОН Тверь 0,95)
Связь категория-товарcategory_productMany-to-Many связь
Характеристикаproperty_productСвойства товара (объём, глубина)
ОпцияoptionsДополнительные опции товара (входы, доп.опции)

1.3 Типы категорий

ТипФлаг additionalОписание
Основные товарыfalseСептики, кессоны, погреба — требуют производственный слот
Дополнительные товарыtrueНасосы, компрессоры, химия — не занимают слот

2. Текущая реализация

2.1 Архитектура

┌─────────────────────────────────────────────────────────────────────────────┐
│                         МОДУЛЬ КАТАЛОГ                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  РОУТЫ (routes/web.php)                                                      │
│  ├── /catalog              → CatalogController@index    → Список категорий   │
│  ├── /category/{id}        → CatalogController@category → Страница категории │
│  ├── /category/{cat}/product/edit/{id} → CatalogController@product → Товар  │
│  └── /products             → CatalogController@products → Все товары списком │
│                                                                              │
│  КОНТРОЛЛЕР (CatalogController.php)                                          │
│  └── Роутинг по group_id:                                                    │
│      ├── Дилер (2)     → Карточки категорий/товаров (только active=1)       │
│      └── Остальные     → Filament таблицы с редактированием                 │
│                                                                              │
│  LIVEWIRE КОМПОНЕНТЫ                                                         │
│  ├── Category/                                                               │
│  │   ├── Listing.php     — Filament таблица категорий                       │
│  │   └── Control.php     — Кнопки Создать/Изменить/Удалить                  │
│  └── Product/                                                                │
│      ├── Listing.php     — Filament таблица товаров в категории             │
│      ├── Control.php     — Форма редактирования товара (2 вкладки)          │
│      ├── All.php         — Все товары списком (только для админа)           │
│      └── Buy.php         — Кнопка "Новый заказ" для дилера                  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 Структура БД

Таблица categories

ПолеТипОписание
idbigintPK
namestring(255)Название категории
imagestringПуть к изображению
category_idFK nullableРодительская категория (для подкатегорий)
additionalbooleanФлаг "Дополнительные товары"
indexintПорядок сортировки
created_attimestamp
updated_attimestamp

Таблица products

ПолеТипОписание
idbigintPK
namestring(255)Название товара
typestringТип товара (Септик, Кессон, Погреб, Другое)
add_typestring nullableПодтип для допников (pump, compressor, box...)
pricedecimal(8,2)Розничная цена
dealer_pricedecimal(8,2) nullableФиксированная дилерская цена (для допников)
price_installdecimal(8,2)Стоимость монтажа
step_sizeintШаг наращивания горловины (мм)
step_pricedecimal(8,2)Цена за шаг (розничная)
step_price_dealerdecimal(8,2)Цена за шаг (дилерская)
timeintВремя производства (единиц)
activebooleanАктивен/скрыт
descriptiontextОписание (HTML)
imagesjsonМассив путей к изображениям
filesjsonМассив прикреплённых файлов [{name, file}]
indexintПорядок сортировки

Связующие таблицы

-- category_product (Many-to-Many)
category_id, product_id

-- manufacture_product (Many-to-Many) — товар на каких заводах
manufacture_id, product_id

-- property_product — характеристики товара
id, product_id, property_id, value

3. Описание полей

3.1 Поля категории

ПолеНазвание в UIТип вводаОписание
nameНазваниеTextInputОбязательное
imageИзображениеFileUploadКартинка категории
category_idРодительская категорияSelectДля подкатегорий
additionalДоп.товарыToggleФлаг дополнительных товаров

3.2 Поля товара

Вкладка "Общая информация"

ПолеНазвание в UIТип вводаОписание
nameНазвание*TextInputОбязательное, max 255
typeТип*SelectСептик, Кессон, Погреб, Другое
add_typeДополнительный типSelectpump, compressor, box, ruff...
priceСтоимость розничная*NumberПрефикс ₽
dealer_priceДилерская ценаNumberФикс. цена для допников
price_installСтоимость монтажаNumberЦена монтажа
step_sizeШаг наращиванияNumberВ мм
step_priceСтоимость шага наращиванияNumberРозничная
step_price_dealerСтоимость шага для дилераNumberДилерская
activeАктивен*ToggleПоказывать ли дилерам
manufacturesДоступно на производствахMultiSelectЗаводы
descriptionОписаниеRichEditorHTML
categoriesКатегорииMultiSelectПривязка к категориям
timeВремяNumberВремя производства
filesФайлыRepeater[{name, file}]
optionsОпцииRepeaterВходы и доп.опции
imagesЗагрузите изображенияFileUpload MultipleГалерея

Вкладка "Характеристики"

ПолеТипОписание
productPropertiesRepeaterproperty_id + value

3.3 Опции товара

┌──────────────────────────────────────────────────────────────────┐
│ Опции товара (таблица options)                                    │
├──────────────────────────────────────────────────────────────────┤
│ name      — название опции                                        │
│ price     — цена                                                  │
│ group     — тип: 'enter' (вход) или 'single' (доп.опция)         │
│ type      — для входов: 'end' (торец), 'left', 'right'           │
└──────────────────────────────────────────────────────────────────┘

4. Права доступа

4.1 Политики категорий

ПолитикаОписание
category_can_viewМожет смотреть категории
category_can_addМожет добавлять категории
category_can_editМожет редактировать категории
category_can_deleteМожет удалять категории

4.2 Политики товаров

ПолитикаОписание
product_can_viewМожет смотреть товары
product_can_addМожет добавлять товары
product_can_editМожет редактировать товары
product_can_deleteМожет удалять товары

4.3 Матрица прав по ролям

РольПросмотрСозданиеРедактированиеУдалениеИнлайн-редакт.
Администратор
Менеджер⚠️⚠️⚠️⚠️
Дилер✅ (active=1)
Производство

5. UI/UX по ролям

5.1 Интерфейс для Администратора

Список категорий (/catalog)

┌─────────────────────────────────────────────────────────────────────────────┐
│ Каталог                                                     [+ Создать]     │
├─────────────────────────────────────────────────────────────────────────────┤
│ [↕ Изменить порядок]                                [Поиск...] [Столбцы ▾]  │
├──────┬────────────┬──────────────────────┬───────────┬──────────┬───────────┤
│ ☐    │ Изображение│ Название             │ Доп.товары│ Кол-во   │ Действия  │
├──────┼────────────┼──────────────────────┼───────────┼──────────┼───────────┤
│ ☐    │ [img]      │ ТВЕРЬ CLASSIC        │ [ ]       │ 0        │ [Удалить] │
│ ☐    │ [img]      │ ТВЕРЬ LITE           │ [ ]       │ 0        │ [Удалить] │
│ ☐    │ [img]      │ КЕССОНЫ              │ [ ]       │ 6        │ [Удалить] │
│ ☐    │ [img]      │ Дополнительные товары│ [●]       │ 35       │ [Удалить] │
└──────┴────────────┴──────────────────────┴───────────┴──────────┴───────────┘

Функции:
✅ Drag&Drop сортировка (reorderable)
✅ Toggle "Доп.товары" прямо в таблице
✅ Поиск по названию
✅ Удаление (одиночное и массовое)
✅ Переход к категории по клику

Страница категории (/category/{id})

┌─────────────────────────────────────────────────────────────────────────────┐
│ [← Назад]  Категория КЕССОНЫ                       [Изменить] [Удалить]     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ Подкатегории                                                [+ Создать]     │
│ ┌───────────────────────────────────────────────────────────────────────┐   │
│ │ Не найдено categories                                                 │   │
│ └───────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│ Продукты                                                    [+ Создать]     │
│ ┌───────────────────────────────────────────────────────────────────────┐   │
│ │ ☐ │ Превью │ Название*      │ Тип*  │ Цена*    │ Монтаж  │ Время│Акт.│   │
│ │───│────────│────────────────│───────│──────────│─────────│──────│────│   │
│ │ ☐ │ [img]  │[КЕССОН 0,95  ]│[Кессон]│[66100.00]│[46900.00]│[100] │[●] │   │
│ │ ☐ │ [img]  │[КЕССОН 0,95У ]│[Кессон]│[79900.00]│[46900.00]│[100] │[●] │   │
│ └───────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Функции:
✅ Инлайн-редактирование (TextInputColumn, SelectColumn, ToggleColumn)
✅ Создание подкатегорий
✅ Создание товаров с автопривязкой к категории
✅ Drag&Drop сортировка

Карточка товара (/category/{cat}/product/edit/{id})

┌─────────────────────────────────────────────────────────────────────────────┐
│ [← Назад]  Продукт КЕССОН Тверь 0,95               [Удалить] [Сохранить]    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ [Общая информация] [Характеристики]                                         │
│ ┌──────────────────────────────────────────────┬───────────────────────────┐│
│ │ Название*           [КЕССОН Тверь 0,95    ]  │ Загрузите изображения     ││
│ │ Тип*                [Кессон           ▾]     │ ┌─────────────────────┐   ││
│ │ Дополнительный тип  [Выбрать вариант  ▾]     │ │ [img1] [img2] [img3]│   ││
│ │                                              │ │ Перетащите файлы    │   ││
│ │ Стоимость розничная* [₽ 66100.00        ]   │ └─────────────────────┘   ││
│ │ Дилерская цена       [₽                 ]   │                           ││
│ │ Стоимость монтажа    [₽ 46900.00        ]   │                           ││
│ │                                              │                           ││
│ │ Шаг наращивания      [мм 200            ]   │                           ││
│ │ Стоимость шага       [₽ 5500.00         ]   │                           ││
│ │ Стоимость для дилера [₽ 4500.00         ]   │                           ││
│ │                                              │                           ││
│ │ Активен* [●]                                │                           ││
│ │ Доступно на производствах [Выбрать... ▾]    │                           ││
│ │                                              │                           ││
│ │ Описание                                     │                           ││
│ │ [B][I][S][🔗][H2][H3][❝][</>][≡][#][📎]     │                           ││
│ │ ┌────────────────────────────────────────┐  │                           ││
│ │ │ Inventore est in omnis hic nesciunt.   │  │                           ││
│ │ └────────────────────────────────────────┘  │                           ││
│ │                                              │                           ││
│ │ Категории [КЕССОНЫ ×] [+ Добавить]          │                           ││
│ │ Время [100]                                  │                           ││
│ │                                              │                           ││
│ │ Файлы                          [+ Добавить] │                           ││
│ │ Опции                          [+ Добавить] │                           ││
│ └──────────────────────────────────────────────┴───────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘

5.2 Интерфейс для Дилера

Список категорий (/catalog)

┌─────────────────────────────────────────────────────────────────────────────┐
│ Каталог                                                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                       │
│  │    [img]     │  │    [img]     │  │    [img]     │                       │
│  │              │  │              │  │              │                       │
│  │ ТВЕРЬ LITE   │  │ТВЕРЬ CLASSIC │  │ ТВЕРЬ AERO   │                       │
│  └──────────────┘  └──────────────┘  └──────────────┘                       │
│                                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                       │
│  │    [img]     │  │    [img]     │  │    [img]     │                       │
│  │              │  │              │  │              │                       │
│  │   КЕССОНЫ    │  │   ПОГРЕБА    │  │  ТВЕРЬ PRO   │                       │
│  └──────────────┘  └──────────────┘  └──────────────┘                       │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Особенности:
- Только категории с активными товарами
- Карточки с изображениями
- Красивый дизайн (синие заголовки)

Страница категории для дилера (/category/{id})

┌─────────────────────────────────────────────────────────────────────────────┐
│ [← Назад]  КЕССОНЫ                                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  [Подкатегория 1] [Подкатегория 2]  ← кнопки подкатегорий                  │
│                                                                              │
│  ┌──────────────────────────┐  ┌──────────────────────────┐                 │
│  │ [img] КЕССОН Тверь 0,95  │  │ [img] КЕССОН Тверь 0,95У │                 │
│  │                          │  │                          │                 │
│  │ Цена    66 100 руб.      │  │ Цена    79 900 руб.      │                 │
│  └──────────────────────────┘  └──────────────────────────┘                 │
│                                                                              │
│  ┌──────────────────────────┐  ┌──────────────────────────┐                 │
│  │ [img] КЕССОН Тверь 1,27  │  │ [img] КЕССОН Тверь 1,27У │                 │
│  │                          │  │                          │                 │
│  │ Цена    93 000 руб.      │  │ Цена   111 500 руб.      │                 │
│  └──────────────────────────┘  └──────────────────────────┘                 │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Особенности:
- Только активные товары (active=1)
- Отображение цены и характеристик
- Товары из подкатегорий тоже показываются

Карточка товара для дилера (/category/{cat}/product/edit/{id})

┌─────────────────────────────────────────────────────────────────────────────┐
│ [← Назад]                                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌────────────────┐   КЕССОН Тверь 0,95                                     │
│  │                │   Цена    66 100 руб.                                   │
│  │    [IMAGE]     │                                                         │
│  │                │   [🛒 Новый заказ]                                       │
│  │                │                                                         │
│  └────────────────┘                                                         │
│                                                                              │
│  [Описание] [Файлы]                                                         │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │ Inventore est in omnis hic nesciunt.                                 │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Функции:
✅ Просмотр изображения (Fancybox галерея)
✅ Вкладки Описание / Файлы
✅ Кнопка "Новый заказ" — переход к оформлению заказа
✅ Скачивание файлов

6. Бизнес-логика

6.1 Ценообразование

// Product::getDealerPrice($dealer)

if ($this->isAdditionalProduct()) {
    // Для допников — фиксированная дилерская цена
    return $this->dealer_price;
}

// Для основных товаров — цена со скидкой дилера
$basePrice = $this->price;
$topCategory = $this->categories->first()->getTopLevelParent();
$discount = $dealer->discount($topCategory->id); // % скидки на категорию
$dealerPrice = $basePrice * (1 - $discount / 100);

6.2 Определение типа товара

// Product::isAdditionalProduct()

// Товар является дополнительным, если его корневая категория
// имеет флаг additional = true

foreach ($this->categories as $category) {
    $parent = $category->getTopLevelParent();
    if ($parent && $parent->additional) {
        return true;
    }
}
return false;

6.3 Наращивание горловины

ПолеОписание
step_sizeШаг в мм (обычно 200мм)
step_priceРозничная цена за шаг
step_price_dealerДилерская цена за шаг
Итоговая цена = базовая_цена + (количество_шагов × цена_шага)

6.4 Время производства

  • Поле time определяет, сколько производственных единиц занимает товар
  • Используется в календаре для расчёта свободного времени
  • Допники (additional=true) не занимают производственный слот

7. Выявленные проблемы

7.1 Критические

#ПроблемаВлияниеРешение
1Нет проверки прав category_can_addБезопасностьДобавить в Control.php
2Нет валидации при инлайн-редактированииЦелостность данныхДобавить afterStateUpdated

7.2 Средние

#ПроблемаВлияниеРешение
3Дилер видит розничную ценуНеверное отображениеПоказывать getDealerPrice()
4Нет кеширования категорийПроизводительностьДобавить Cache
5N+1 в cat_list.blade.phpПроизводительностьEager loading
6Дублирование price_install в Listing.phpКодУдалить дубль

7.3 Низкие

#ПроблемаВлияниеРешение
7Неиспользуемый Log::emergencyЗасорение логовУдалить или заменить
8Хардкод add_typeПоддержкаВынести в конфиг или БД
9Нет превью файловUXДобавить иконки типов файлов

8. Рекомендации по улучшению

8.1 Приоритет: Высокий

8.1.1 Показывать дилерскую цену

Файл: resources/views/components/catalog/product.blade.php

// Было:
@formatPrice($product->price) руб.

// Стало:
@php
    $dealer = auth()->user();
    $displayPrice = $product->getDealerPrice($dealer);
@endphp
@formatPrice($displayPrice) руб.

8.1.2 Добавить проверку прав при создании

Файл: app/Livewire/Category/Control.php

public function createAction(): CreateAction
{
    $canAdd = $this->authUser?->check_access('category_can_add') ?? false;
    
    return CreateAction::make('create')
        ->visible($canAdd) // Скрыть кнопку если нет прав
        // ...
}

8.2 Приоритет: Средний

8.2.1 Кеширование категорий

// В контроллере
$categories = Cache::remember('catalog_categories', 3600, function () {
    return Category::whereNull('category_id')
        ->whereHas('products', fn($q) => $q->where('active', 1))
        ->get();
});

8.2.2 Eager loading в cat_list

// CatalogController::category()
$category->load([
    'products' => fn($q) => $q->where('active', 1)->with('productProperties.property'),
    'children.products' => fn($q) => $q->where('active', 1)->with('productProperties.property'),
]);

8.3 Приоритет: Низкий

8.3.1 Вынести add_type в БД

// Создать таблицу product_subtypes
Schema::create('product_subtypes', function (Blueprint $table) {
    $table->id();
    $table->string('key');   // pump, compressor...
    $table->string('name');  // Насос, Компрессор...
    $table->timestamps();
});

Приложение A: Структура файлов

app/
├── Http/Controllers/
│   └── CatalogController.php
├── Livewire/
│   ├── Category/
│   │   ├── Control.php
│   │   └── Listing.php
│   └── Product/
│       ├── All.php
│       ├── Buy.php
│       ├── Control.php
│       └── Listing.php
└── Models/
    ├── Category.php
    ├── Product.php
    └── Option.php

database/migrations/
├── 2024_03_07_104535_create_categories_table.php
├── 2024_03_07_104550_create_products_table.php
├── 2024_03_07_113802_create_category_product_table.php
├── 2024_04_23_123318_add_field_to_categories_table.php  # additional
├── 2024_04_26_165816_add_field_to_products_table.php    # dealer_price
└── 2024_05_14_170055_add_step_price_dealer_to_products_table.php

resources/views/
├── components/catalog/
│   ├── category.blade.php    # Карточка категории для дилера
│   ├── subcategory.blade.php # Кнопка подкатегории
│   ├── product.blade.php     # Карточка товара для дилера
│   └── gallery.blade.php     # Галерея изображений
├── livewire/product/
│   └── buy.blade.php         # Кнопка "Новый заказ"
└── templates/
    ├── categories/
    │   ├── catalog.blade.php    # Список категорий (админ)
    │   ├── category.blade.php   # Страница категории (админ)
    │   ├── list.blade.php       # Список категорий (дилер)
    │   └── cat_list.blade.php   # Страница категории (дилер)
    └── products/
        ├── all.blade.php        # Все товары (только админ)
        ├── edit.blade.php       # Редактирование (админ)
        └── show.blade.php       # Просмотр (дилер)

Приложение B: SQL для аналитики

Товары по категориям

SELECT 
    c.name as category,
    COUNT(cp.product_id) as products_count,
    SUM(CASE WHEN p.active = 1 THEN 1 ELSE 0 END) as active_count
FROM categories c
LEFT JOIN category_product cp ON c.id = cp.category_id
LEFT JOIN products p ON cp.product_id = p.id
WHERE c.category_id IS NULL
GROUP BY c.id
ORDER BY c.index;

Товары без категории

SELECT p.id, p.name, p.active
FROM products p
LEFT JOIN category_product cp ON p.id = cp.product_id
WHERE cp.category_id IS NULL;

Документ подготовлен на основе анализа исходного кода проекта

On this page

Модуль "Каталог"Содержание1. Общее описание1.1 Предназначение модуля1.2 Сущности модуля1.3 Типы категорий2. Текущая реализация2.1 Архитектура2.2 Структура БДТаблица categoriesТаблица productsСвязующие таблицы3. Описание полей3.1 Поля категории3.2 Поля товараВкладка "Общая информация"Вкладка "Характеристики"3.3 Опции товара4. Права доступа4.1 Политики категорий4.2 Политики товаров4.3 Матрица прав по ролям5. UI/UX по ролям5.1 Интерфейс для АдминистратораСписок категорий (/catalog)Страница категории (/category/{id})Карточка товара (/category/{cat}/product/edit/{id})5.2 Интерфейс для ДилераСписок категорий (/catalog)Страница категории для дилера (/category/{id})Карточка товара для дилера (/category/{cat}/product/edit/{id})6. Бизнес-логика6.1 Ценообразование6.2 Определение типа товара6.3 Наращивание горловины6.4 Время производства7. Выявленные проблемы7.1 Критические7.2 Средние7.3 Низкие8. Рекомендации по улучшению8.1 Приоритет: Высокий8.1.1 Показывать дилерскую цену8.1.2 Добавить проверку прав при создании8.2 Приоритет: Средний8.2.1 Кеширование категорий8.2.2 Eager loading в cat_list8.3 Приоритет: Низкий8.3.1 Вынести add_type в БДПриложение A: Структура файловПриложение B: SQL для аналитикиТовары по категориямТовары без категории