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

Товары списком

Модуль "Товары списком"

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


Содержание

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

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

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

Модуль "Товары списком" предоставляет плоский список всех товаров системы без группировки по категориям.

Основные цели:

  • Быстрый поиск товара по названию
  • Массовое редактирование товаров (инлайн)
  • Просмотр и редактирование товаров без категории
  • Быстрая сортировка и фильтрация всех товаров

1.2 Отличие от модуля "Каталог"

КритерийКаталогТовары списком
СтруктураИерархия категорий → товарыПлоский список всех товаров
ДоступВсе пользователи с category_can_viewТолько администраторы (group_id == 1)
Создание товаров✅ Да (с привязкой к категории)❌ Нет
Товары без категории❌ Не видны✅ Видны
НазначениеОсновная работа с каталогомАдминистративный инструмент

1.3 Статистика БД

МетрикаЗначение
Всего товаров145
С категориями142
Без категорий3
ТипыСептик (69), Другое (63), Погреб (7), Кессон (6)

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

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

┌─────────────────────────────────────────────────────────────────────────────┐
│                      МОДУЛЬ "ТОВАРЫ СПИСКОМ"                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  РОУТ (routes/web.php)                                                       │
│  └── GET /products → CatalogController@products                              │
│                                                                              │
│  КОНТРОЛЛЕР (CatalogController.php)                                          │
│  └── products() — проверка group_id == 1, возврат view                       │
│                                                                              │
│  LIVEWIRE КОМПОНЕНТ                                                          │
│  └── Product/All.php — Filament Table со всеми товарами                     │
│                                                                              │
│  BLADE ШАБЛОН                                                                │
│  └── templates/products/all.blade.php                                        │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 Файлы модуля

ФайлОписание
routes/web.phpРоут /products
app/Http/Controllers/CatalogController.phpМетод products()
app/Livewire/Product/All.phpLivewire компонент с Filament Table
resources/views/templates/products/all.blade.phpBlade шаблон
app/View/Components/Sidebar.phpПункт меню (строка 88-89)

2.3 Код контроллера

// CatalogController.php
public function products(Request $request)
{
    $user = $request->user();
    if (!$user or $user->group_id != 1)  // ТОЛЬКО АДМИНИСТРАТОР
        abort(403);
    return view('templates.products.all');
}

2.4 Код Sidebar

// Sidebar.php (строки 88-89)
if ($this->authUser->group_id == 1) {
    $this->items['products'] = 'Товары списком';
}

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

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

ПроверкаГдеОписание
group_id == 1CatalogController@productsДоступ только администраторам
group_id == 1Sidebar.phpОтображение пункта меню
❌ Нет проверки правProduct/All.phpУдаление без проверки

3.2 Матрица доступа

РольДоступ к модулюПросмотрИнлайн-редактированиеУдаление
Администратор
Менеджер❌ 403
Дилер❌ 403
Производство❌ 403

3.3 ⚠️ Проблема с правами

В Product/All.php нет проверки прав на удаление (product_can_delete):

// Product/All.php (строки 47-57)
->actions([
    Action::make('delete')->label('Удалить')
        ->icon('heroicon-o-trash')
        ->requiresConfirmation()
        ->action(fn (Product $record) => $record->delete())  // ⚠️ Нет проверки!
])
->bulkActions([
    BulkAction::make('delete')->label('Удалить отмеченные')
        ->requiresConfirmation()
        ->action(fn (Collection $records) => $records->each->delete())  // ⚠️ Нет проверки!
])

4. Функции и возможности

4.1 Доступные функции

ФункцияОписаниеРеализация
Просмотр спискаВсе товары в плоском спискеFilament Table
ПоискПо названию и типуsearchable()
СортировкаПо цене, времени, монтажуsortable()
Инлайн-редактированиеПрямо в таблицеTextInputColumn, ToggleColumn
Drag&Drop сортировкаИзменение порядкаreorderable('index')
УдалениеОдиночное и массовоеActions + BulkActions
Переход к карточкеКлик по строкеrecordUrl()
Скрытие столбцовToggle колонокtoggleable()
Пагинация5/10/25/50/ВсеFilament defaults

4.2 НЕдоступные функции

ФункцияПричинаРекомендация
Создание товараНе реализованоДобавить CreateAction
ФильтрыЗакомментированыДобавить фильтр по типу, активности
ЭкспортНе реализованДобавить ExportAction
Просмотр категорийНе отображаетсяДобавить колонку категорий

5. UI/UX

5.1 Интерфейс

