Обновления системы после анализа и оптимизации архитектуры
- Обновлена схема Prisma с новыми полями и связями - Актуализированы правила системы в rules-complete.md - Оптимизированы GraphQL типы, запросы и мутации - Улучшены компоненты интерфейса и валидация данных - Исправлены критические ESLint ошибки: удалены неиспользуемые импорты и переменные - Добавлены тестовые файлы для проверки функционала 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v9.2
|
||||
# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v10.0
|
||||
|
||||
> ⚠️ **АБСОЛЮТНО ПОЛНЫЙ ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ**: Данный файл объединяет АБСОЛЮТНО ВСЕ правила системы: протоколы работы Claude Code, детальные протоколы по сложности, систему предотвращения нарушений, расширенную самопроверку, специальный UI/UX протокол и бизнес-правила. Визуальные правила вынесены в отдельный файл visual-design-rules.md с автоматической интеграцией.
|
||||
|
||||
@ -309,6 +309,7 @@
|
||||
| **Workflow поставок** | [5](#5--workflow-поставок) | 8 статусов, уведомления, логистика |
|
||||
| **GraphQL запросы** | [18](#18--graphql-и-typescript-правила), [24](#24--технические-приложения) | Резолверы, мутации, типизация |
|
||||
| **Система партнерства** | [13](#13--система-партнерства-и-контрагентов) | Counterparty, WHOLESALE, заявки |
|
||||
| **Рынки и маркет** | [10.1](#101-разделение-понятий-рынок-vs-маркет), [18.7](#187-правила-рынков-и-маркета) | РЫНОК ≠ МАРКЕТ, Organization.market |
|
||||
| **Критические запреты** | [17](#17--критические-запреты) | Что НЕЛЬЗЯ делать в системе |
|
||||
|
||||
### 🎯 ДЛЯ РАЗНЫХ РОЛЕЙ
|
||||
@ -1005,15 +1006,17 @@ const handleSuppliesClick = () => {
|
||||
|
||||
**ОБНОВЛЕННАЯ СТРУКТУРА СИСТЕМЫ (4 БЛОКА):**
|
||||
|
||||
**БЛОК 1: ПОСТАВЩИКИ** _(горизонтальный скролл)_
|
||||
- **Отображение**: Карточки поставщиков из раздела "Партнеры"
|
||||
- **Навигация**: Горизонтальный скролл (слева-направо) при превышении ширины экрана
|
||||
**БЛОК 1: ПОСТАВЩИКИ** _(адаптивная сетка)_
|
||||
- **Заголовок**: Минималистичный "🏢 Поставщики" без лишних элементов
|
||||
- **Поиск**: Компактное поле справа "Поиск поставщиков..." (w-64)
|
||||
- **Отображение**: Карточки поставщиков из раздела "Партнеры" в адаптивной сетке
|
||||
- **Выбор**: Клик выделяет карточку поставщика
|
||||
- **Результат**: Загружаются карточки товаров выбранного поставщика в блок 2
|
||||
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(горизонтальный скролл - НОВЫЙ)_
|
||||
- **Отображение**: Компактные карточки товаров выбранного поставщика
|
||||
- **Навигация**: Горизонтальный скролл аналогично блоку 1
|
||||
- **Отображение**: ТОЛЬКО минималистичные карточки товаров 80×112px
|
||||
- **Содержание**: ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
|
||||
- **Навигация**: Горизонтальный скролл при множестве товаров
|
||||
- **Выбор**: Клик добавляет товар в детальный каталог
|
||||
- **Результат**: Товар добавляется в блок 3 для управления поставкой
|
||||
|
||||
@ -1136,28 +1139,305 @@ const handleSuppliesClick = () => {
|
||||
- **Источник данных**: Товары выбранного поставщика из Блока 1
|
||||
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
|
||||
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
|
||||
- **Поведение**: Горизонтальный скролл при множестве товаров (аналогично Блоку 1)
|
||||
- **Поведение**: Горизонтальный скролл при множестве товаров
|
||||
|
||||
**АРХИТЕКТУРА И РАЗМЕРЫ:**
|
||||
- **Общая высота блока**: 160px фиксированная
|
||||
- **Заголовок**: "Товары [Название поставщика]" + поиск (~40px)
|
||||
- **Контейнер скролла**: 120px (h-30) с горизонтальным скроллом
|
||||
- **Внешний контейнер**: bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0
|
||||
- **Внутренний контейнер скролла**: flex gap-3 overflow-x-auto p-4
|
||||
- **Стилизация скролла**: scrollbarWidth: 'thin' для тонкой полосы прокрутки
|
||||
- **Отступы**: padding: 16px (p-4) внутри, gap: 12px (gap-3) между карточками
|
||||
- **Адаптивная высота**: по содержимому карточек (БЕЗ фиксированной высоты)
|
||||
- **Визуальное единство**: стеклянный эффект как у других блоков системы
|
||||
- **БЕЗ заголовков/иконок**: только чистые карточки товаров в контейнере
|
||||
|
||||
**РАЗМЕРЫ КАРТОЧЕК ТОВАРОВ:**
|
||||
- **Компактная карточка**: 80×112px (соотношение 5:7), вертикальное изображение
|
||||
- **Отступы**: 12px между карточками, без дополнительных отступов от краев
|
||||
- **Компактная карточка**: 80×112px (w-20 h-28), соотношение 5:7
|
||||
- **Адаптивность**: фиксированный размер для всех устройств
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
|
||||
- **Только изображение**: 80×112px товара, вертикальное
|
||||
- **Минималистичный дизайн**: без текста, названий, цен
|
||||
- **Состояния**: выбранное/невыбранное с визуальной индикацией
|
||||
- **Hover эффект**: увеличение border, изменение тени
|
||||
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
|
||||
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
|
||||
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
|
||||
- **Рамка**: border-white/10, при выборе border-white/30
|
||||
- **Фон**: bg-white/5 полупрозрачный
|
||||
|
||||
**ДЕЙСТВИЕ:**
|
||||
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
|
||||
|
||||
### 9.2.2.1 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ
|
||||
#### **9.2.3 Правила Блока 3 "Детальный каталог товаров"**
|
||||
|
||||
**НАЗНАЧЕНИЕ И СТРУКТУРА:**
|
||||
- **Контент**: Детальные карточки выбранных товаров с полным управлением
|
||||
- **Верхняя панель**: Выбор даты + Выбор Fulfillment + Поиск
|
||||
- **Основная область**: Сетка карточек товаров с детальной информацией
|
||||
|
||||
#### **9.2.3.1 Структура верхней панели Блока 3**
|
||||
|
||||
**МИНИМАЛИСТИЧНАЯ ПАНЕЛЬ УПРАВЛЕНИЯ:**
|
||||
- **Выбор даты поставки**: DatePicker для планирования поставки
|
||||
- **Выбор Fulfillment-центра**: Select dropdown со списком доступных фулфилментов
|
||||
- **Поиск по товарам**: Input с иконкой поиска и placeholder
|
||||
- **Компоновка**: Горизонтальная строка с равномерным распределением
|
||||
|
||||
**ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ:**
|
||||
```tsx
|
||||
// Структура компонентов панели
|
||||
<div className="flex items-center gap-4 p-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl mb-4">
|
||||
<DatePicker placeholder="Дата поставки" />
|
||||
<Select placeholder="Выберите фулфилмент">
|
||||
<SelectContent>
|
||||
{fulfillmentCenters.map(center => (
|
||||
<SelectItem value={center.id}>{center.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
|
||||
<Input placeholder="Поиск товаров..." className="pl-10 glass-input" />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### **9.2.3.2 Структура основной области карточек**
|
||||
|
||||
**СЕТКА ТОВАРОВ:**
|
||||
- **Адаптивная сетка**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`
|
||||
- **Детальные карточки**: Полная информация + количество + управление
|
||||
- **Состояния**: Default, Selected, Editing
|
||||
- **Интерактивность**: Изменение количества, удаление, настройки рецептуры
|
||||
|
||||
**ФУНКЦИОНАЛЬНОСТЬ ПАНЕЛИ:**
|
||||
- **Выбор даты**: Планирование времени поставки (обязательное поле)
|
||||
- **Выбор фулфилмента**: Определение исполнителя поставки (обязательное поле)
|
||||
- **Поиск**: Фильтрация товаров в каталоге по названию/артикулу
|
||||
- **Валидация**: Блокировка создания поставки без заполнения даты и фулфилмента
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
- **Пустой каталог**: Заглушка "Добавьте товары"
|
||||
- **Нет фулфилментов**: Сообщение "Настройте партнерство с фулфилмент-центрами"
|
||||
- **Поиск без результатов**: "По запросу ничего не найдено"
|
||||
|
||||
#### **9.2.2.1 Структура контейнера Блока 2**
|
||||
|
||||
**ДВУХУРОВНЕВАЯ АРХИТЕКТУРА:**
|
||||
|
||||
**УРОВЕНЬ 1 - Внешний контейнер (блок):**
|
||||
```jsx
|
||||
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0">
|
||||
```
|
||||
- **Назначение**: Визуальное обрамление блока, единство с другими блоками
|
||||
- **Стилизация**: Стеклянный эффект с размытием и полупрозрачностью
|
||||
- **Рамка**: Тонкая белая рамка border-white/20 с закруглёнными углами
|
||||
- **Поведение**: flex-shrink-0 предотвращает сжатие блока
|
||||
|
||||
**УРОВЕНЬ 2 - Внутренний контейнер (скролл):**
|
||||
```jsx
|
||||
<div className="flex gap-3 overflow-x-auto p-4" style={{ scrollbarWidth: 'thin' }}>
|
||||
```
|
||||
- **Назначение**: Горизонтальная прокрутка карточек товаров
|
||||
- **Раскладка**: Flex с промежутками gap-3 (12px) между карточками
|
||||
- **Отступы**: padding p-4 (16px) со всех сторон
|
||||
- **Скролл**: overflow-x-auto с тонкой полосой прокрутки
|
||||
- **Поведение**: Автоматическое появление скролла при превышении ширины
|
||||
|
||||
**ПРАВИЛА КОНТЕЙНЕРОВ:**
|
||||
- Внешний контейнер НЕ содержит заголовков, иконок, описаний
|
||||
- Внутренний контейнер содержит ТОЛЬКО карточки товаров
|
||||
- Высота адаптируется под размер карточек (80×112px + отступы)
|
||||
- Визуальное единство со всеми блоками формы поставки
|
||||
|
||||
**ТЕХНИЧЕСКИЕ ПРАВИЛА:**
|
||||
- **Условие отображения**: selectedSupplier && products.length > 0
|
||||
- **Источник данных**: products массив из GraphQL запроса organizationProducts
|
||||
- **Реактивность**: Автоматическое обновление при смене поставщика
|
||||
- **Производительность**: React.memo для карточек при большом количестве товаров
|
||||
- **Доступность**: Клавиатурная навигация (Tab, Enter для выбора)
|
||||
|
||||
**UX ПРАВИЛА ВЗАИМОДЕЙСТВИЯ:**
|
||||
- **Скролл**: Автоматическое появление при превышении ширины контейнера
|
||||
- **Индикация загрузки**: Скелетоны карточек во время загрузки товаров
|
||||
- **Пустое состояние**: Скрытие блока при отсутствии поставщика или товаров
|
||||
- **Фокус**: Первая карточка получает фокус при загрузке товаров
|
||||
- **Навигация**: Стрелки ←→ для перемещения между карточками
|
||||
|
||||
**СОСТОЯНИЯ БЛОКА:**
|
||||
- **Скрыт**: При отсутствии выбранного поставщика
|
||||
- **Скрыт**: При отсутствии товаров у поставщика
|
||||
- **Активен**: При наличии поставщика и товаров
|
||||
- **Загрузка**: Показ скелетонов карточек во время запроса
|
||||
|
||||
**ПРАВИЛА ПРОИЗВОДИТЕЛЬНОСТИ:**
|
||||
- **Виртуализация**: При количестве товаров > 100
|
||||
- **Ленивая загрузка изображений**: loading="lazy" для всех изображений
|
||||
- **Мемоизация**: React.memo для компонентов карточек
|
||||
- **Дебаунс**: 300мс для поисковых запросов (если будет добавлен поиск)
|
||||
|
||||
**ПРАВИЛА АДАПТИВНОСТИ:**
|
||||
- **Мобильные устройства**: Свайп для горизонтальной прокрутки
|
||||
- **Планшеты**: Сохранение размеров карточек 80×112px
|
||||
- **Десктоп**: Полная функциональность с клавиатурной навигацией
|
||||
- **Высокие разрешения**: Сохранение пропорций и читаемости
|
||||
|
||||
**ПРАВИЛА БЕЗОПАСНОСТИ И ВАЛИДАЦИИ:**
|
||||
- **Валидация данных**: Проверка существования product.id перед добавлением
|
||||
- **Дубликаты**: Предотвращение добавления одного товара дважды в детальный каталог
|
||||
- **Санитизация**: Безопасное отображение названий товаров (XSS защита)
|
||||
- **Обработка ошибок**: Graceful degradation при ошибках загрузки изображений
|
||||
- **Защита от спама**: Дебаунс кликов 200мс для предотвращения множественных добавлений
|
||||
|
||||
**ПРАВИЛА ИНТЕГРАЦИИ С ДРУГИМИ БЛОКАМИ:**
|
||||
- **Блок 1 (Поставщики)**: Слушает изменения selectedSupplier для обновления товаров
|
||||
- **Блок 3 (Детальный каталог)**: Передаёт выбранные товары через setAllSelectedProducts
|
||||
- **Блок 4 (Корзина)**: Товары добавляются в корзину из Блока 3, не напрямую из Блока 2
|
||||
- **Синхронизация состояний**: Реактивное обновление при изменении данных в любом блоке
|
||||
|
||||
**ПРАВИЛА АНАЛИТИКИ И МЕТРИК:**
|
||||
- **Отслеживание кликов**: Логирование добавления товаров в детальный каталог
|
||||
- **Метрики производительности**: Время загрузки товаров поставщика
|
||||
- **Пользовательское поведение**: Количество просмотренных товаров на поставщика
|
||||
- **A/B тестирование**: Готовность к тестированию различных размеров карточек
|
||||
|
||||
**ПРАВИЛА ЛОКАЛИЗАЦИИ:**
|
||||
- **Alt-текст изображений**: На языке интерфейса пользователя
|
||||
- **Направление скролла**: RTL поддержка для арабского/иврита
|
||||
- **Размеры карточек**: Неизменны для всех локалей (80×112px)
|
||||
- **Сообщения об ошибках**: Локализованные уведомления при проблемах загрузки
|
||||
|
||||
#### **9.2.1.1 Заголовок и поиск Блока 1**
|
||||
|
||||
**МИНИМАЛИСТИЧНЫЙ ДИЗАЙН:**
|
||||
```jsx
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5 text-blue-400" />
|
||||
<h2 className="text-lg font-semibold text-white">Поставщики</h2>
|
||||
</div>
|
||||
<div className="w-64">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Поиск поставщиков..."
|
||||
className="bg-white/5 border-white/10 text-white placeholder:text-white/50 pl-10 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**ПРАВИЛА ЗАГОЛОВКА:**
|
||||
- **Иконка**: Building2 h-5 w-5 text-blue-400 (без фонового контейнера)
|
||||
- **Текст**: "Поставщики" (убран избыточный "товаров")
|
||||
- **Размер**: text-lg font-semibold (увеличен для лучшей читаемости)
|
||||
- **БЕЗ бэджа**: Убран избыточный бэдж "Создание поставки"
|
||||
- **Выравнивание**: flex items-center gap-2 (компактное)
|
||||
|
||||
**ПРАВИЛА ПОИСКА:**
|
||||
- **Позиция**: Справа от заголовка (justify-between)
|
||||
- **Ширина**: w-64 (256px) фиксированная ширина
|
||||
- **Плейсхолдер**: "Поиск поставщиков..." (конкретное описание)
|
||||
- **Иконка**: Search h-4 w-4 слева в поле
|
||||
- **Стили**: Стандартные glass-эффекты, focus:border-white/20
|
||||
|
||||
**ПРАВИЛА КНОПКИ "НАЙТИ В МАРКЕТЕ":**
|
||||
- **Условие**: Показывается только при allCounterparties.length === 0
|
||||
- **Позиция**: Отдельный блок под заголовком (mt-4)
|
||||
- **НЕ интегрирована**: В поле поиска (отдельно)
|
||||
- **Стили**: glass-secondary outline button размера sm
|
||||
|
||||
#### **9.2.1.2 Структура карточки поставщика в Блоке 1**
|
||||
|
||||
**МИНИМАЛИСТИЧНАЯ КАРТОЧКА ПОСТАВЩИКА:**
|
||||
|
||||
**СТРУКТУРА ИНФОРМАЦИИ:**
|
||||
```jsx
|
||||
<div className="flex items-start gap-2">
|
||||
<OrganizationAvatar organization={supplier} size="sm" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-white font-medium text-sm truncate">
|
||||
{supplier.name || supplier.fullName}
|
||||
</h4>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<p className="text-white/60 text-xs font-mono">ИНН: {supplier.inn}</p>
|
||||
{supplier.market && (
|
||||
<Badge className="market-badge">
|
||||
{getMarketLabel(supplier.market)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**ПРАВИЛА СОДЕРЖАНИЯ КАРТОЧКИ:**
|
||||
|
||||
**✅ ОСТАВИТЬ:**
|
||||
- **Аватар организации**: OrganizationAvatar size="sm" слева
|
||||
- **Название поставщика**: supplier.name || supplier.fullName (приоритет name)
|
||||
- **ИНН**: font-mono, text-white/60, с префиксом "ИНН: "
|
||||
|
||||
**🔸 ДОБАВИТЬ:**
|
||||
- **Принадлежность к рынку**: Badge с названием рынка из supplier.market
|
||||
- **Рынки**: "Садовод", "ТЯК Москва" и другие из Organization.market поля
|
||||
|
||||
**❌ УБРАТЬ:**
|
||||
- **Рейтинг**: Звездочка и цифра rating (избыточно)
|
||||
- **Тип бэдж**: "Поставщик" badge (и так понятно из контекста)
|
||||
- **Адрес**: supplier.address (занимает место, не критично)
|
||||
|
||||
**СТИЛИ РЫНОЧНЫХ БЭДЖЕЙ:**
|
||||
- **Садовод**: bg-green-500/20 text-green-300 border-green-500/30
|
||||
- **ТЯК Москва**: bg-blue-500/20 text-blue-300 border-blue-500/30
|
||||
- **По умолчанию**: bg-gray-500/20 text-gray-300 border-gray-500/30
|
||||
|
||||
**ПРАВИЛА АДАПТИВНОСТИ:**
|
||||
- **Мобильные**: Сохранение структуры, truncate для длинных названий
|
||||
- **Планшеты/десктоп**: Полное отображение в сетке
|
||||
- **Малые экраны**: line-clamp-1 для названия организации
|
||||
|
||||
**СОСТОЯНИЯ КАРТОЧКИ:**
|
||||
- **Default**: bg-white/5 border-white/10
|
||||
- **Hover**: hover:border-white/20 hover:bg-white/10
|
||||
- **Selected**: bg-white/15 border-white/40 shadow-lg
|
||||
- **Disabled**: opacity-50 cursor-not-allowed (при недоступности)
|
||||
|
||||
**ПРАВИЛА ИНТЕГРАЦИИ С РЫНКАМИ:**
|
||||
|
||||
**ИСТОЧНИК ДАННЫХ:**
|
||||
- **Поле БД**: Organization.market (String?) - поле принадлежности к рынку
|
||||
- **Настройка**: Указывается в настройках кабинета поставщика
|
||||
- **Опциональность**: Поле может быть пустым (рынок не указан)
|
||||
|
||||
**ФУНКЦИЯ getMarketLabel():**
|
||||
```jsx
|
||||
const getMarketLabel = (market?: string) => {
|
||||
const marketLabels = {
|
||||
'sadovod': 'Садовод',
|
||||
'tyak-moscow': 'ТЯК Москва',
|
||||
'opt-market': 'ОПТ Маркет',
|
||||
}
|
||||
return marketLabels[market as keyof typeof marketLabels] || market
|
||||
}
|
||||
```
|
||||
|
||||
**СТИЛИ ДЛЯ РЫНКОВ:**
|
||||
```jsx
|
||||
const getMarketBadgeStyle = (market?: string) => {
|
||||
const styles = {
|
||||
'sadovod': 'bg-green-500/20 text-green-300 border-green-500/30',
|
||||
'tyak-moscow': 'bg-blue-500/20 text-blue-300 border-blue-500/30',
|
||||
'opt-market': 'bg-purple-500/20 text-purple-300 border-purple-500/30',
|
||||
}
|
||||
return styles[market as keyof typeof styles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30'
|
||||
}
|
||||
```
|
||||
|
||||
**ПРАВИЛА ОТОБРАЖЕНИЯ:**
|
||||
- **Условие**: Показывать badge только если supplier.market существует
|
||||
- **Размер**: text-xs для соответствия ИНН
|
||||
- **Позиция**: Справа от ИНН в той же строке
|
||||
- **Приоритет**: Рынок важнее типа организации для селлера
|
||||
|
||||
### 9.2.2.2 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ
|
||||
|
||||
**🎯 ОСНОВНОЙ ПРИНЦИП:**
|
||||
Выбранные товары в детальном каталоге (блок 3) сохраняются при смене поставщика и могут быть удалены только явным действием пользователя.
|
||||
@ -1194,88 +1474,6 @@ const handleSuppliesClick = () => {
|
||||
- Визуальная индикация выбранных товаров в блоке 2
|
||||
- Информация о поставщике для каждого товара в блоке 3
|
||||
|
||||
### 9.2.2.2 ПРАВИЛО ВСПЛЫВАЮЩЕЙ ПОДСКАЗКИ ТОВАРА
|
||||
|
||||
**🎯 ОСНОВНОЙ ПРИНЦИП:**
|
||||
При наведении курсора на компактную карточку товара в блоке 2 появляется динамическое модальное окно с полной информацией о товаре.
|
||||
|
||||
**📱 АДАПТИВНОСТЬ:**
|
||||
- Планшеты: показывать по долгому нажатию (500ms), работает как desktop
|
||||
- Desktop: стандартное поведение hover (300ms задержка)
|
||||
- Мобильные: не показывать (< 768px)
|
||||
|
||||
**🎨 ДИЗАЙН И РАЗМЕРЫ:**
|
||||
- Ширина: 220px фиксированная
|
||||
- Высота: фиксированная (не изменяется)
|
||||
- Фон: `bg-white/10 backdrop-blur-xl` (подстраивается под блок)
|
||||
- Граница: `border border-white/20`, скругления: `rounded-xl`
|
||||
- Тень: `shadow-2xl`
|
||||
|
||||
**📊 СТРУКТУРА КОНТЕНТА (ПРИОРИТЕТ):**
|
||||
|
||||
**ЗАГОЛОВОК (КРУПНО):**
|
||||
- Название товара: `text-lg font-semibold`, truncate
|
||||
- Цена: `text-lg font-bold` "₽ 2,500 за шт"
|
||||
|
||||
**ГРУППЫ ИНФОРМАЦИИ:**
|
||||
1. **ОСНОВНОЕ**: Остатки (с цветовой индикацией) + Категория (badge)
|
||||
2. **ХАРАКТЕРИСТИКИ** (если есть): Цвет, размеры, объемы, комплектность
|
||||
3. **СЛУЖЕБНОЕ**: Артикул формата `SP-ABC123456`
|
||||
|
||||
**📊 ИСТОЧНИК ДАННЫХ:**
|
||||
- Только из карточки товара `GoodsProduct` - никаких дополнительных запросов
|
||||
- Если данных нет - не показываем этот пункт
|
||||
- Показываем только то, что есть
|
||||
|
||||
**🖱️ ИНТЕРАКТИВНОСТЬ:**
|
||||
- Read-only - никакого взаимодействия внутри подсказки
|
||||
- Клик на карточку добавляет товар (как обычно)
|
||||
- Подсказка не блокирует основное взаимодействие
|
||||
|
||||
**📐 ПОЗИЦИОНИРОВАНИЕ:**
|
||||
- Умное позиционирование по наибольшему свободному месту
|
||||
- Приоритет: справа → слева → сверху → снизу
|
||||
- Частично видимые карточки: все равно показывать подсказку
|
||||
- Отступы от краев экрана: минимум 16px
|
||||
|
||||
**🚨 ОБРАБОТКА ОШИБОК:**
|
||||
- При ошибках загрузки: не показывать подсказку вообще
|
||||
- Без изображения: показывать данные как обычно
|
||||
- Длинные названия: truncate, размеры модалки НЕ изменять
|
||||
|
||||
**⚡ ПРОИЗВОДИТЕЛЬНОСТЬ:**
|
||||
- Debounce: 300ms задержка перед показом
|
||||
- Throttle: позиционирование при скролле/ресайзе
|
||||
- React.memo для оптимизации рендера
|
||||
|
||||
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
|
||||
```css
|
||||
.products-cards-container {
|
||||
height: 160px; /* Общая высота блока */
|
||||
flex-shrink: 0;
|
||||
min-width: 0; /* Предотвращает растяжение */
|
||||
}
|
||||
|
||||
.products-cards-block {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-behavior: smooth;
|
||||
gap: 10px;
|
||||
padding: 0 12px 6px 12px; /* px-3 pb-1.5 */
|
||||
height: 120px; /* h-30 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #64748b33 transparent;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
flex-shrink: 0;
|
||||
width: 180px; /* Десктоп */
|
||||
height: 88px; /* Фиксированная высота */
|
||||
padding: 6px; /* p-1.5 */
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
```
|
||||
|
||||
**СОСТОЯНИЯ БЛОКА:**
|
||||
- **Не выбран поставщик**: Заглушка "Выберите поставщика для просмотра товаров"
|
||||
- **Поставщик выбран, нет товаров**: "У поставщика нет товаров"
|
||||
@ -1284,7 +1482,7 @@ const handleSuppliesClick = () => {
|
||||
**ВЗАИМОДЕЙСТВИЕ:**
|
||||
- **Навигация**: Горизонтальная прокрутка мышью, клавишами ←→
|
||||
- **Выбор**: Клик → добавление в Блок 3 с анимацией
|
||||
- **Состояния карточек**: Default, Hover, Active (при добавлении)
|
||||
- **Состояния карточек**: Default, Selected, Active (при добавлении)
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
- **1-5 карточек**: Скролл неактивен, выравнивание по левому краю
|
||||
@ -1539,9 +1737,10 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins)
|
||||
- Горизонтальный скролл при превышении ширины
|
||||
- Выбор только одного поставщика одновременно
|
||||
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(160px - НОВЫЙ БЛОК)_:
|
||||
- Компактные карточки товаров выбранного поставщика
|
||||
- Горизонтальный скролл аналогично блоку 1
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(адаптивная высота - НОВЫЙ БЛОК)_:
|
||||
- ТОЛЬКО минималистичные карточки товаров 80×112px
|
||||
- ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
|
||||
- Горизонтальный скролл при множестве товаров
|
||||
- Клик добавляет товар в блок 3
|
||||
|
||||
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(flex-1, детальный каталог)_:
|
||||
@ -1596,14 +1795,67 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins)
|
||||
|
||||
## 10. 🏪 КАБИНЕТ ПОСТАВЩИКА
|
||||
|
||||
### 10.1 Основные возможности
|
||||
### 10.1 Разделение понятий: РЫНОК vs МАРКЕТ
|
||||
|
||||
**🔍 КРИТИЧЕСКОЕ РАЗДЕЛЕНИЕ ПОНЯТИЙ:**
|
||||
|
||||
### **РЫНОК** 🏪 - физическое торговое место
|
||||
- **Назначение**: Географическая принадлежность поставщиков
|
||||
- **Примеры**: Садовод, ТЯК Москва
|
||||
- **Структура**: Название + адрес
|
||||
- **Связь**: Поставщик принадлежит рынку
|
||||
|
||||
### **МАРКЕТ** 🛒 - раздел системы для торговли
|
||||
- **Назначение**: Глобальный каталог товаров в системе
|
||||
- **Роут**: `/market` - просмотр и заказ товаров
|
||||
- **Содержание**: Все доступные товары от всех поставщиков
|
||||
- **Связь**: НЕ связан с физическими рынками
|
||||
|
||||
**🏢 АРХИТЕКТУРА ПРИНАДЛЕЖНОСТИ:**
|
||||
|
||||
```
|
||||
РЫНОК (физическое место)
|
||||
└── Поставщик (Organization.market)
|
||||
└── Товары/Расходники (наследуют рынок от поставщика)
|
||||
└── Отображаются в МАРКЕТЕ (/market)
|
||||
```
|
||||
|
||||
**🎯 ПРИНЦИПЫ ИЕРАРХИИ:**
|
||||
|
||||
1. **РЫНОК → ПОСТАВЩИК**: Поставщик работает на конкретном рынке
|
||||
2. **ПОСТАВЩИК → ТОВАРЫ**: Товары принадлежат поставщику с его рынка
|
||||
3. **ТОВАРЫ → МАРКЕТ**: Все товары показываются в глобальном маркете (/market)
|
||||
4. **НАСЛЕДОВАНИЕ**: Товары получают рынок от организации поставщика
|
||||
|
||||
**🏪 ФИЗИЧЕСКИЕ РЫНКИ В СИСТЕМЕ:**
|
||||
|
||||
- **"Садовод"** (`sadovod`) - Москва, 14-й км МКАД
|
||||
- **Цветовая схема**: `bg-green-500/20 text-green-300 border-green-500/30`
|
||||
- **"ТЯК Москва"** (`tyak-moscow`) - Москва, Алтуфьевское шоссе, 27
|
||||
- **Цветовая схема**: `bg-blue-500/20 text-blue-300 border-blue-500/30`
|
||||
|
||||
**🛒 МАРКЕТ В СИСТЕМЕ:**
|
||||
|
||||
- **Роут**: `/market` - глобальный каталог товаров
|
||||
- **Функции**: Просмотр, поиск, фильтрация, заказ товаров
|
||||
- **Источник**: Товары от всех поставщиков всех рынков
|
||||
- **Отображение рынка**: В карточках поставщиков и товаров
|
||||
|
||||
**🔧 ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
|
||||
|
||||
- **Поле рынка**: `Organization.market` (String?) - принадлежность поставщика к рынку
|
||||
- **Настройка рынка**: В настройках организации поставщика
|
||||
- **Отображение в маркете**: Товары показывают рынок через `product.organization.market`
|
||||
- **Фильтрация**: В маркете по рынку поставщика
|
||||
|
||||
### 10.2 Основные возможности
|
||||
|
||||
**СОЗДАНИЕ КАРТОЧЕК**:
|
||||
|
||||
- **ТОВАР** - базовые товары поставщика
|
||||
- **РАСХОДНИКИ** - материалы и вспомогательные товары
|
||||
|
||||
### 10.2 Обязательные поля карточки
|
||||
### 10.3 Обязательные поля карточки
|
||||
|
||||
**Базовые параметры**:
|
||||
|
||||
@ -1618,7 +1870,7 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins)
|
||||
- Цена за единицу и за комплект
|
||||
- Заказано, В пути, Остаток, Продано
|
||||
|
||||
### 10.3 Отображение информации в карточках
|
||||
### 10.4 Отображение информации в карточках
|
||||
|
||||
**Каждая карточка содержит**:
|
||||
|
||||
@ -1629,7 +1881,7 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins)
|
||||
- Данные о движении: остаток, заказано, в пути, продано
|
||||
- Индикаторы низких остатков
|
||||
|
||||
### 10.4 Статистика поставщика
|
||||
### 10.5 Статистика поставщика
|
||||
|
||||
**Блок статистики включает**:
|
||||
|
||||
@ -1773,29 +2025,96 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins)
|
||||
|
||||
### 11.7 Услуги фулфилмента (`/services`)
|
||||
|
||||
#### 11.7.1 Структура: 3 обязательные вкладки
|
||||
#### 11.7.1 Архитектура интеграции с системой
|
||||
|
||||
**СВЯЗЬ С РЕЦЕПТУРАМИ СЕЛЛЕРОВ:**
|
||||
```
|
||||
СЕЛЛЕР (создание поставки)
|
||||
└── Рецептура
|
||||
├── Товар (от поставщика)
|
||||
├── Услуги фулфилмента ← CRUD в разделе Услуги
|
||||
├── Расходники селлера
|
||||
└── Расходники фулфилмента ← ТОЛЬКО с установленной ценой
|
||||
↓
|
||||
ФУЛФИЛМЕНТ (обработка)
|
||||
├── Входящие поставки → Поставки расходников (создание)
|
||||
└── Услуги → Расходники (установка цены за единицу)
|
||||
```
|
||||
|
||||
#### 11.7.2 Структура: 3 обязательные вкладки
|
||||
|
||||
**A) 🛠️ УСЛУГИ** (`defaultValue="services"`):
|
||||
- **CRUD операции**: создание, редактирование, удаление услуг
|
||||
- **Управление ценами** и описаниями
|
||||
- **Загрузка изображений** услуг (`imageUrl`)
|
||||
- **Полный CRUD**: создание, редактирование, удаление услуг
|
||||
- **Поля**: `name`, `description`, `price`, `imageUrl`
|
||||
- **Glass Upload Zone**: элегантная загрузка изображений
|
||||
- **Назначение**: каталог услуг для рецептур селлеров
|
||||
- **GraphQL**: `GET_MY_SERVICES`, `CREATE_SERVICE`, `UPDATE_SERVICE`, `DELETE_SERVICE`
|
||||
|
||||
**B) 🚚 ЛОГИСТИКА**:
|
||||
- **Создание маршрутов доставки** (откуда → куда)
|
||||
- **Тарификация**: цена до 1м³ и свыше 1м³
|
||||
- **Полный CRUD**: маршруты доставки
|
||||
- **Поля**: откуда → куда, тарификация до/свыше 1м³
|
||||
- **Группированные локации**:
|
||||
- Мой фулфилмент (название организации)
|
||||
- Рынки (предустановленные)
|
||||
- Склады Wildberries
|
||||
- Склады Ozon
|
||||
- **Glass Upload Zone**: для изображений маршрутов
|
||||
- **GraphQL**: `GET_MY_LOGISTICS`, `CREATE_LOGISTICS`, `UPDATE_LOGISTICS`, `DELETE_LOGISTICS`
|
||||
|
||||
**C) 📦 РАСХОДНИКИ**:
|
||||
- **Управление расходниками фулфилмента**
|
||||
- **Интеграция с модулем "Услуги"** - селлеры могут использовать в услугах
|
||||
- **Списание со складских остатков** при использовании
|
||||
- **Стоимость включается** в стоимость услуги
|
||||
**C) 📦 РАСХОДНИКИ** (**❌ БЕЗ СОЗДАНИЯ**):
|
||||
- **ТОЛЬКО ПРОСМОТР** расходников с фулфилмент-склада
|
||||
- **ЕДИНСТВЕННОЕ РЕДАКТИРУЕМОЕ ПОЛЕ**: `pricePerUnit` - цена за единицу для рецептур
|
||||
- **UI подсветка**: "Цена за 1 {unit}" (например, "Цена за 1 шт")
|
||||
- **Автоматическая синхронизация** при приеме поставки расходников
|
||||
- **Glass Upload Zone**: для обновления изображений расходников
|
||||
- **GraphQL**: `GET_MY_SUPPLIES` (read-only), `UPDATE_SUPPLY_PRICE`
|
||||
|
||||
#### 11.7.3 Workflow расходников
|
||||
|
||||
**ШАГ 1 - СОЗДАНИЕ**: Только через "Входящие поставки → Поставки расходников фулфилмента"
|
||||
|
||||
**ШАГ 2 - СИНХРОНИЗАЦИЯ**: При приеме на склад → автоматически в Услуги/Расходники
|
||||
|
||||
**ШАГ 3 - ЦЕНООБРАЗОВАНИЕ**: Установка цены за единицу в разделе Услуги
|
||||
|
||||
**ШАГ 4 - ИСПОЛЬЗОВАНИЕ**: Доступны в рецептурах селлеров
|
||||
|
||||
#### 11.7.4 Правила видимости в рецептурах
|
||||
|
||||
**В РЕЦЕПТУРАХ СЕЛЛЕРОВ ПОКАЗЫВАЮТСЯ ТОЛЬКО:**
|
||||
- `isAvailable = true` (есть на skladе)
|
||||
- `pricePerUnit != null` (цена установлена)
|
||||
|
||||
**НЕ ПОКАЗЫВАЮТСЯ:**
|
||||
- Расходники без цены (`pricePerUnit = null`)
|
||||
- Удаленные со склада (`isAvailable = false`)
|
||||
|
||||
**В РАЗДЕЛЕ УСЛУГИ/РАСХОДНИКИ ВИДНЫ ВСЕ:**
|
||||
- С визуальной индикацией состояния (активные/неактивные/без цены)
|
||||
|
||||
#### 11.7.5 Технические требования
|
||||
|
||||
**GraphQL типы:**
|
||||
```graphql
|
||||
# Для рецептур - только доступные с ценой
|
||||
getAvailableSuppliesForRecipe: [SupplyForRecipe!]!
|
||||
|
||||
# В разделе Услуги - все расходники
|
||||
getMySupplies: [Supply!]!
|
||||
|
||||
type Supply {
|
||||
pricePerUnit: Float # Может быть null
|
||||
unit: String! # "шт", "кг", "м"
|
||||
isAvailable: Boolean! # Статус на складе
|
||||
warehouseConsumableId: ID! # Связь со складом
|
||||
}
|
||||
```
|
||||
|
||||
**Экономический учет:**
|
||||
- Создание: через поставки расходников
|
||||
- Ценообразование: в разделе Услуги
|
||||
- Списание: со склада при использовании
|
||||
- Стоимость = количество × цена за единицу
|
||||
|
||||
### 11.8 Сотрудники фулфилмента (`/employees`)
|
||||
|
||||
@ -2206,6 +2525,8 @@ const wholesalePartners = await prisma.counterparty.findMany({
|
||||
19. ❌ **Показывать расходники в формах создания поставок товаров** (строгая типизация `PRODUCT`/`CONSUMABLE`)
|
||||
20. ❌ **Фильтровать предметы по типу на фронтенде** (фильтрация должна быть в GraphQL резолвере)
|
||||
21. ❌ **ИСПОЛЬЗОВАТЬ МОКОВЫЕ ДАННЫЕ БЕЗ РАЗРЕШЕНИЯ** - все компоненты ОБЯЗАТЕЛЬНО должны использовать реальные GraphQL запросы. Моковые данные можно добавлять ТОЛЬКО с явного разрешения пользователя
|
||||
22. ❌ **ДОБАВЛЯТЬ ПОЛЕ РЫНКА К ТОВАРАМ** - рынок принадлежит организации поставщика (`Organization.market`), товары наследуют рынок через связь с организацией
|
||||
23. ❌ **ПУТАТЬ РЫНОК И МАРКЕТ** - РЫНОК = физическое место (Садовод, ТЯК), МАРКЕТ = раздел системы (/market)
|
||||
|
||||
### 17.2 ОБЯЗАТЕЛЬНЫЕ ПРАВИЛА:
|
||||
|
||||
@ -2317,6 +2638,54 @@ interface GoodsProduct {
|
||||
- **Использовать параметризованные запросы** (`organizationId`, `type`, `search`) вместо фильтрации на фронтенде
|
||||
- **Добавлять подробное логирование** в резолверы для отладки (входные параметры, результаты фильтрации)
|
||||
- **Типы запросов должны отражать бизнес-логику**: `organizationProducts` для товаров конкретной организации
|
||||
|
||||
### 18.7 Правила РЫНКОВ и МАРКЕТА
|
||||
|
||||
**🔍 КРИТИЧЕСКОЕ РАЗДЕЛЕНИЕ:**
|
||||
|
||||
- **РЫНОК** 🏪 = физическое место (Садовод, ТЯК)
|
||||
- **МАРКЕТ** 🛒 = раздел системы `/market`
|
||||
|
||||
**ПОЛЕ РЫНКА В SCHEMA:**
|
||||
|
||||
- **Organization.market** ✅ - поставщик принадлежит физическому рынку
|
||||
- **Product.market** ❌ - ЗАПРЕЩЕНО, товары наследуют рынок от организации
|
||||
- **Отображение рынка товаров**: через `product.organization.market`
|
||||
- **Фильтрация по рынкам**: через `organization.market`, НЕ через `product.market`
|
||||
|
||||
**ЗАПРОСЫ С РЫНКАМИ:**
|
||||
|
||||
```graphql
|
||||
# ✅ ПРАВИЛЬНО - рынок от организации поставщика
|
||||
query GetProductsWithMarket {
|
||||
myProducts {
|
||||
id
|
||||
name
|
||||
organization {
|
||||
market # Физический рынок поставщика
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ✅ ПРАВИЛЬНО - товары в маркете с информацией о рынке
|
||||
query GetMarketProducts {
|
||||
marketProducts {
|
||||
id
|
||||
name
|
||||
organization {
|
||||
market # Рынок поставщика
|
||||
name # Название поставщика
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**МАРКЕТ (/market) ПРАВИЛА:**
|
||||
|
||||
- **Назначение**: Глобальный каталог всех товаров
|
||||
- **Фильтрация**: По рынкам поставщиков, типам товаров, категориям
|
||||
- **Отображение**: Показать рынок поставщика в карточках товаров
|
||||
- **НЕ путать**: МАРКЕТ ≠ конкретный физический рынок
|
||||
- **Значения по умолчанию в резолверах** для критических параметров (`type: args.type || "PRODUCT"`)
|
||||
- **Валидация обязательных параметров** на уровне схемы (`organizationId: ID!`)
|
||||
- **Кеширование обходить при проблемах** через `fetchPolicy: 'network-only'`
|
||||
|
Reference in New Issue
Block a user