┌─────────────────────────────────────────────────────────────────────────────┐
│ Товары списком                                                               │
├─────────────────────────────────────────────────────────────────────────────┤
│ [↕ Изменить порядок]                                [Поиск...] [Столбцы ▾]  │
├──────┬────────┬──────────────────┬───────┬───────┬────────┬─────┬─────┬─────┤
│ ☐    │Превью  │Название          │Тип    │Цена ↕ │Монтаж ↕│Время│Акт. │ ··· │
├──────┼────────┼──────────────────┼───────┼───────┼────────┼─────┼─────┼─────┤
│ ☐    │[img]   │[КЕССОН Тверь 0,95]│[Кессон]│[66100]│[46900] │[100]│ [●] │[Уд] │
│ ☐    │[img]   │[Тверь CLASSIC 2П]│[Септик]│[296900│[49500] │[150]│ [●] │[Уд] │
│ ...  │        │                  │       │       │        │     │     │     │
└──────┴────────┴──────────────────┴───────┴───────┴────────┴─────┴─────┴─────┘
│ Показано с 1 по 10 из 145              [5][10][25][50][Все] [1][2]...[15][→]│
└─────────────────────────────────────────────────────────────────────────────┘

5.2 Колонки таблицы

Видимые по умолчанию

КолонкаТипИнлайн-редакт.СортировкаПоиск
Превью (images)ImageColumn
Название (name)TextInputColumn
Тип (type)TextInputColumn
Цена (price)TextInputColumn
Стоимость монтажаTextInputColumn
Время (time)TextInputColumn
Активен (active)ToggleColumn

Скрытые по умолчанию (toggleable)

КолонкаОписание
Дилерская цена (dealer_price)Фикс. цена для допников
Шаг наращивания (step_size)В мм
Стоимость шага (step_price)Розничная
Стоимость шага для дилераДилерская
Дата созданияcreated_at
Дата обновленияupdated_at

5.3 Особенности инлайн-редактирования

  • TextInputColumn — изменение сохраняется при потере фокуса
  • ToggleColumn — мгновенное переключение активности
  • Нет валидации — можно ввести невалидные значения
  • Нет уведомлений — нет обратной связи после сохранения

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

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

#ПроблемаФайлСтрокиВлияние
1Нет проверки прав на удалениеProduct/All.php47-57Безопасность
2Нет валидации инлайн-вводаProduct/All.php79-106Целостность данных

6.2 Средние

#ПроблемаОписаниеРешение
3Избыточная проверка authUser$this->authUser and $this->authUser (строка 72)Упростить
4Нет отображения категорииТовар может быть в нескольких категорияхДобавить колонку
5Нет фильтровЗакомментированыРаскомментировать и добавить
6Товары без категорииСсылка на /category/none/product/edit/{id}Исправить fallback

6.3 Низкие

#ПроблемаОписание
7Нет кнопки созданияНельзя создать товар из этого модуля
8Дублирование кодаПохожий код в Product/Listing.php
9Нет экспортаПолезно для отчётов

7. Сравнение с модулем Каталог

7.1 Компоненты

КомпонентProduct/All.phpProduct/Listing.php
Источник данныхProduct::query()$category->products()
Проверка прав удаления❌ Нетproduct_can_delete
Колонка typeTextInputColumnSelectColumn
Дублирование price_install❌ Нет⚠️ Да (строки 111-112, 125-126)

7.2 Код сравнения

// Product/All.php — нет проверки прав
->actions([
    Action::make('delete')
        ->action(fn (Product $record) => $record->delete())
])

// Product/Listing.php — есть проверка
$can_delete = $this->authUser->check_access('product_can_delete');
// ...
if ($can_delete)
    $out[] = Action::make('delete')...

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

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

8.1.1 Добавить проверку прав на удаление

Файл: app/Livewire/Product/All.php

public function table(Table $table): Table
{
    $can_delete = $this->authUser->check_access('product_can_delete');
    
    return $table
        ->query(Product::query())
        // ...
        ->actions(
            $can_delete ? [
                Action::make('delete')->label('Удалить')
                    ->icon('heroicon-o-trash')
                    ->requiresConfirmation()
                    ->action(fn (Product $record) => $record->delete())
            ] : []
        )
        ->bulkActions(
            $can_delete ? [
                BulkAction::make('delete')->label('Удалить отмеченные')
                    ->requiresConfirmation()
                    ->action(fn (Collection $records) => $records->each->delete())
                    ->deselectRecordsAfterCompletion()
            ] : []
        );
}

8.1.2 Добавить валидацию инлайн-полей

Tables\Columns\TextInputColumn::make('price')
    ->rules(['required', 'numeric', 'min:0'])
    ->afterStateUpdated(function ($record, $state) {
        Notification::make()
            ->title('Цена обновлена')
            ->success()
            ->send();
    })
    ->sortable(),

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

8.2.1 Добавить колонку категорий

Tables\Columns\TextColumn::make('categories.name')
    ->label('Категории')
    ->badge()
    ->separator(',')
    ->toggleable(),

8.2.2 Добавить фильтры

->filters([
    Tables\Filters\SelectFilter::make('type')
        ->options(Type::all()->pluck('name', 'name')),
    Tables\Filters\TernaryFilter::make('active')
        ->label('Активен'),
    Tables\Filters\Filter::make('no_category')
        ->label('Без категории')
        ->query(fn (Builder $query) => $query->doesntHave('categories')),
])

8.2.3 Исправить fallback для товаров без категории

->recordUrl(fn ($record) => 
    $record->categories->count() 
        ? route('product-edit', [
            'category_id' => $record->categories->first()->id, 
            'id' => $record->id
          ]) 
        : route('products')  // Остаёмся на странице списка
);

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

8.3.1 Упростить проверку authUser

// Было (строка 72):
if ($this->authUser and $this->authUser) {

// Стало:
if ($this->authUser) {

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

->headerActions([
    Tables\Actions\CreateAction::make()
        ->model(Product::class)
        ->form([
            TextInput::make('name')->required(),
            Select::make('type')->options(Type::all()->pluck('name', 'name'))->required(),
            // ...
        ])
])

Приложение A: Полный код компонента

Product/All.php (текущий)

<?php

namespace App\Livewire\Product;

use App\Models\Product;
use App\Models\User;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\BulkAction;
use Livewire\Component;
use Illuminate\Database\Eloquent\Collection;

class All extends Component implements HasForms, HasTable
{
    use InteractsWithForms, InteractsWithTable;

    public User $authUser;

    public function mount()
    {
        $this->authUser = Request::user();
    }

    public function table(Table $table): Table
    {
        return $table
            ->query(Product::query())
            ->inverseRelationship('categories')
            ->columns($this->getTableColumns())
            ->reorderable('index')
            ->defaultSort('index', 'asc')
            ->filters([/* пусто */])
            ->actions([
                Action::make('delete')->label('Удалить')
                    ->icon('heroicon-o-trash')
                    ->requiresConfirmation()
                    ->action(fn (Product $record) => $record->delete())
            ])
            ->bulkActions([
                BulkAction::make('delete')->label('Удалить отмеченные')
                    ->requiresConfirmation()
                    ->action(fn (Collection $records) => $records->each->delete())
                    ->deselectRecordsAfterCompletion()
            ])
            ->recordUrl(fn ($record) => 
                $record->categories->count() 
                    ? route('product-edit', ['category_id' => $record->categories->first()->id, 'id' => $record->id]) 
                    : route('product-edit', ['category_id' => 'none', 'id' => $record->id])
            );
    }

    public function getTableColumns()
    {
        // Возвращает колонки с инлайн-редактированием
        // TextInputColumn, ToggleColumn, toggleable() для скрытых
    }
}

Приложение B: Вкладки в карточке товара

При клике на товар открывается карточка редактирования (Product/Control.php) с двумя вкладками:

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

ПолеТипОбязательностьОписание
nameTextInput✅ requiredНазвание товара
typeSelect✅ requiredТип (Септик, Кессон, Погреб, Другое)
add_typeSelectПодтип для допников
priceTextInput (numeric)✅ requiredРозничная цена
dealer_priceTextInput (numeric)Дилерская цена
price_installTextInput (numeric)Стоимость монтажа
step_sizeTextInput (numeric)Шаг наращивания (мм)
step_priceTextInput (numeric)Цена за шаг
step_price_dealerTextInput (numeric)Дилерская цена за шаг
activeToggle✅ requiredАктивность
manufacturesMultiSelectЗаводы
descriptionRichEditorОписание HTML
categoriesMultiSelectКатегории
timeTextInput (numeric)Время производства
filesRepeaterФайлы [{name, file}]
optionsRepeaterОпции [{name, price, group, type}]
imagesFileUpload multipleИзображения

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

ПолеТипОбязательностьОписание
productPropertiesRepeaterСвязь property_id + value

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

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

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

Статистика по типам

SELECT type, COUNT(*) as cnt, 
       SUM(CASE WHEN active = 1 THEN 1 ELSE 0 END) as active_cnt
FROM products 
GROUP BY type 
ORDER BY cnt DESC;

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

On this page

Модуль "Товары списком"Содержание1. Общее описание1.1 Предназначение модуля1.2 Отличие от модуля "Каталог"1.3 Статистика БД2. Текущая реализация2.1 Архитектура2.2 Файлы модуля2.3 Код контроллера2.4 Код Sidebar3. Права доступа3.1 Текущая реализация3.2 Матрица доступа3.3 ⚠️ Проблема с правами4. Функции и возможности4.1 Доступные функции4.2 НЕдоступные функции5. UI/UX5.1 Интерфейс5.2 Колонки таблицыВидимые по умолчаниюСкрытые по умолчанию (toggleable)5.3 Особенности инлайн-редактирования6. Выявленные проблемы6.1 Критические6.2 Средние6.3 Низкие7. Сравнение с модулем Каталог7.1 Компоненты7.2 Код сравнения8. Рекомендации по улучшению8.1 Приоритет: Высокий8.1.1 Добавить проверку прав на удаление8.1.2 Добавить валидацию инлайн-полей8.2 Приоритет: Средний8.2.1 Добавить колонку категорий8.2.2 Добавить фильтры8.2.3 Исправить fallback для товаров без категории8.3 Приоритет: Низкий8.3.1 Упростить проверку authUser8.3.2 Добавить кнопку создания товараПриложение A: Полный код компонентаProduct/All.php (текущий)Приложение B: Вкладки в карточке товараВкладка "Общая информация"Вкладка "Характеристики"Приложение C: SQL для аналитикиТовары без категорииСтатистика по типам