Реализация реферальной системы и улучшение системы авторизации
- Добавлена полная реферальная система с GraphQL резолверами и UI компонентами - Улучшена система регистрации с поддержкой ВКонтакте и реферальных ссылок - Обновлена схема Prisma для поддержки реферальной системы - Добавлены новые файлы документации правил системы - Улучшена система партнерства и контрагентов - Обновлены компоненты авторизации для поддержки новых функций - Удален устаревший server.log 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
47
CLAUDE.md
47
CLAUDE.md
@ -5,11 +5,14 @@
|
||||
### Обязательные для чтения:
|
||||
|
||||
- **`rules-complete.md`** - основные бизнес-правила (ВСЕГДА читать первым)
|
||||
- **`workflow-catalog.md`** - каталог всех бизнес-процессов системы
|
||||
|
||||
### Специфичные правила по кабинетам:
|
||||
|
||||
- **`wholesale-cabinet-rules.md`** - при работе с кабинетом поставщика
|
||||
- **`logist-cabinet-rules.md`** - при работе с кабинетом логистики
|
||||
- **`fulfillment-cabinet-rules.md`** - при работе с кабинетом фулфилмента
|
||||
- **`seller-ui-rules.md`** - при работе с UI/UX кабинета селлера
|
||||
- **`visual-design-rules.md`** - при работе с UI/UX
|
||||
|
||||
### Правила взаимодействия:
|
||||
@ -20,6 +23,9 @@
|
||||
|
||||
- Упоминание "поставщик", "wholesale", "/warehouse", "/supplier-orders" → читать wholesale-cabinet-rules.md
|
||||
- Упоминание "логистика", "доставка", "logist", "/logistics-requests", "/routes" → читать logist-cabinet-rules.md
|
||||
- Упоминание "фулфилмент", "fulfillment", "/services", "/employees" → читать fulfillment-cabinet-rules.md
|
||||
- Упоминание "селлер", "seller", "/supplies", "/my-supplies" → читать seller-ui-rules.md
|
||||
- Упоминание "workflow", "процесс", "этап", "статус" → читать workflow-catalog.md
|
||||
- Упоминание "дизайн", "UI", "компонент", "стиль" → читать visual-design-rules.md
|
||||
|
||||
## 🚨 ЕДИНСТВЕННЫЙ ИСТОЧНИК ПРАВИЛ
|
||||
@ -63,6 +69,47 @@
|
||||
- **Честность и прозрачность**: открыто сообщать о неопределенностях
|
||||
- **Протоколы по сложности**: для каждого типа задач свой подход
|
||||
|
||||
## 🔧 КОМАНДЫ ПРОВЕРКИ КОДА
|
||||
|
||||
### Обязательные команды после изменений:
|
||||
|
||||
```bash
|
||||
# TypeScript проверка типов
|
||||
npm run typecheck
|
||||
|
||||
# Проверка линтером
|
||||
npm run lint
|
||||
|
||||
# Запуск тестов
|
||||
npm test
|
||||
|
||||
# Dev сервер для проверки работы
|
||||
npm run dev
|
||||
```
|
||||
|
||||
> ⚠️ **ВАЖНО**: Всегда выполнять эти команды перед завершением задачи!
|
||||
|
||||
## 💾 РАБОТА С КОНТЕКСТОМ
|
||||
|
||||
### Файлы для сохранения контекста:
|
||||
|
||||
- **`current-session.md`** - текущая сессия работы (активные задачи, решения, контекст)
|
||||
- **`CLAUDE.md`** - системные правила и команды (этот файл)
|
||||
- **TodoWrite инструмент** - для планирования и отслеживания задач
|
||||
|
||||
### При потере контекста:
|
||||
|
||||
1. **Первым делом прочитать**: `current-session.md`
|
||||
2. **Проверить статус задач**: через TodoWrite
|
||||
3. **Восстановить контекст**: из истории изменений в current-session.md
|
||||
|
||||
### Рекомендации для длинных сессий:
|
||||
|
||||
- Обновлять `current-session.md` после каждой важной задачи
|
||||
- Фиксировать принятые решения и обоснования
|
||||
- Документировать обнаруженные проблемы и их решения
|
||||
- Использовать `--resume` флаг для продолжения сессий
|
||||
|
||||
## 🚨 НАПОМИНАНИЕ
|
||||
|
||||
**Этот файл служит для корректной работы system-reminder'ов. Все детальные правила находятся в `rules-complete.md`!**
|
||||
|
106
current-session.md
Normal file
106
current-session.md
Normal file
@ -0,0 +1,106 @@
|
||||
# ТЕКУЩАЯ СЕССИЯ РАБОТЫ
|
||||
|
||||
> 📅 Дата начала: 2025-08-10
|
||||
> 🎯 Цель: Отслеживание контекста и прогресса текущей работы
|
||||
|
||||
---
|
||||
|
||||
## 📋 АКТИВНЫЕ ЗАДАЧИ
|
||||
|
||||
### Текущая задача:
|
||||
- **Что делаем**: Создание структуры для сохранения контекста
|
||||
- **Статус**: В процессе
|
||||
- **Начато**: 2025-08-10
|
||||
|
||||
### Очередь задач:
|
||||
1. ✅ Восстановить rules-complete.md из backup
|
||||
2. 🔄 Создать систему сохранения контекста
|
||||
3. ⏳ [Следующие задачи будут добавлены]
|
||||
|
||||
---
|
||||
|
||||
## 🔧 ТЕКУЩИЙ КОНТЕКСТ ПРОЕКТА
|
||||
|
||||
### О проекте SFERA:
|
||||
**Тип**: Система управления складами и поставками (B2B маркетплейс)
|
||||
**Технологии**:
|
||||
- Frontend: Next.js 15.4.1 (React 19), TypeScript, Tailwind CSS
|
||||
- Backend: GraphQL (Apollo Server), Prisma ORM
|
||||
- База данных: PostgreSQL (через Prisma)
|
||||
- UI: Radix UI, Lucide icons, shadcn/ui компоненты
|
||||
|
||||
### Архитектура:
|
||||
- **4 типа кабинетов**: SELLER (селлер), FULFILLMENT (фулфилмент), WHOLESALE (поставщик), LOGIST (логистика)
|
||||
- **Типы предметов**: PRODUCT (товар), CONSUMABLE (расходники), DEFECT (брак), FINISHED_PRODUCT (готовый продукт)
|
||||
- **Workflow поставок**: 8 статусов от PENDING до DELIVERED
|
||||
- **Система партнерства**: через модель Counterparty
|
||||
|
||||
### Ключевые особенности:
|
||||
- Строгая типизация GraphQL + TypeScript
|
||||
- Ролевая модель доступа (проверки на уровне резолверов)
|
||||
- Модульная структура компонентов по кабинетам
|
||||
- Glass-эффекты и OKLCH цветовая система в UI
|
||||
|
||||
### Важные решения:
|
||||
- Восстановлен файл rules-complete.md из backup-20250809-192625 (3,301 строк)
|
||||
- Удалена испорченная версия (2,686 строк)
|
||||
- Создана система сохранения контекста (current-session.md, task-template.md)
|
||||
|
||||
### Обнаруженные проблемы:
|
||||
- Claude часто теряет контекст при длинных сессиях
|
||||
- Необходима система для сохранения важной информации между сообщениями
|
||||
|
||||
### Согласованные подходы:
|
||||
- Использовать TodoWrite для планирования
|
||||
- Документировать все важные решения
|
||||
- Следовать правилам из interaction-integrity-rules.md
|
||||
- Всегда читать rules-complete.md перед изменениями
|
||||
|
||||
---
|
||||
|
||||
## 💡 ВАЖНЫЕ ОТКРЫТИЯ И РЕШЕНИЯ
|
||||
|
||||
### Структура правил системы:
|
||||
- `rules-complete.md` - основные бизнес-правила
|
||||
- `interaction-integrity-rules.md` - методология работы Claude
|
||||
- `CLAUDE.md` - системные правила и напоминания
|
||||
- Специфичные правила по кабинетам (wholesale, logist, fulfillment, seller)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 КОМАНДЫ ДЛЯ ПРОВЕРКИ
|
||||
|
||||
```bash
|
||||
# TypeScript проверка
|
||||
npm run typecheck
|
||||
|
||||
# Линтинг
|
||||
npm run lint
|
||||
|
||||
# Тесты
|
||||
npm test
|
||||
|
||||
# Dev сервер
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 ЗАМЕТКИ ДЛЯ СЛЕДУЮЩЕЙ СЕССИИ
|
||||
|
||||
- При продолжении работы ОБЯЗАТЕЛЬНО прочитать этот файл первым
|
||||
- Проверить статус задач в TodoWrite
|
||||
- Продолжить с последней незавершенной задачи
|
||||
|
||||
---
|
||||
|
||||
## 🔄 ИСТОРИЯ ИЗМЕНЕНИЙ
|
||||
|
||||
### 2025-08-10
|
||||
- Создан файл current-session.md
|
||||
- Восстановлен rules-complete.md из резервной копии
|
||||
- Начата работа над системой сохранения контекста
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **ВАЖНО**: Этот файл обновляется в течение сессии для сохранения контекста!
|
686
fulfillment-cabinet-rules.md
Normal file
686
fulfillment-cabinet-rules.md
Normal file
@ -0,0 +1,686 @@
|
||||
# ПРАВИЛА КАБИНЕТА ФУЛФИЛМЕНТА (FULFILLMENT)
|
||||
|
||||
> ⚠️ **ВАЖНО**: Это файл с техническими деталями кабинета фулфилмента.
|
||||
> Общие бизнес-правила находятся в **[rules-complete.md](./rules-complete.md)**
|
||||
|
||||
## Когда использовать этот файл:
|
||||
|
||||
- Работа с компонентами `/warehouse`, `/fulfillment-supplies`, `/services`, `/employees`
|
||||
- GraphQL запросы для фулфилмента
|
||||
- UI/UX специфика кабинета фулфилмента
|
||||
- Технические детали реализации услуг и логистики
|
||||
|
||||
## 1. 🏭 СТРУКТУРА КАБИНЕТА ФУЛФИЛМЕНТА
|
||||
|
||||
### 1.1 Основные разделы
|
||||
|
||||
**ФУЛФИЛМЕНТ (`FULFILLMENT`)** имеет доступ к следующим разделам:
|
||||
|
||||
- **Склад** (`/warehouse`) - управление товарами по модулям
|
||||
- **Поставки** (`/fulfillment-supplies`) - обработка поставок
|
||||
- **Услуги** (`/services`) - управление услугами и расходниками
|
||||
- **Сотрудники** (`/employees`) - управление персоналом
|
||||
- **Статистика** (`/fulfillment-statistics`) - аналитика фулфилмента
|
||||
- **Партнеры** (`/partners`) - управление контрагентами
|
||||
- **Мессенджер** (`/messenger`) - связь с партнерами
|
||||
- **Настройки** (`/settings`) - профиль и настройки
|
||||
- **Экономика** (`/economics`) - финансовая аналитика
|
||||
|
||||
### 1.2 Навигация и роутинг
|
||||
|
||||
#### При входе в систему:
|
||||
|
||||
```typescript
|
||||
switch (user?.organization?.type) {
|
||||
case 'FULFILLMENT':
|
||||
router.push('/warehouse') // Направляем на склад
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
#### Специальная логика роутинга:
|
||||
|
||||
- **МАКСИМАЛЬНЫЕ ПРАВА**: Доступ ко ВСЕМ разделам системы
|
||||
- **АДАПТИВНАЯ НАВИГАЦИЯ**: Sidebar изменяется в зависимости от типа организации
|
||||
- **СПЕЦИАЛЬНЫЙ РОУТИНГ**: `/fulfillment-supplies` вместо `/supplies`
|
||||
|
||||
> 📖 **Бизнес-логика роутинга**: См. [rules-complete.md#4-система-ролей-и-доступов](./rules-complete.md#4--система-ролей-и-доступов)
|
||||
|
||||
## 2. 🎨 UI/UX КОМПОНЕНТЫ
|
||||
|
||||
### 2.1 Dashboard компоненты
|
||||
|
||||
#### Основные компоненты кабинета:
|
||||
|
||||
- `FulfillmentWarehouseDashboard` - склад по модулям
|
||||
- `FulfillmentSuppliesDashboard` - поставки фулфилмента
|
||||
- `ServicesManagement` - управление услугами
|
||||
- `FulfillmentStatistics` - статистика и аналитика
|
||||
- `EmployeeManagement` - управление сотрудниками
|
||||
|
||||
#### Wrapper-компоненты:
|
||||
|
||||
- `HomePageWrapper` - маршрутизация по типам организаций
|
||||
- `EconomicsPageWrapper` - адаптивная экономика по кабинетам
|
||||
|
||||
#### Специализированные компоненты:
|
||||
|
||||
- `WarehouseModuleCard` - карточка модуля склада
|
||||
- `ServiceCard` - карточка услуги
|
||||
- `EmployeeCard` - карточка сотрудника
|
||||
- `FulfillmentStats` - статистика фулфилмента
|
||||
- `LogisticsRouteCard` - карточка маршрута доставки
|
||||
|
||||
### 2.2 Архитектурные особенности
|
||||
|
||||
#### Технические правила отображения:
|
||||
|
||||
```tsx
|
||||
// GraphQL проверки
|
||||
const { data } = useQuery(GET_FULFILLMENT_DATA, {
|
||||
skip: user?.organization?.type !== 'FULFILLMENT'
|
||||
})
|
||||
|
||||
// Условное отображение
|
||||
{user?.organization?.type === "FULFILLMENT" && (
|
||||
<FulfillmentExclusiveComponent />
|
||||
)}
|
||||
|
||||
// Адаптивные отступы
|
||||
const margin = getSidebarMargin()
|
||||
|
||||
// Полинг данных для реального времени
|
||||
const { data } = useQuery(GET_FULFILLMENT_STATS, {
|
||||
pollInterval: 60000 // Обновление каждую минуту
|
||||
})
|
||||
```
|
||||
|
||||
### 2.3 Система учета склада (3-уровневая иерархия)
|
||||
|
||||
#### Структура данных:
|
||||
|
||||
```
|
||||
🔵 УРОВЕНЬ 1: МАГАЗИНЫ
|
||||
↓
|
||||
🟢 УРОВЕНЬ 2: ТОВАРЫ (зеленый - green-500)
|
||||
↓
|
||||
🟠 УРОВЕНЬ 3: ВАРИАНТЫ ТОВАРОВ (оранжевый - orange-500)
|
||||
```
|
||||
|
||||
#### Цветовое кодирование:
|
||||
|
||||
- Каждый уровень имеет цветной индикатор увеличивающегося размера
|
||||
- Цветная левая граница с увеличивающимся отступом и толщиной
|
||||
- Скроллбары в цвете уровня
|
||||
- Контрастный цвет текста для читаемости
|
||||
|
||||
## 3. 📊 ФУНКЦИОНАЛЬНЫЕ ВОЗМОЖНОСТИ
|
||||
|
||||
> 📖 **Бизнес-правила**: См. [rules-complete.md#11-кабинет-фулфилмента](./rules-complete.md#11--кабинет-фулфилмента) для правил workflow и процессов
|
||||
|
||||
### 3.1 Структура склада по модулям (ОБЯЗАТЕЛЬНАЯ ПОСЛЕДОВАТЕЛЬНОСТЬ)
|
||||
|
||||
1. **📦 ПРОДУКТЫ** - готовые к отправке товары
|
||||
2. **🛒 ТОВАРЫ** - базовые товары от поставщиков
|
||||
- **"На складе"** - готовы к обработке
|
||||
- **"В обработке"** - в процессе создания продукта
|
||||
3. **❌ БРАК** - товары с дефектами, требуют утилизации
|
||||
4. **↩️ ВОЗВРАТЫ С ПВЗ** - возвращенные товары, к обработке
|
||||
5. **🎯 РАСХОДНИКИ СЕЛЛЕРОВ** - материалы для селлеров
|
||||
6. **⚙️ РАСХОДНИКИ ФУЛФИЛМЕНТА** - операционные материалы (КЛИКАБЕЛЬНЫЙ модуль)
|
||||
|
||||
### 3.2 Система учета склада
|
||||
|
||||
#### Показатели движения (дополнительные значения):
|
||||
|
||||
- **ПРИБЫЛО** - количество поступивших на склад за период
|
||||
- **УБЫЛО** - количество списанных со склада за период
|
||||
|
||||
#### Текущие остатки (основные значения):
|
||||
|
||||
- **ФОРМУЛА**: Основные значения = Предыдущие остатки + Прибыло - Убыло
|
||||
- **ОБНОВЛЕНИЕ**: В реальном времени с изменениями за сутки
|
||||
- **ИСТОЧНИК**: GraphQL query `GET_FULFILLMENT_WAREHOUSE_STATS`
|
||||
|
||||
### 3.3 Поставки фулфилмента (`/fulfillment-supplies`)
|
||||
|
||||
#### Структура: 2 основные вкладки
|
||||
|
||||
**A) 🛒 ПОСТАВКИ ТОВАРОВ**:
|
||||
- **Детализированные товары ФФ** - планы и факты поставок с маршрутами
|
||||
- **Товары ФФ** - общие поставки товаров от селлеров
|
||||
- **Возвраты с ПВЗ** - обработка возвращенных товаров
|
||||
|
||||
**B) 🔧 ПОСТАВКИ РАСХОДНИКОВ**:
|
||||
- **Заказы расходников** - управление заказами от селлеров
|
||||
- **Расходники селлеров** - материалы для клиентов
|
||||
- **Создание поставок** - формирование новых поставок расходников
|
||||
|
||||
#### Workflow поставок товаров:
|
||||
|
||||
1. **Planned** - поставка запланирована
|
||||
2. **In-transit** - товар в пути
|
||||
3. **Delivered** - доставлен на склад
|
||||
4. **Completed** - обработка завершена
|
||||
|
||||
### 3.4 Услуги фулфилмента (`/services`)
|
||||
|
||||
#### Архитектура интеграции с системой
|
||||
|
||||
**СВЯЗЬ С РЕЦЕПТУРАМИ СЕЛЛЕРОВ:**
|
||||
|
||||
```
|
||||
СЕЛЛЕР (создание поставки)
|
||||
└── Рецептура
|
||||
├── Товар (от поставщика)
|
||||
├── Услуги фулфилмента ← CRUD в разделе Услуги
|
||||
├── Расходники селлера
|
||||
└── Расходники фулфилмента ← ТОЛЬКО с установленной ценой
|
||||
↓
|
||||
ФУЛФИЛМЕНТ (обработка)
|
||||
├── Входящие поставки → Поставки расходников (создание)
|
||||
└── Услуги → Расходники (установка цены за единицу)
|
||||
```
|
||||
|
||||
#### Структура: 3 обязательные вкладки
|
||||
|
||||
**A) 🛠️ УСЛУГИ** (`defaultValue="services"`):
|
||||
- **Полный CRUD**: создание, редактирование, удаление услуг
|
||||
- **Поля**: `name`, `description`, `price`, `imageUrl`
|
||||
- **Glass Upload Zone**: элегантная загрузка изображений
|
||||
- **Назначение**: каталог услуг для рецептур селлеров
|
||||
|
||||
**B) 🚚 ЛОГИСТИКА**:
|
||||
- **Полный CRUD**: маршруты доставки
|
||||
- **Поля**: откуда → куда, тарификация до/свыше 1м³
|
||||
- **Группированные локации**:
|
||||
- Мой фулфилмент (название организации)
|
||||
- Рынки (предустановленные)
|
||||
- Склады Wildberries
|
||||
- Склады Ozon
|
||||
- **Glass Upload Zone**: для изображений маршрутов
|
||||
|
||||
**C) 📦 РАСХОДНИКИ** (**❌ БЕЗ СОЗДАНИЯ**):
|
||||
- **ТОЛЬКО ПРОСМОТР** расходников с фулфилмент-склада
|
||||
- **ЕДИНСТВЕННОЕ РЕДАКТИРУЕМОЕ ПОЛЕ**: `pricePerUnit` - цена за единицу для рецептур
|
||||
- **UI подсветка**: "Цена за 1 {unit}" (например, "Цена за 1 шт")
|
||||
- **Автоматическая синхронизация** при приеме поставки расходников
|
||||
- **Glass Upload Zone**: для обновления изображений расходников
|
||||
|
||||
#### Workflow расходников:
|
||||
|
||||
**ШАГ 1 - СОЗДАНИЕ**: Только через "Входящие поставки → Поставки расходников фулфилмента"
|
||||
**ШАГ 2 - СИНХРОНИЗАЦИЯ**: При приеме на склад → автоматически в Услуги/Расходники
|
||||
**ШАГ 3 - ЦЕНООБРАЗОВАНИЕ**: Установка цены за единицу в разделе Услуги
|
||||
**ШАГ 4 - ИСПОЛЬЗОВАНИЕ**: Доступны в рецептурах селлеров
|
||||
|
||||
#### Правила видимости в рецептурах:
|
||||
|
||||
**В РЕЦЕПТУРАХ СЕЛЛЕРОВ ПОКАЗЫВАЮТСЯ ТОЛЬКО:**
|
||||
- `isAvailable = true` (есть на складе)
|
||||
- `pricePerUnit != null` (цена установлена)
|
||||
|
||||
**НЕ ПОКАЗЫВАЮТСЯ:**
|
||||
- Расходники без цены (`pricePerUnit = null`)
|
||||
- Удаленные со склада (`isAvailable = false`)
|
||||
|
||||
### 3.5 Разделение цен закупки и продажи
|
||||
|
||||
**КРИТИЧЕСКОЕ ПРАВИЛО**: Расходники фулфилмента имеют **ДВЕ РАЗНЫЕ ЦЕНЫ**:
|
||||
|
||||
1. **ЦЕНА ЗАКУПКИ** (`Supply.price`) - цена покупки у поставщика
|
||||
2. **ЦЕНА ПРОДАЖИ** (`Supply.pricePerUnit`) - цена продажи селлерам
|
||||
|
||||
#### Поля в базе данных:
|
||||
|
||||
```prisma
|
||||
model Supply {
|
||||
price Decimal @db.Decimal(10, 2) // Цена закупки у поставщика (НЕИЗМЕННАЯ)
|
||||
pricePerUnit Decimal? @db.Decimal(10, 2) // Цена продажи селлерам (устанавливается фулфилментом)
|
||||
}
|
||||
```
|
||||
|
||||
#### Правила отображения по разделам:
|
||||
|
||||
**РАЗДЕЛ "СКЛАД → РАСХОДНИКИ ФУЛФИЛМЕНТА"**:
|
||||
- Показывает `Supply.price` (цена закупки)
|
||||
- Цена ТОЛЬКО ДЛЯ ЧТЕНИЯ, нельзя изменять
|
||||
- Отражает историческую стоимость приобретения
|
||||
|
||||
**РАЗДЕЛ "УСЛУГИ → РАСХОДНИКИ"**:
|
||||
- Показывает и редактирует `Supply.pricePerUnit` (цена продажи)
|
||||
- Единственное место где можно изменить цену для селлеров
|
||||
- Влияет на рецептуры и расчеты для селлеров
|
||||
|
||||
#### Бизнес-логика создания:
|
||||
|
||||
```typescript
|
||||
// ПРИ ПОСТУПЛЕНИИ ОТ ПОСТАВЩИКА:
|
||||
const supply = await prisma.supply.create({
|
||||
data: {
|
||||
price: item.price, // Цена поставщика → ЗАФИКСИРОВАНА
|
||||
pricePerUnit: null, // Цена продажи → ПУСТАЯ
|
||||
},
|
||||
})
|
||||
|
||||
// УСТАНОВКА ЦЕНЫ ПРОДАЖИ (в разделе "Услуги"):
|
||||
const updated = await prisma.supply.update({
|
||||
data: {
|
||||
pricePerUnit: newPrice, // ТОЛЬКО цена продажи
|
||||
// price НЕ ТРОГАЕМ - остается цена закупки
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 3.6 Сотрудники фулфилмента (`/employees`)
|
||||
|
||||
#### Структура: 2 основные вкладки
|
||||
|
||||
**A) 👥 СОТРУДНИКИ** (`defaultValue="combined"`):
|
||||
|
||||
**Управление персоналом**:
|
||||
- **CRUD операции**: создание, редактирование, удаление сотрудников
|
||||
- **Статусы сотрудников**: `ACTIVE`, `VACATION`, `SICK`, `FIRED`
|
||||
- **Формы добавления**: Компактная (`showCompactForm`) / Полная форма
|
||||
- **Поиск и фильтрация** по имени, должности, статусу
|
||||
|
||||
**Табель рабочего времени**:
|
||||
- **Навигация по месяцам**: текущий год/месяц с кнопками ←/→
|
||||
- **Отметки по дням**: статус дня и количество отработанных часов
|
||||
|
||||
**B) 📋 ОТЧЕТЫ** (`value="reports"`):
|
||||
- **Сводные отчеты** по сотрудникам за период
|
||||
- **Экспорт данных** табеля
|
||||
- **Аналитика рабочего времени**
|
||||
|
||||
### 3.7 Статистика фулфилмента (`/fulfillment-statistics`)
|
||||
|
||||
#### Блоки аналитики (сворачиваемые):
|
||||
|
||||
**1. НАКОПЛЕННАЯ СТАТИСТИКА** (`allTime: true`):
|
||||
- Обработано товаров (общий объем)
|
||||
- Выявлено брака (всего единиц)
|
||||
- Поставок получено
|
||||
- Общий доход (за все время)
|
||||
- Выполнено заказов (успешных отгрузок)
|
||||
- Удовлетворенность клиентов (средний рейтинг)
|
||||
|
||||
**2. ОТГРУЗКА НА ПЛОЩАДКИ** (`marketplaces: true`):
|
||||
- Отправлено на Wildberries
|
||||
- Отправлено на Ozon
|
||||
- Отправлено на другие площадки
|
||||
|
||||
**3. АНАЛИТИКА ПРОИЗВОДИТЕЛЬНОСТИ** (`performance: false`):
|
||||
- Среднее время обработки (на единицу товара)
|
||||
- Уровень брака (от общего объема)
|
||||
- Уровень возвратов (возвраты с площадок)
|
||||
- Удовлетворенность клиентов
|
||||
|
||||
## 4. 🛠️ GRAPHQL API
|
||||
|
||||
### 4.1 Основные запросы (Queries)
|
||||
|
||||
#### Получение статистики склада:
|
||||
|
||||
```graphql
|
||||
query GetFulfillmentWarehouseStats {
|
||||
fulfillmentWarehouseStats {
|
||||
products {
|
||||
total
|
||||
received
|
||||
shipped
|
||||
}
|
||||
supplies {
|
||||
onStock
|
||||
inProcessing
|
||||
}
|
||||
defects {
|
||||
total
|
||||
pending
|
||||
}
|
||||
returns {
|
||||
pending
|
||||
processed
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Получение услуг фулфилмента:
|
||||
|
||||
```graphql
|
||||
query GetMyServices {
|
||||
myServices {
|
||||
id
|
||||
name
|
||||
description
|
||||
price
|
||||
imageUrl
|
||||
isActive
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Получение расходников с ценами:
|
||||
|
||||
```graphql
|
||||
query GetMySupplies {
|
||||
mySupplies {
|
||||
id
|
||||
name
|
||||
price # Цена закупки (read-only)
|
||||
pricePerUnit # Цена продажи (editable)
|
||||
unit
|
||||
isAvailable
|
||||
warehouseConsumableId
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Получение логистических маршрутов:
|
||||
|
||||
```graphql
|
||||
query GetMyLogistics {
|
||||
myLogistics {
|
||||
id
|
||||
fromLocation
|
||||
toLocation
|
||||
tariffUnder1m3
|
||||
tariffOver1m3
|
||||
description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Получение сотрудников:
|
||||
|
||||
```graphql
|
||||
query GetEmployees {
|
||||
employees {
|
||||
id
|
||||
name
|
||||
position
|
||||
status
|
||||
schedule {
|
||||
date
|
||||
hoursWorked
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Мутации (Mutations)
|
||||
|
||||
#### Создание услуги:
|
||||
|
||||
```graphql
|
||||
mutation CreateService($input: ServiceInput!) {
|
||||
createService(input: $input) {
|
||||
success
|
||||
service {
|
||||
id
|
||||
name
|
||||
description
|
||||
price
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Обновление цены расходника:
|
||||
|
||||
```graphql
|
||||
mutation UpdateSupplyPrice($supplyId: ID!, $pricePerUnit: Float!) {
|
||||
updateSupplyPrice(id: $supplyId, pricePerUnit: $pricePerUnit) {
|
||||
success
|
||||
supply {
|
||||
id
|
||||
pricePerUnit
|
||||
isAvailable
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Создание логистического маршрута:
|
||||
|
||||
```graphql
|
||||
mutation CreateLogistics($input: LogisticsInput!) {
|
||||
createLogistics(input: $input) {
|
||||
success
|
||||
logistics {
|
||||
id
|
||||
fromLocation
|
||||
toLocation
|
||||
tariffUnder1m3
|
||||
tariffOver1m3
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Управление сотрудниками:
|
||||
|
||||
```graphql
|
||||
mutation CreateEmployee($input: EmployeeInput!) {
|
||||
createEmployee(input: $input) {
|
||||
success
|
||||
employee {
|
||||
id
|
||||
name
|
||||
position
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateEmployeeSchedule($employeeId: ID!, $date: String!, $hoursWorked: Int!, $status: String!) {
|
||||
updateEmployeeSchedule(employeeId: $employeeId, date: $date, hoursWorked: $hoursWorked, status: $status) {
|
||||
success
|
||||
schedule {
|
||||
date
|
||||
hoursWorked
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Специальные запросы для рецептур
|
||||
|
||||
#### Расходники доступные для рецептур (только с ценой):
|
||||
|
||||
```graphql
|
||||
query GetAvailableSuppliesForRecipe {
|
||||
availableSuppliesForRecipe {
|
||||
id
|
||||
name
|
||||
pricePerUnit # Только те что != null
|
||||
unit
|
||||
imageUrl
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GraphQL типы:
|
||||
|
||||
```graphql
|
||||
type Supply {
|
||||
id: ID!
|
||||
name: String!
|
||||
price: Float! # Цена закупки (неизменная)
|
||||
pricePerUnit: Float # Цена продажи (может быть null)
|
||||
unit: String! # "шт", "кг", "м"
|
||||
isAvailable: Boolean! # Статус на складе
|
||||
warehouseConsumableId: ID! # Связь со складом
|
||||
imageUrl: String
|
||||
}
|
||||
|
||||
type Service {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
price: Float!
|
||||
imageUrl: String
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type Logistics {
|
||||
id: ID!
|
||||
fromLocation: String!
|
||||
toLocation: String!
|
||||
tariffUnder1m3: Float!
|
||||
tariffOver1m3: Float!
|
||||
description: String
|
||||
organization: Organization!
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 📁 ТЕХНИЧЕСКИЕ КОМПОНЕНТЫ
|
||||
|
||||
### 5.1 Расположение компонентов
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── fulfillment/ # Компоненты фулфилмента
|
||||
│ ├── fulfillment-warehouse-dashboard.tsx
|
||||
│ ├── fulfillment-supplies-dashboard.tsx
|
||||
│ ├── fulfillment-statistics.tsx
|
||||
│ └── warehouse-module-card.tsx
|
||||
├── services/ # Компоненты услуг
|
||||
│ ├── services-management.tsx
|
||||
│ ├── service-card.tsx
|
||||
│ ├── logistics-management.tsx
|
||||
│ └── consumables-pricing.tsx
|
||||
├── employees/ # Компоненты сотрудников
|
||||
│ ├── employee-management.tsx
|
||||
│ ├── employee-card.tsx
|
||||
│ ├── employee-schedule.tsx
|
||||
│ └── employee-reports.tsx
|
||||
└── economics/ # Экономика
|
||||
└── fulfillment-economics-page.tsx
|
||||
```
|
||||
|
||||
### 5.2 Страницы (Pages)
|
||||
|
||||
```
|
||||
src/app/
|
||||
├── warehouse/
|
||||
│ └── page.tsx # Склад фулфилмента
|
||||
├── fulfillment-supplies/
|
||||
│ └── page.tsx # Поставки фулфилмента
|
||||
├── services/
|
||||
│ └── page.tsx # Услуги и расходники
|
||||
├── employees/
|
||||
│ └── page.tsx # Сотрудники
|
||||
└── fulfillment-statistics/
|
||||
└── page.tsx # Статистика
|
||||
```
|
||||
|
||||
## 6. 🚨 ТЕХНИЧЕСКИЕ ПРАВИЛА И ОГРАНИЧЕНИЯ
|
||||
|
||||
> 📖 **Workflow процессов**: См. [rules-complete.md#11-кабинет-фулфилмента](./rules-complete.md#11--кабинет-фулфилмента) для бизнес-процессов
|
||||
|
||||
### 6.1 Обязательные проверки:
|
||||
|
||||
- Проверка типа организации: `organization.type === 'FULFILLMENT'`
|
||||
- Валидация прав доступа на уровне GraphQL резолверов
|
||||
- Проверка наличия товаров на складе перед отгрузкой
|
||||
- Контроль цен расходников перед отображением в рецептурах
|
||||
|
||||
### 6.2 Правила безопасности доступа:
|
||||
|
||||
#### Контроль на уровне компонентов:
|
||||
|
||||
```typescript
|
||||
{user?.organization?.type === "FULFILLMENT" && (
|
||||
<FulfillmentWarehouseDashboard />
|
||||
)}
|
||||
|
||||
// Для полинга данных
|
||||
const { data } = useQuery(GET_FULFILLMENT_STATS, {
|
||||
skip: user?.organization?.type !== 'FULFILLMENT',
|
||||
pollInterval: 60000
|
||||
})
|
||||
```
|
||||
|
||||
#### Проверки в GraphQL резолверах:
|
||||
|
||||
```typescript
|
||||
// Проверка что пользователь - фулфилмент
|
||||
if (context.user.organization.type !== 'FULFILLMENT') {
|
||||
throw new Error('Access denied: Fulfillment access required')
|
||||
}
|
||||
|
||||
// Проверка доступа к своим услугам
|
||||
const service = await prisma.service.findFirst({
|
||||
where: {
|
||||
id: serviceId,
|
||||
organizationId: context.user.organizationId,
|
||||
},
|
||||
})
|
||||
|
||||
// Обязательное включение organization в ответы
|
||||
const updated = await prisma.service.update({
|
||||
where: { id: serviceId },
|
||||
data: input,
|
||||
include: { organization: true }, // ОБЯЗАТЕЛЬНО!
|
||||
})
|
||||
```
|
||||
|
||||
### 6.3 Запрещено:
|
||||
|
||||
- Создавать расходники в разделе "Услуги" (только через поставки)
|
||||
- Изменять цену закупки (`Supply.price`) - она фиксируется при поступлении
|
||||
- Показывать в рецептурах расходники без установленной цены продажи
|
||||
- Удалять услуги, используемые в активных рецептурах
|
||||
|
||||
### 6.4 Правила создания и ценообразования:
|
||||
|
||||
- **Услуги**: Создаются фулфилментом с установленной ценой
|
||||
- **Расходники**: Создаются только при поступлении от поставщиков
|
||||
- **Цена продажи**: Устанавливается отдельно в разделе Услуги
|
||||
- **Логистика**: Тарификация до/свыше 1м³ обязательна
|
||||
|
||||
### 6.5 Правила фулфилмента
|
||||
|
||||
**ОБЯЗАТЕЛЬНО**:
|
||||
- Установка цен на расходники перед доступностью селлерам
|
||||
- Контроль качества товаров при приемке
|
||||
- Своевременная обработка возвратов
|
||||
- Ведение учета движения товаров по модулям
|
||||
- Управление персоналом и рабочим временем
|
||||
|
||||
**ЗАПРЕЩЕНО**:
|
||||
- Отгружать товары без подтверждения наличия
|
||||
- Создавать расходники минуя систему поставок
|
||||
- Изменять цены закупки после поступления товара
|
||||
- Показывать в рецептурах неактивные расходники
|
||||
- Нарушать последовательность модулей склада
|
||||
|
||||
**ИНТЕГРАЦИЯ С ПАРТНЕРАМИ**:
|
||||
- Фулфилмент видит всех партнеров системы
|
||||
- Принимает поставки от всех типов организаций
|
||||
- Предоставляет услуги селлерам через рецептуры
|
||||
- Все взаимодействия фиксируются в системе уведомлений
|
||||
|
||||
> 📖 **Критические запреты**: См. [rules-complete.md#17-критические-запреты](./rules-complete.md#17--критические-запреты)
|
||||
|
||||
---
|
||||
|
||||
**Последнее обновление**: Август 2025
|
||||
**Связанные файлы**:
|
||||
|
||||
- [rules-complete.md](./rules-complete.md) - Общие бизнес-правила
|
||||
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила
|
@ -2,7 +2,6 @@ generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// Конфигурация для автоматического seeding
|
||||
generator seed {
|
||||
provider = "prisma-client-js"
|
||||
output = "./generated/client"
|
||||
@ -31,7 +30,7 @@ model User {
|
||||
model Admin {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
password String // Хеш пароля
|
||||
password String
|
||||
email String? @unique
|
||||
isActive Boolean @default(true)
|
||||
lastLogin DateTime?
|
||||
@ -89,6 +88,9 @@ model Organization {
|
||||
revenue BigInt?
|
||||
taxSystem String?
|
||||
dadataData Json?
|
||||
referralCode String? @unique
|
||||
referredById String?
|
||||
referralPoints Int @default(0)
|
||||
apiKeys ApiKey[]
|
||||
carts Cart?
|
||||
counterpartyOf Counterparty[] @relation("CounterpartyOf")
|
||||
@ -96,25 +98,31 @@ model Organization {
|
||||
receivedRequests CounterpartyRequest[] @relation("ReceivedRequests")
|
||||
sentRequests CounterpartyRequest[] @relation("SentRequests")
|
||||
employees Employee[]
|
||||
externalAds ExternalAd[] @relation("ExternalAds")
|
||||
favorites Favorites[]
|
||||
logistics Logistics[]
|
||||
receivedMessages Message[] @relation("ReceivedMessages")
|
||||
sentMessages Message[] @relation("SentMessages")
|
||||
referredBy Organization? @relation("ReferralRelation", fields: [referredById], references: [id])
|
||||
referrals Organization[] @relation("ReferralRelation")
|
||||
products Product[]
|
||||
referralTransactions ReferralTransaction[] @relation("ReferralTransactions")
|
||||
referrerTransactions ReferralTransaction[] @relation("ReferrerTransactions")
|
||||
sellerStatsCaches SellerStatsCache[] @relation("SellerStatsCaches")
|
||||
services Service[]
|
||||
supplies Supply[]
|
||||
users User[]
|
||||
logistics Logistics[]
|
||||
supplyOrders SupplyOrder[]
|
||||
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
|
||||
sellerSupplies Supply[] @relation("SellerSupplies")
|
||||
fulfillmentSupplyOrders SupplyOrder[] @relation("SupplyOrderFulfillmentCenter")
|
||||
logisticsSupplyOrders SupplyOrder[] @relation("SupplyOrderLogistics")
|
||||
wildberriesSupplies WildberriesSupply[]
|
||||
supplyOrders SupplyOrder[]
|
||||
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
|
||||
supplySuppliers SupplySupplier[] @relation("SupplySuppliers")
|
||||
externalAds ExternalAd[] @relation("ExternalAds")
|
||||
users User[]
|
||||
wbWarehouseCaches WBWarehouseCache[] @relation("WBWarehouseCaches")
|
||||
sellerStatsCaches SellerStatsCache[] @relation("SellerStatsCaches")
|
||||
sellerSupplies Supply[] @relation("SellerSupplies")
|
||||
wildberriesSupplies WildberriesSupply[]
|
||||
|
||||
@@index([referralCode])
|
||||
@@index([referredById])
|
||||
@@map("organizations")
|
||||
}
|
||||
|
||||
@ -153,10 +161,14 @@ model Counterparty {
|
||||
createdAt DateTime @default(now())
|
||||
organizationId String
|
||||
counterpartyId String
|
||||
type CounterpartyType @default(MANUAL)
|
||||
triggeredBy String?
|
||||
triggerEntityId String?
|
||||
counterparty Organization @relation("CounterpartyOf", fields: [counterpartyId], references: [id])
|
||||
organization Organization @relation("OrganizationCounterparties", fields: [organizationId], references: [id])
|
||||
|
||||
@@unique([organizationId, counterpartyId])
|
||||
@@index([type])
|
||||
@@map("counterparties")
|
||||
}
|
||||
|
||||
@ -203,26 +215,26 @@ model Supply {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String?
|
||||
price Decimal @db.Decimal(10, 2) // Цена закупки у поставщика (не меняется)
|
||||
pricePerUnit Decimal? @db.Decimal(10, 2) // Цена продажи селлерам (устанавливается фулфилментом)
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
pricePerUnit Decimal? @db.Decimal(10, 2)
|
||||
quantity Int @default(0)
|
||||
unit String @default("шт")
|
||||
category String @default("Расходники")
|
||||
status String @default("planned") // planned, in-transit, delivered, in-stock
|
||||
status String @default("planned")
|
||||
date DateTime @default(now())
|
||||
supplier String @default("Не указан")
|
||||
minStock Int @default(0)
|
||||
currentStock Int @default(0)
|
||||
usedStock Int @default(0) // Количество использованных расходников
|
||||
usedStock Int @default(0)
|
||||
imageUrl String?
|
||||
type SupplyType @default(FULFILLMENT_CONSUMABLES) // Тип расходников
|
||||
sellerOwnerId String? // ID селлера-владельца (для расходников селлеров)
|
||||
sellerOwner Organization? @relation("SellerSupplies", fields: [sellerOwnerId], references: [id], onDelete: SetNull)
|
||||
shopLocation String? // Местоположение в магазине фулфилмента
|
||||
type SupplyType @default(FULFILLMENT_CONSUMABLES)
|
||||
sellerOwnerId String?
|
||||
shopLocation String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
sellerOwner Organization? @relation("SellerSupplies", fields: [sellerOwnerId], references: [id])
|
||||
|
||||
@@map("supplies")
|
||||
}
|
||||
@ -266,9 +278,9 @@ model Product {
|
||||
organizationId String
|
||||
cartItems CartItem[]
|
||||
favorites Favorites[]
|
||||
supplyOrderItems SupplyOrderItem[]
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
supplyOrderItems SupplyOrderItem[]
|
||||
|
||||
@@unique([organizationId, article])
|
||||
@@map("products")
|
||||
@ -401,6 +413,156 @@ model WildberriesSupplyCard {
|
||||
@@map("wildberries_supply_cards")
|
||||
}
|
||||
|
||||
model Logistics {
|
||||
id String @id @default(cuid())
|
||||
fromLocation String
|
||||
toLocation String
|
||||
priceUnder1m3 Float
|
||||
priceOver1m3 Float
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
|
||||
@@map("logistics")
|
||||
}
|
||||
|
||||
model SupplyOrder {
|
||||
id String @id @default(cuid())
|
||||
partnerId String
|
||||
deliveryDate DateTime
|
||||
status SupplyOrderStatus @default(PENDING)
|
||||
totalAmount Decimal @db.Decimal(12, 2)
|
||||
totalItems Int
|
||||
fulfillmentCenterId String?
|
||||
logisticsPartnerId String?
|
||||
consumableType String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
items SupplyOrderItem[]
|
||||
fulfillmentCenter Organization? @relation("SupplyOrderFulfillmentCenter", fields: [fulfillmentCenterId], references: [id])
|
||||
logisticsPartner Organization? @relation("SupplyOrderLogistics", fields: [logisticsPartnerId], references: [id])
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id])
|
||||
|
||||
@@map("supply_orders")
|
||||
}
|
||||
|
||||
model SupplyOrderItem {
|
||||
id String @id @default(cuid())
|
||||
supplyOrderId String
|
||||
productId String
|
||||
quantity Int
|
||||
price Decimal @db.Decimal(12, 2)
|
||||
totalPrice Decimal @db.Decimal(12, 2)
|
||||
services String[] @default([])
|
||||
fulfillmentConsumables String[] @default([])
|
||||
sellerConsumables String[] @default([])
|
||||
marketplaceCardId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
supplyOrder SupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([supplyOrderId, productId])
|
||||
@@map("supply_order_items")
|
||||
}
|
||||
|
||||
model SupplySupplier {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
contactName String
|
||||
phone String
|
||||
market String?
|
||||
address String?
|
||||
place String?
|
||||
telegram String?
|
||||
organizationId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("SupplySuppliers", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("supply_suppliers")
|
||||
}
|
||||
|
||||
model ExternalAd {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
url String
|
||||
cost Decimal @db.Decimal(12, 2)
|
||||
date DateTime
|
||||
nmId String
|
||||
clicks Int @default(0)
|
||||
organizationId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("ExternalAds", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([organizationId, date])
|
||||
@@map("external_ads")
|
||||
}
|
||||
|
||||
model WBWarehouseCache {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
cacheDate DateTime
|
||||
data Json
|
||||
totalProducts Int @default(0)
|
||||
totalStocks Int @default(0)
|
||||
totalReserved Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("WBWarehouseCaches", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([organizationId, cacheDate])
|
||||
@@index([organizationId, cacheDate])
|
||||
@@map("wb_warehouse_caches")
|
||||
}
|
||||
|
||||
model SellerStatsCache {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
cacheDate DateTime
|
||||
period String
|
||||
dateFrom DateTime?
|
||||
dateTo DateTime?
|
||||
productsData Json?
|
||||
productsTotalSales Decimal? @db.Decimal(15, 2)
|
||||
productsTotalOrders Int?
|
||||
productsCount Int?
|
||||
advertisingData Json?
|
||||
advertisingTotalCost Decimal? @db.Decimal(15, 2)
|
||||
advertisingTotalViews Int?
|
||||
advertisingTotalClicks Int?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("SellerStatsCaches", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([organizationId, cacheDate, period, dateFrom, dateTo])
|
||||
@@index([organizationId, cacheDate])
|
||||
@@index([expiresAt])
|
||||
@@map("seller_stats_caches")
|
||||
}
|
||||
|
||||
model ReferralTransaction {
|
||||
id String @id @default(cuid())
|
||||
referrerId String
|
||||
referralId String
|
||||
points Int
|
||||
type ReferralTransactionType
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
referral Organization @relation("ReferralTransactions", fields: [referralId], references: [id])
|
||||
referrer Organization @relation("ReferrerTransactions", fields: [referrerId], references: [id])
|
||||
|
||||
@@index([referrerId, createdAt])
|
||||
@@index([referralId])
|
||||
@@map("referral_transactions")
|
||||
}
|
||||
|
||||
enum OrganizationType {
|
||||
FULFILLMENT
|
||||
SELLER
|
||||
@ -467,147 +629,20 @@ enum ProductType {
|
||||
}
|
||||
|
||||
enum SupplyType {
|
||||
FULFILLMENT_CONSUMABLES // Расходники фулфилмента (купленные фулфилментом для себя)
|
||||
SELLER_CONSUMABLES // Расходники селлеров (принятые от селлеров для хранения)
|
||||
FULFILLMENT_CONSUMABLES
|
||||
SELLER_CONSUMABLES
|
||||
}
|
||||
|
||||
model Logistics {
|
||||
id String @id @default(cuid())
|
||||
fromLocation String
|
||||
toLocation String
|
||||
priceUnder1m3 Float
|
||||
priceOver1m3 Float
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
|
||||
@@map("logistics")
|
||||
enum CounterpartyType {
|
||||
MANUAL
|
||||
REFERRAL
|
||||
AUTO_BUSINESS
|
||||
AUTO
|
||||
}
|
||||
|
||||
model SupplyOrder {
|
||||
id String @id @default(cuid())
|
||||
partnerId String
|
||||
deliveryDate DateTime
|
||||
status SupplyOrderStatus @default(PENDING)
|
||||
totalAmount Decimal @db.Decimal(12, 2)
|
||||
totalItems Int
|
||||
fulfillmentCenterId String?
|
||||
logisticsPartnerId String? // Опциональная логистика - может назначить фулфилмент
|
||||
consumableType String? // Классификация расходников: FULFILLMENT_CONSUMABLES, SELLER_CONSUMABLES
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
items SupplyOrderItem[]
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id])
|
||||
fulfillmentCenter Organization? @relation("SupplyOrderFulfillmentCenter", fields: [fulfillmentCenterId], references: [id])
|
||||
logisticsPartner Organization? @relation("SupplyOrderLogistics", fields: [logisticsPartnerId], references: [id])
|
||||
|
||||
@@map("supply_orders")
|
||||
}
|
||||
|
||||
model SupplyOrderItem {
|
||||
id String @id @default(cuid())
|
||||
supplyOrderId String
|
||||
productId String
|
||||
quantity Int
|
||||
price Decimal @db.Decimal(12, 2)
|
||||
totalPrice Decimal @db.Decimal(12, 2)
|
||||
// Поля для рецептуры продукта
|
||||
services String[] @default([]) // ID услуг
|
||||
fulfillmentConsumables String[] @default([]) // ID расходников фулфилмента
|
||||
sellerConsumables String[] @default([]) // ID расходников селлера
|
||||
marketplaceCardId String? // ID карточки маркетплейса
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
supplyOrder SupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade)
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
|
||||
@@unique([supplyOrderId, productId])
|
||||
@@map("supply_order_items")
|
||||
}
|
||||
|
||||
model SupplySupplier {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
contactName String
|
||||
phone String
|
||||
market String?
|
||||
address String?
|
||||
place String?
|
||||
telegram String?
|
||||
organizationId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("SupplySuppliers", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("supply_suppliers")
|
||||
}
|
||||
|
||||
model ExternalAd {
|
||||
id String @id @default(cuid())
|
||||
name String // Название рекламы
|
||||
url String // URL рекламы
|
||||
cost Decimal @db.Decimal(12, 2) // Стоимость
|
||||
date DateTime // Дата рекламы
|
||||
nmId String // ID товара Wildberries
|
||||
clicks Int @default(0) // Количество кликов
|
||||
organizationId String // ID организации
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("ExternalAds", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([organizationId, date])
|
||||
@@map("external_ads")
|
||||
}
|
||||
|
||||
model WBWarehouseCache {
|
||||
id String @id @default(cuid())
|
||||
organizationId String // ID организации
|
||||
cacheDate DateTime // Дата кеширования (только дата, без времени)
|
||||
data Json // Кешированные данные склада WB
|
||||
totalProducts Int @default(0) // Общее количество товаров
|
||||
totalStocks Int @default(0) // Общее количество остатков
|
||||
totalReserved Int @default(0) // Общее количество в резерве
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("WBWarehouseCaches", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([organizationId, cacheDate])
|
||||
@@index([organizationId, cacheDate])
|
||||
@@map("wb_warehouse_caches")
|
||||
}
|
||||
|
||||
model SellerStatsCache {
|
||||
id String @id @default(cuid())
|
||||
organizationId String // ID организации
|
||||
cacheDate DateTime // Дата кеширования (только дата, без времени)
|
||||
period String // Период статистики (week, month, quarter, custom)
|
||||
dateFrom DateTime? // Дата начала периода (для custom)
|
||||
dateTo DateTime? // Дата окончания периода (для custom)
|
||||
|
||||
// Данные товаров
|
||||
productsData Json? // Кешированные данные товаров
|
||||
productsTotalSales Decimal? @db.Decimal(15, 2) // Общая сумма продаж товаров
|
||||
productsTotalOrders Int? // Общее количество заказов товаров
|
||||
productsCount Int? // Количество товаров
|
||||
|
||||
// Данные рекламы
|
||||
advertisingData Json? // Кешированные данные рекламы
|
||||
advertisingTotalCost Decimal? @db.Decimal(15, 2) // Общие расходы на рекламу
|
||||
advertisingTotalViews Int? // Общие показы рекламы
|
||||
advertisingTotalClicks Int? // Общие клики рекламы
|
||||
|
||||
// Метаданные
|
||||
expiresAt DateTime // Время истечения кеша (24 часа)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation("SellerStatsCaches", fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([organizationId, cacheDate, period, dateFrom, dateTo])
|
||||
@@index([organizationId, cacheDate])
|
||||
@@index([expiresAt])
|
||||
@@map("seller_stats_caches")
|
||||
enum ReferralTransactionType {
|
||||
REGISTRATION
|
||||
AUTO_PARTNERSHIP
|
||||
FIRST_ORDER
|
||||
MONTHLY_BONUS
|
||||
}
|
||||
|
619
referral-system-rules.md
Normal file
619
referral-system-rules.md
Normal file
@ -0,0 +1,619 @@
|
||||
# 🎯 ПРАВИЛА РЕФЕРАЛЬНОЙ СИСТЕМЫ SFERA
|
||||
|
||||
⚠️ **КРИТИЧЕСКИ ВАЖНОЕ ПРАВИЛО: НЕ ПУТАТЬ "ПАРТНЁРСКИЕ ССЫЛКИ" И "РЕФЕРАЛЬНЫЕ ССЫЛКИ"**
|
||||
|
||||
> 📅 **Дата создания**: 2025-01-10
|
||||
> 🔧 **Статус**: Спецификация для разработки
|
||||
> 📌 **Версия**: 1.1
|
||||
|
||||
## 📋 ОГЛАВЛЕНИЕ
|
||||
|
||||
1. [Общие принципы](#1-общие-принципы)
|
||||
2. [Структура данных](#2-структура-данных)
|
||||
3. [Генерация реферальных ссылок](#3-генерация-реферальных-ссылок)
|
||||
4. [UI/UX компоненты](#4-uiux-компоненты)
|
||||
5. [Система начисления баллов](#5-система-начисления-баллов)
|
||||
6. [GraphQL API](#6-graphql-api)
|
||||
7. [Процесс регистрации по реферальной ссылке](#7-процесс-регистрации-по-реферальной-ссылке)
|
||||
8. [Автоматическое партнерство через бизнес-сделки](#8-автоматическое-партнерство-через-бизнес-сделки)
|
||||
9. [Безопасность и ограничения](#9-безопасность-и-ограничения)
|
||||
|
||||
---
|
||||
|
||||
## 1. ОБЩИЕ ПРИНЦИПЫ
|
||||
|
||||
### 1.1 Основные положения
|
||||
|
||||
- **УНИВЕРСАЛЬНОСТЬ**: Реферальная система доступна для всех типов кабинетов (SELLER, WHOLESALE, FULFILLMENT, LOGIST)
|
||||
- **АВТОМАТИЗАЦИЯ**: Реферальная ссылка генерируется автоматически при создании организации
|
||||
- **ПРОЗРАЧНОСТЬ**: Все начисления сфер ⚡ видны в режиме реального времени
|
||||
- **БЕЗОПАСНОСТЬ**: Невозможно изменить реферальную ссылку после генерации
|
||||
- **ИНТЕГРАЦИЯ С БИЗНЕСОМ**: Партнерство создается не только через рефералы, но и через коммерческие сделки
|
||||
|
||||
### 1.2 Терминология
|
||||
|
||||
- **РЕФЕРЕР** - организация, которая приглашает новых участников
|
||||
- **РЕФЕРАЛ** - организация, зарегистрированная по реферальной ссылке
|
||||
- **РЕФЕРАЛЬНЫЙ КОД** - уникальный идентификатор в ссылке (10 символов)
|
||||
- **СФЕРЫ ⚡** - единица вознаграждения за привлечение рефералов и коммерческие сделки
|
||||
- **АВТОПАРТНЕРСТВО** - автоматическое создание партнерских связей при коммерческих сделках
|
||||
- **РЕФЕРАЛЬНАЯ ССЫЛКА** - маркетинговый инструмент (`?ref=`), только начисление сфер
|
||||
- **ПАРТНЕРСКАЯ ССЫЛКА** - бизнес-инструмент (`?partner=`), сферы + автоматическое партнерство
|
||||
|
||||
### 1.3 Разделение ссылок
|
||||
|
||||
#### Реферальная система (маркетинг):
|
||||
- **URL формат**: `https://app.sfera.ru/register?ref=SF2X9K4M7P`
|
||||
- **Местоположение**: Вкладка "Рефералы"
|
||||
- **Цель**: Привлечение новых пользователей на платформу
|
||||
- **Результат**: +100 сфер ⚡, автоматического партнерства НЕТ
|
||||
- **Ссылка**: Уже готова при создании организации, быстрое копирование
|
||||
|
||||
#### Партнерская система (бизнес):
|
||||
- **URL формат**: `https://app.sfera.ru/register?partner=SF2X9K4M7P`
|
||||
- **Местоположение**: Вкладка "Мои партнеры"
|
||||
- **Цель**: Прямое деловое сотрудничество
|
||||
- **Результат**: +100 сфер ⚡ + автоматическое добавление в партнеры
|
||||
- **Ссылка**: Уже готова при создании организации, быстрое копирование
|
||||
|
||||
---
|
||||
|
||||
## 2. СТРУКТУРА ДАННЫХ
|
||||
|
||||
### 2.1 Расширение модели Organization (Prisma)
|
||||
|
||||
```prisma
|
||||
model Organization {
|
||||
// Существующие поля...
|
||||
|
||||
// Реферальная система
|
||||
referralCode String? @unique @default(cuid()) // Уникальный код для реферальной ссылки
|
||||
referredById String? // ID организации-реферера
|
||||
referredBy Organization? @relation("ReferralRelation", fields: [referredById], references: [id])
|
||||
referrals Organization[] @relation("ReferralRelation")
|
||||
referralPoints Int @default(0) // Общее количество баллов
|
||||
|
||||
@@index([referralCode])
|
||||
@@index([referredById])
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Новая модель ReferralTransaction
|
||||
|
||||
```prisma
|
||||
model ReferralTransaction {
|
||||
id String @id @default(cuid())
|
||||
referrerId String // Кто получил баллы
|
||||
referralId String // За кого получил баллы
|
||||
points Int // Количество баллов
|
||||
type ReferralTransactionType // Тип транзакции
|
||||
description String? // Описание транзакции
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
referrer Organization @relation("ReferrerTransactions", fields: [referrerId], references: [id])
|
||||
referral Organization @relation("ReferralTransactions", fields: [referralId], references: [id])
|
||||
|
||||
@@index([referrerId, createdAt])
|
||||
@@index([referralId])
|
||||
}
|
||||
|
||||
enum ReferralTransactionType {
|
||||
REGISTRATION // За регистрацию по реферальной ссылке
|
||||
AUTO_PARTNERSHIP // За автоматическое партнерство через бизнес-сделку
|
||||
FIRST_ORDER // За первый заказ (будущее расширение)
|
||||
MONTHLY_BONUS // Ежемесячный бонус (будущее расширение)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. ГЕНЕРАЦИЯ РЕФЕРАЛЬНЫХ ССЫЛОК
|
||||
|
||||
### 3.1 Формат реферальной ссылки
|
||||
|
||||
```
|
||||
https://app.sfera.ru/register?ref={referralCode}
|
||||
```
|
||||
|
||||
### 3.2 Алгоритм генерации кода
|
||||
|
||||
- **Длина**: 10 символов
|
||||
- **Символы**: A-Z, 0-9 (исключая похожие: O/0, I/1)
|
||||
- **Пример**: `SF2X9K4M7P`
|
||||
|
||||
### 3.3 Правила генерации
|
||||
|
||||
- Генерируется автоматически при создании организации
|
||||
- Проверка уникальности перед сохранением
|
||||
- Невозможно изменить после создания
|
||||
- Хранится в поле `referralCode` модели Organization
|
||||
|
||||
---
|
||||
|
||||
## 4. UI/UX КОМПОНЕНТЫ
|
||||
|
||||
### 4.1 Вкладка "Рефералы" в разделе Партнеры
|
||||
|
||||
#### Расположение
|
||||
- Раздел: `/partners`
|
||||
- Новая вкладка: 6-я позиция после "Поставщик"
|
||||
- Название: "Рефералы"
|
||||
- Иконка: `Gift` или `Users2` из lucide-react
|
||||
|
||||
#### Структура страницы
|
||||
|
||||
```tsx
|
||||
<div className="referrals-page">
|
||||
{/* Блок с реферальной ссылкой */}
|
||||
<div className="referral-link-section glass-card p-6 mb-6">
|
||||
<h3>Ваша реферальная ссылка</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="hidden-link">••••••••••</div>
|
||||
<Button onClick={copyLink}>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Копировать ссылку
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-white/60 mt-2">
|
||||
Поделитесь ссылкой с партнерами и получайте баллы за каждую регистрацию
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Статистика */}
|
||||
<div className="stats-grid grid grid-cols-4 gap-4 mb-6">
|
||||
<StatsCard title="Всего партнеров" value={totalReferrals} icon="Users" />
|
||||
<StatsCard title="Сфер заработано" value={totalPoints} icon="Zap" suffix="⚡" />
|
||||
<StatsCard title="Партнеров за месяц" value={monthlyReferrals} icon="TrendingUp" />
|
||||
<StatsCard title="Сфер за месяц" value={monthlyPoints} icon="Zap" suffix="⚡" />
|
||||
</div>
|
||||
|
||||
{/* Фильтры */}
|
||||
<div className="filters glass-card p-4 mb-6">
|
||||
<DateRangePicker />
|
||||
<TypeFilter types={['SELLER', 'WHOLESALE', 'FULFILLMENT', 'LOGIST']} />
|
||||
<SearchInput placeholder="Поиск по названию или ИНН" />
|
||||
</div>
|
||||
|
||||
{/* Таблица рефералов */}
|
||||
<div className="referrals-table glass-card">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Дата регистрации</TableHead>
|
||||
<TableHead>Название организации</TableHead>
|
||||
<TableHead>ИНН</TableHead>
|
||||
<TableHead>Тип кабинета</TableHead>
|
||||
<TableHead>Источник</TableHead>
|
||||
<TableHead>Начислено сфер ⚡</TableHead>
|
||||
<TableHead>Статус</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{referrals.map(referral => (
|
||||
<TableRow key={referral.id}>
|
||||
<TableCell>{formatDate(referral.createdAt)}</TableCell>
|
||||
<TableCell>{referral.name || referral.fullName}</TableCell>
|
||||
<TableCell>{referral.inn}</TableCell>
|
||||
<TableCell>
|
||||
<Badge className={getTypeBadgeStyles(referral.type)}>
|
||||
{getTypeLabel(referral.type)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
{referral.source === 'REFERRAL' ? (
|
||||
<>
|
||||
<UserPlus className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-blue-300">Реферальная ссылка</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ShoppingCart className="h-4 w-4 text-orange-400" />
|
||||
<span className="text-orange-300">Бизнес-сделка</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-green-400">+{referral.points}</span>
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="success">Активен</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4.2 Визуальные элементы
|
||||
|
||||
#### Скрытие реферальной ссылки
|
||||
- Ссылка НЕ отображается в явном виде
|
||||
- Показываются точки или звездочки: `••••••••••`
|
||||
- Только кнопка "Копировать ссылку"
|
||||
|
||||
#### Уведомления
|
||||
- При копировании: "Реферальная ссылка скопирована"
|
||||
- При новом реферале: push-уведомление
|
||||
- При начислении баллов: анимация изменения баланса
|
||||
|
||||
---
|
||||
|
||||
## 5. СИСТЕМА НАЧИСЛЕНИЯ БАЛЛОВ
|
||||
|
||||
### 5.1 Базовые ставки
|
||||
|
||||
| Тип регистрации | Количество баллов |
|
||||
|-----------------|-------------------|
|
||||
| SELLER | 100 сфер ⚡ |
|
||||
| WHOLESALE | 100 сфер ⚡ |
|
||||
| FULFILLMENT | 100 сфер ⚡ |
|
||||
| LOGIST | 100 сфер ⚡ |
|
||||
|
||||
> 💡 **ИКОНКА СФЕРЫ**: ⚡ (молния) - символизирует энергию, скорость и силу партнерства
|
||||
|
||||
### 5.2 Правила начисления
|
||||
|
||||
- **МОМЕНТ НАЧИСЛЕНИЯ**: Сразу после успешной регистрации реферала
|
||||
- **УСЛОВИЕ**: Реферал должен пройти полную регистрацию (подтвердить ИНН)
|
||||
- **ОГРАНИЧЕНИЯ**: Один ИНН = одно начисление (защита от дублей)
|
||||
- **ВИДИМОСТЬ**: Баллы сразу отображаются в таблице и общем счетчике
|
||||
|
||||
### 5.3 Будущие расширения
|
||||
|
||||
- Бонусы за первый заказ реферала
|
||||
- Ежемесячные начисления за активных рефералов
|
||||
- Многоуровневая система (рефералы рефералов)
|
||||
|
||||
---
|
||||
|
||||
## 6. GRAPHQL API
|
||||
|
||||
### 6.1 Новые Queries
|
||||
|
||||
```graphql
|
||||
type Query {
|
||||
# Получить мою реферальную ссылку
|
||||
myReferralLink: String!
|
||||
|
||||
# Список моих рефералов
|
||||
myReferrals(
|
||||
dateFrom: DateTime
|
||||
dateTo: DateTime
|
||||
type: OrganizationType
|
||||
search: String
|
||||
limit: Int
|
||||
offset: Int
|
||||
): ReferralsResponse!
|
||||
|
||||
# Статистика по рефералам
|
||||
myReferralStats: ReferralStats!
|
||||
|
||||
# История транзакций баллов
|
||||
myReferralTransactions(
|
||||
limit: Int
|
||||
offset: Int
|
||||
): ReferralTransactionsResponse!
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Новые Types
|
||||
|
||||
```graphql
|
||||
type ReferralsResponse {
|
||||
referrals: [Referral!]!
|
||||
totalCount: Int!
|
||||
totalPages: Int!
|
||||
}
|
||||
|
||||
type Referral {
|
||||
id: ID!
|
||||
organization: Organization!
|
||||
registeredAt: DateTime!
|
||||
pointsEarned: Int!
|
||||
status: ReferralStatus!
|
||||
transactions: [ReferralTransaction!]!
|
||||
}
|
||||
|
||||
type ReferralStats {
|
||||
totalReferrals: Int!
|
||||
totalPoints: Int!
|
||||
monthlyReferrals: Int!
|
||||
monthlyPoints: Int!
|
||||
referralsByType: [ReferralTypeStats!]!
|
||||
}
|
||||
|
||||
type ReferralTypeStats {
|
||||
type: OrganizationType!
|
||||
count: Int!
|
||||
points: Int!
|
||||
}
|
||||
|
||||
type ReferralTransaction {
|
||||
id: ID!
|
||||
points: Int!
|
||||
type: ReferralTransactionType!
|
||||
description: String
|
||||
createdAt: DateTime!
|
||||
referral: Organization!
|
||||
}
|
||||
|
||||
enum ReferralStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
BLOCKED
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Расширение существующих типов
|
||||
|
||||
```graphql
|
||||
extend type Organization {
|
||||
referralCode: String
|
||||
referredBy: Organization
|
||||
referrals: [Organization!]!
|
||||
referralPoints: Int!
|
||||
isMyReferral: Boolean! # Computed field
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. ПРОЦЕСС РЕГИСТРАЦИИ ПО ССЫЛКАМ
|
||||
|
||||
### 7.1 Реферальная ссылка (маркетинг)
|
||||
|
||||
#### Пошаговый процесс:
|
||||
1. **Переход по ссылке**
|
||||
- Пользователь переходит по ссылке: `https://app.sfera.ru/register?ref=SF2X9K4M7P`
|
||||
- Система сохраняет реферальный код в sessionStorage
|
||||
|
||||
2. **Регистрация**
|
||||
- Стандартный процесс регистрации
|
||||
- Реферальный код передается в mutation `registerOrganization`
|
||||
|
||||
3. **Валидация**
|
||||
- Проверка существования реферального кода
|
||||
- Проверка, что организация не регистрировалась ранее
|
||||
|
||||
4. **Создание связи**
|
||||
- Установка `referredById` для новой организации
|
||||
- Создание записи в `ReferralTransaction`
|
||||
- **ВАЖНО**: Автоматическое партнерство НЕ создается
|
||||
|
||||
5. **Начисление баллов**
|
||||
- Автоматическое начисление 100 сфер ⚡ рефереру
|
||||
- Отправка уведомления рефереру
|
||||
|
||||
### 7.2 Партнерская ссылка (бизнес)
|
||||
|
||||
#### Пошаговый процесс:
|
||||
1. **Переход по ссылке**
|
||||
- Пользователь переходит по ссылке: `https://app.sfera.ru/register?partner=SF2X9K4M7P`
|
||||
- Система сохраняет партнерский код в sessionStorage
|
||||
|
||||
2. **Регистрация**
|
||||
- Стандартный процесс регистрации
|
||||
- Партнерский код передается в mutation `registerOrganization`
|
||||
|
||||
3. **Валидация**
|
||||
- Проверка существования партнерского кода
|
||||
- Проверка, что организация не регистрировалась ранее
|
||||
|
||||
4. **Создание связи**
|
||||
- Установка `referredById` для новой организации
|
||||
- Создание записи в `ReferralTransaction`
|
||||
- **ВАЖНО**: Автоматическое создание партнерства (`Counterparty`)
|
||||
|
||||
5. **Начисление баллов**
|
||||
- Автоматическое начисление 100 сфер ⚡ партнеру
|
||||
- Отправка уведомления партнеру
|
||||
- Добавление в список "Мои партнеры"
|
||||
|
||||
### 7.3 Логика различения
|
||||
|
||||
```javascript
|
||||
// В процессе регистрации
|
||||
if (query.ref) {
|
||||
// Реферальная ссылка - только сферы
|
||||
await addReferralTransaction(referrerId, newUserId, 100, 'REFERRAL_LINK')
|
||||
} else if (query.partner) {
|
||||
// Партнерская ссылка - сферы + автоматическое партнерство
|
||||
await addReferralTransaction(partnerId, newUserId, 100, 'REFERRAL_LINK')
|
||||
await createPartnership(partnerId, newUserId, 'REFERRAL')
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 Обработка ошибок
|
||||
|
||||
- **Невалидный код**: Регистрация продолжается без реферала/партнерства
|
||||
- **Самореферал**: Блокировка (нельзя регистрироваться по своей ссылке)
|
||||
- **Повторная регистрация**: Игнорирование кода
|
||||
|
||||
---
|
||||
|
||||
## 8. АВТОМАТИЧЕСКОЕ ПАРТНЕРСТВО ЧЕРЕЗ БИЗНЕС-СДЕЛКИ
|
||||
|
||||
### 8.1 Принцип работы
|
||||
|
||||
Кроме реферальных ссылок, партнерские отношения создаются автоматически через коммерческие взаимодействия в системе.
|
||||
|
||||
### 8.2 Триггер автопартнерства
|
||||
|
||||
**МОМЕНТ СОЗДАНИЯ ПАРТНЕРСТВА**: Когда поставщик одобряет заявку на поставку (`supplierApproveOrder` mutation)
|
||||
|
||||
**УСЛОВИЕ**: Если между организациями еще нет партнерских отношений (`Counterparty` связи)
|
||||
|
||||
**ДЕЙСТВИЕ**: Автоматическое создание взаимного партнерства
|
||||
|
||||
### 8.3 Алгоритм автопартнерства
|
||||
|
||||
```typescript
|
||||
// В GraphQL резолвере supplierApproveOrder
|
||||
async supplierApproveOrder(supplyOrderId: string) {
|
||||
// 1. Одобрить заявку
|
||||
const supplyOrder = await updateSupplyOrderStatus(supplyOrderId, 'SUPPLIER_APPROVED')
|
||||
|
||||
// 2. Проверить существование партнерства
|
||||
const existingCounterparty = await checkCounterpartyExists(
|
||||
supplyOrder.organizationId, // Покупатель
|
||||
supplyOrder.partnerId // Поставщик
|
||||
)
|
||||
|
||||
// 3. Создать автопартнерство если его нет
|
||||
if (!existingCounterparty) {
|
||||
await createAutoCounterparty({
|
||||
organizationId: supplyOrder.organizationId,
|
||||
counterpartyId: supplyOrder.partnerId,
|
||||
type: 'AUTO_BUSINESS',
|
||||
triggeredBy: 'SUPPLY_ORDER_APPROVAL',
|
||||
supplyOrderId: supplyOrderId
|
||||
})
|
||||
}
|
||||
|
||||
return supplyOrder
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 Расширение модели данных
|
||||
|
||||
```prisma
|
||||
model Counterparty {
|
||||
// Существующие поля...
|
||||
|
||||
// Новые поля для автопартнерства
|
||||
type CounterpartyType @default(MANUAL)
|
||||
triggeredBy String? // 'SUPPLY_ORDER_APPROVAL', 'REFERRAL_LINK'
|
||||
triggerEntityId String? // ID заявки, реферала и т.д.
|
||||
|
||||
@@index([type])
|
||||
}
|
||||
|
||||
enum CounterpartyType {
|
||||
MANUAL // Создано вручную через UI
|
||||
REFERRAL // Создано через реферальную ссылку
|
||||
AUTO_BUSINESS // Создано автоматически через бизнес-сделку
|
||||
}
|
||||
```
|
||||
|
||||
### 8.5 Начисление сфер за автопартнерство
|
||||
|
||||
**ДЛЯ ПОСТАВЩИКА** (кто одобрил заявку):
|
||||
- +100 сфер ⚡ за каждого нового партнера через бизнес-сделку
|
||||
- Равно рефералу, так как это также ценное партнерство
|
||||
|
||||
**ДЛЯ ПОКУПАТЕЛЯ**:
|
||||
- Партнерство создается, но сферы не начисляются
|
||||
- Выгода в том, что появляется прямая связь с поставщиком
|
||||
|
||||
### 8.6 Отображение в UI
|
||||
|
||||
В таблице рефералов добавить колонку "Источник":
|
||||
|
||||
| Источник | Описание | Иконка |
|
||||
|----------|----------|---------|
|
||||
| Реферальная ссылка | Зарегистрировались по вашей ссылке | `UserPlus` |
|
||||
| Бизнес-сделка | Стали партнерами через заказ | `ShoppingCart` |
|
||||
|
||||
### 8.7 Логирование автопартнерства
|
||||
|
||||
```typescript
|
||||
// Лог события создания автопартнерства
|
||||
{
|
||||
event: 'auto_counterparty_created',
|
||||
supplierId: '...',
|
||||
buyerId: '...',
|
||||
supplyOrderId: '...',
|
||||
spheresEarned: 50,
|
||||
timestamp: '2025-01-10T15:30:00Z'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. БЕЗОПАСНОСТЬ И ОГРАНИЧЕНИЯ
|
||||
|
||||
### 9.1 Защита от злоупотреблений
|
||||
|
||||
- **Один ИНН - одна регистрация**: Проверка уникальности ИНН
|
||||
- **Блокировка самореферала**: Проверка IP и device fingerprint
|
||||
- **Лимиты**: Максимум 100 регистраций в день с одного реферального кода
|
||||
- **Валидация ИНН**: Обязательная проверка через DaData API
|
||||
|
||||
### 9.2 Права доступа
|
||||
|
||||
- **Просмотр своих рефералов**: Только владелец реферального кода
|
||||
- **Изменение кода**: Запрещено
|
||||
- **Просмотр чужих рефералов**: Запрещено
|
||||
- **Администратор**: Полный доступ для модерации
|
||||
|
||||
### 9.3 Логирование
|
||||
|
||||
Все действия с реферальной системой логируются:
|
||||
- Генерация ссылок
|
||||
- Переходы по ссылкам
|
||||
- Регистрации
|
||||
- Начисления баллов
|
||||
- Попытки злоупотреблений
|
||||
|
||||
---
|
||||
|
||||
## 🎨 ВИЗУАЛЬНЫЙ ДИЗАЙН
|
||||
|
||||
### Цветовая схема для сфер ⚡
|
||||
- **Положительные начисления**: `text-green-400`
|
||||
- **Иконка молнии**: `text-yellow-400` (золотистый)
|
||||
- **Фон для сфер**: `bg-yellow-500/20`
|
||||
- **Анимация при изменении**: `animate-pulse` на 2 секунды
|
||||
|
||||
### Иконки источников партнерства
|
||||
- **Реферальная ссылка**: `UserPlus` в `text-blue-400`
|
||||
- **Бизнес-сделка**: `ShoppingCart` в `text-orange-400`
|
||||
- **Сферы**: `Zap` в `text-yellow-400`
|
||||
|
||||
### Стили для типов кабинетов (соответствуют существующим)
|
||||
- **SELLER**: `bg-green-500/20 text-green-300`
|
||||
- **WHOLESALE**: `bg-purple-500/20 text-purple-300`
|
||||
- **FULFILLMENT**: `bg-blue-500/20 text-blue-300`
|
||||
- **LOGIST**: `bg-orange-500/20 text-orange-300`
|
||||
|
||||
---
|
||||
|
||||
## 📊 МЕТРИКИ УСПЕХА
|
||||
|
||||
- **Конверсия**: % регистраций по реферальным ссылкам
|
||||
- **Активность**: Среднее количество рефералов на одного партнера
|
||||
- **Retention**: % активных рефералов через 30 дней
|
||||
- **Виральность**: Количество рефералов, которые сами привели рефералов
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ПЛАН ВНЕДРЕНИЯ
|
||||
|
||||
### Фаза 1 (MVP)
|
||||
- Базовая генерация ссылок
|
||||
- Регистрация по реферальным ссылкам
|
||||
- Начисление баллов за регистрацию
|
||||
- UI для просмотра рефералов
|
||||
|
||||
### Фаза 2
|
||||
- Бонусы за первый заказ
|
||||
- Email уведомления о новых рефералах
|
||||
- Экспорт данных о рефералах
|
||||
|
||||
### Фаза 3
|
||||
- Многоуровневая система
|
||||
- Использование баллов для оплаты услуг
|
||||
- Gamification элементы (достижения, уровни)
|
||||
|
||||
---
|
||||
|
||||
**Дата создания**: 2025-01-10
|
||||
**Автор**: Claude AI Assistant
|
||||
**Статус**: Готово к реализации
|
178
registration-authorization-rules.md
Normal file
178
registration-authorization-rules.md
Normal file
@ -0,0 +1,178 @@
|
||||
# ПРАВИЛА РЕГИСТРАЦИИ И АВТОРИЗАЦИИ
|
||||
|
||||
## 🏗️ АРХИТЕКТУРА СИСТЕМЫ
|
||||
|
||||
### Основные сущности:
|
||||
- **ПОЛЬЗОВАТЕЛЬ** - верхний уровень, может иметь много номеров телефонов
|
||||
- **НОМЕР ТЕЛЕФОНА** - привязан к пользователю, может иметь много организаций
|
||||
- **ОРГАНИЗАЦИЯ/КАБИНЕТ** - бизнес-сущность, привязана к номеру телефона
|
||||
- **ПАРТНЕРСТВО** - связь между организациями (не пользователями или номерами)
|
||||
|
||||
### Связи:
|
||||
```
|
||||
ПОЛЬЗОВАТЕЛЬ (1) ←→ (N) НОМЕРА ТЕЛЕФОНОВ (1) ←→ (N) ОРГАНИЗАЦИИ
|
||||
ОРГАНИЗАЦИЯ (N) ←→ (N) ОРГАНИЗАЦИИ (партнерство)
|
||||
```
|
||||
|
||||
### Пример структуры:
|
||||
```
|
||||
ПОЛЬЗОВАТЕЛЬ "Иван Петров"
|
||||
├── +7111111111 (номер 1)
|
||||
│ ├── СБЕРБАНК (организация 1)
|
||||
│ └── АВИТО (организация 2)
|
||||
└── +7222222222 (номер 2)
|
||||
└── ЯНДЕКС (организация 3)
|
||||
```
|
||||
|
||||
## 📋 СПОСОБЫ СОЗДАНИЯ ОРГАНИЗАЦИЙ
|
||||
|
||||
### 1. СТАНДАРТНАЯ РЕГИСТРАЦИЯ
|
||||
**URL:** `/register` (без параметров)
|
||||
|
||||
**Флоу для НОВОГО номера телефона:**
|
||||
1. Ввод телефона → SMS код → авторизация
|
||||
2. Выбор типа кабинета
|
||||
3. Ввод данных организации (ИНН или API ключи)
|
||||
4. Создание организации
|
||||
5. Привязка организации к номеру телефона
|
||||
|
||||
**Флоу для АВТОРИЗОВАННОГО номера:**
|
||||
1. Редирект в дашборд (стандартное поведение)
|
||||
|
||||
### 2. РЕГИСТРАЦИЯ ЧЕРЕЗ САЙДБАР (ПОТОМ)
|
||||
**Местоположение:** Блок организации в сайдбаре
|
||||
|
||||
**Флоу:**
|
||||
1. Модалка создания новой организации
|
||||
2. Выбор типа кабинета
|
||||
3. Ввод данных организации
|
||||
4. Создание организации без партнерства
|
||||
|
||||
### 3. ПАРТНЕРСКАЯ ССЫЛКА
|
||||
**URL:** `/register?partner=CODE`
|
||||
|
||||
**Флоу для НОВОГО номера телефона:**
|
||||
1. Ввод телефона → SMS код → авторизация
|
||||
2. Выбор типа кабинета
|
||||
3. Ввод данных организации
|
||||
4. Создание организации С ПАРТНЕРСТВОМ
|
||||
5. Автоматическое создание партнерских связей между организациями
|
||||
6. Начисление +100 сфер организации-рефереру
|
||||
|
||||
**Флоу для АВТОРИЗОВАННОГО номера телефона:**
|
||||
1. Пропуск авторизации (уже авторизован на этом номере)
|
||||
2. Переход сразу к выбору типа кабинета
|
||||
3. Ввод данных новой организации
|
||||
4. Создание организации С ПАРТНЕРСТВОМ
|
||||
5. Автоматическое создание партнерских связей
|
||||
6. Начисление +100 сфер организации-рефереру
|
||||
|
||||
### 4. РЕФЕРАЛЬНАЯ ССЫЛКА
|
||||
**URL:** `/register?ref=CODE`
|
||||
|
||||
**Флоу для НОВОГО номера телефона:**
|
||||
1. Ввод телефона → SMS код → авторизация
|
||||
2. Выбор типа кабинета
|
||||
3. Ввод данных организации
|
||||
4. Создание организации С РЕФЕРАЛЬНОЙ СВЯЗЬЮ
|
||||
5. Начисление +100 сфер организации-рефереру
|
||||
6. БЕЗ автоматического партнерства
|
||||
|
||||
**Флоу для АВТОРИЗОВАННОГО номера телефона:**
|
||||
1. Пропуск авторизации (уже авторизован на этом номере)
|
||||
2. Переход сразу к выбору типа кабинета
|
||||
3. Ввод данных новой организации
|
||||
4. Создание организации С РЕФЕРАЛЬНОЙ СВЯЗЬЮ
|
||||
5. Начисление +100 сфер организации-рефереру
|
||||
6. БЕЗ автоматического партнерства
|
||||
|
||||
## 📊 ДАШБОРД РЕФЕРАЛОВ (РЕАЛИЗОВАНО)
|
||||
|
||||
### Местоположение: `/partners` → вкладка "Рефералы"
|
||||
|
||||
### Функции:
|
||||
1. **Мои ссылки:**
|
||||
- Реферальная ссылка: `register?ref=CODE`
|
||||
- Партнерская ссылка: `register?partner=CODE`
|
||||
- Кнопка копирования
|
||||
|
||||
2. **Статистика:**
|
||||
- Всего партнеров и начисленных сфер
|
||||
- Статистика за текущий месяц
|
||||
|
||||
3. **Список рефералов:**
|
||||
- Привлеченные организации
|
||||
- Фильтрация и поиск
|
||||
- Источник регистрации
|
||||
|
||||
## 🔧 ТЕХНИЧЕСКИЕ ПРАВИЛА
|
||||
|
||||
### Валидация URL параметров:
|
||||
- Нельзя использовать `partner` и `ref` одновременно
|
||||
- Коды должны быть длиной 10 символов
|
||||
- Коды должны существовать в системе
|
||||
- Коды должны соответствовать формату `[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{10}`
|
||||
|
||||
### Поведение AuthGuard:
|
||||
- **Без кодов:** если авторизован → дашборд, иначе → форма авторизации
|
||||
- **С кодами:** всегда показывать AuthFlow (даже для авторизованных)
|
||||
|
||||
### Поведение AuthFlow:
|
||||
- **Новый номер телефона:** полный флоу (телефон → SMS → создание организации)
|
||||
- **Авторизованный номер:** пропустить телефон/SMS, сразу к созданию организации
|
||||
|
||||
### Создание партнерства:
|
||||
- Создаются записи в таблице `Counterparty` в ОБЕ стороны
|
||||
- Создается `ReferralTransaction` с начислением сфер
|
||||
- Устанавливается `referredById` у новой организации
|
||||
|
||||
### Создание реферальной связи:
|
||||
- Создается `ReferralTransaction` с начислением сфер
|
||||
- Устанавливается `referredById` у новой организации
|
||||
- БЕЗ создания `Counterparty` записей
|
||||
|
||||
## ⚠️ ВАЖНЫЕ ОГРАНИЧЕНИЯ
|
||||
|
||||
### Архитектурные ограничения:
|
||||
- **ОДИН ПОЛЬЗОВАТЕЛЬ** может иметь **МНОГО номеров телефонов**
|
||||
- **ОДИН НОМЕР ТЕЛЕФОНА** привязан к **ОДНОМУ ПОЛЬЗОВАТЕЛЮ**
|
||||
- **ОДИН НОМЕР ТЕЛЕФОНА** может иметь **МНОГО ОРГАНИЗАЦИЙ**
|
||||
- Нельзя зарегистрировать один номер телефона дважды
|
||||
|
||||
### Уникальность организаций:
|
||||
- Одна организация (по ИНН) может быть зарегистрирована только один раз
|
||||
- API ключи селлеров должны быть уникальными
|
||||
|
||||
### Сохранение кодов:
|
||||
- Партнерские/реферальные коды должны передаваться через все компоненты
|
||||
- Коды не должны теряться при авторизации номера телефона
|
||||
- Коды применяются при создании ОРГАНИЗАЦИИ, не пользователя или номера
|
||||
|
||||
## 🧪 ТЕСТОВЫЕ СЦЕНАРИИ
|
||||
|
||||
### Сценарий 1: Новый номер + партнерская ссылка
|
||||
1. Переход по `register?partner=A2LA72BZZX`
|
||||
2. Ввод нового телефона → SMS → авторизация
|
||||
3. Создание новой организации
|
||||
4. Проверка: создались партнерские связи, начислились сферы
|
||||
|
||||
### Сценарий 2: Авторизованный номер + партнерская ссылка
|
||||
1. Уже авторизован на номере в системе
|
||||
2. Переход по `register?partner=A2LA72BZZX`
|
||||
3. Пропуск авторизации → сразу создание организации
|
||||
4. Проверка: создались партнерские связи, начислились сферы
|
||||
|
||||
### Сценарий 3: Реферальная ссылка
|
||||
1. Переход по `register?ref=A2LA72BZZX`
|
||||
2. Авторизация (если нужно)
|
||||
3. Создание организации
|
||||
4. Проверка: начислились сферы, НЕТ партнерских связей
|
||||
|
||||
## ❓ ВОПРОСЫ ДЛЯ СОГЛАСОВАНИЯ
|
||||
|
||||
1. **Архитектура БД:** Нужна ли новая таблица для "мастер-пользователей" или изменить текущую схему?
|
||||
2. **Связь пользователь-телефон:** Как будет происходить связывание номеров телефонов с одним пользователем?
|
||||
3. **UI для авторизованных:** Показывать ли информацию "Вы создаете организацию для номера +7XXX"?
|
||||
4. **Переключение организаций:** Как пользователь выбирает активную организацию из разных номеров?
|
||||
5. **Лимиты:** Есть ли ограничения на количество номеров у пользователя или организаций у номера?
|
||||
6. **Идентификация:** Как система определяет, что два номера принадлежат одному пользователю?
|
@ -1,3 +1,15 @@
|
||||
# 🔒 РЕЗЕРВНАЯ КОПИЯ - НЕ РЕДАКТИРОВАТЬ БЕЗ РАЗРЕШЕНИЯ!
|
||||
|
||||
> 🚨 **КРИТИЧЕСКИ ВАЖНО**: Это РЕЗЕРВНАЯ КОПИЯ файла rules-complete.md
|
||||
>
|
||||
> ❌ **ЗАПРЕЩЕНО РЕДАКТИРОВАТЬ БЕЗ ЯВНОГО РАЗРЕШЕНИЯ ПОЛЬЗОВАТЕЛЯ!**
|
||||
>
|
||||
> 📅 **Дата создания резерва**: 2025-08-08
|
||||
> 🔐 **Статус**: ЗАЩИЩЕН ОТ ИЗМЕНЕНИЙ
|
||||
> 📄 **Оригинальный файл**: rules-complete.md
|
||||
|
||||
---
|
||||
|
||||
# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v10.0
|
||||
|
||||
> ⚠️ **АБСОЛЮТНО ПОЛНЫЙ ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ**: Данный файл объединяет АБСОЛЮТНО ВСЕ правила системы: протоколы работы Claude Code, детальные протоколы по сложности, систему предотвращения нарушений, расширенную самопроверку, специальный UI/UX протокол и бизнес-правила. Визуальные правила вынесены в отдельный файл visual-design-rules.md с автоматической интеграцией.
|
||||
@ -113,7 +125,7 @@
|
||||
| ----------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- |
|
||||
| **Типы предметов** | [2](#2--типизация-предметов) | PRODUCT, CONSUMABLE, DEFECT, FINISHED_PRODUCT |
|
||||
| **Кабинет фулфилмента** | [11](#11--кабинет-фулфилмента-полная-документация) | Склад, Услуги, Сотрудники, 6 модулей |
|
||||
| **Workflow поставок** | [5](#5--workflow-поставок) | 8 статусов, уведомления, роль логистики |
|
||||
| **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 |
|
||||
@ -227,6 +239,8 @@ SupplyOrder представляет собой единый документ,
|
||||
|
||||
## 📑 ОГЛАВЛЕНИЕ
|
||||
|
||||
> 📖 **Каталог процессов**: См. [workflow-catalog.md](./workflow-catalog.md) для полного каталога всех бизнес-процессов системы
|
||||
|
||||
> 📋 **ЧТО ОБЪЕДИНЕНО**:
|
||||
>
|
||||
> - rules-unified.md (v3.0) - общая база знаний системы
|
||||
@ -508,45 +522,7 @@ if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
|
||||
> 📌 **ВИЗУАЛЬНЫЕ ПРАВИЛА**: См. [visual-design-rules.md - Статусы поставок](#142-статусы-поставок---расширенная-цветовая-система)
|
||||
|
||||
### 5.1 Детализированная система статусов
|
||||
|
||||
**Статусы SupplyOrder (Заказ поставки):**
|
||||
|
||||
1. **PENDING** - Ожидает подтверждения поставщиком
|
||||
2. **SUPPLIER_APPROVED** - Одобрено поставщиком
|
||||
3. **CONFIRMED** - Подтвержден (готов к обработке)
|
||||
4. **LOGISTICS_CONFIRMED** - Подтверждено логистикой
|
||||
5. **SHIPPED** - Отгружено поставщиком
|
||||
6. **IN_TRANSIT** - В пути (логистика доставляет)
|
||||
7. **DELIVERED** - Доставлен на фулфилмент
|
||||
8. **CANCELLED** - Отменен
|
||||
|
||||
### 5.2 Пошаговый процесс поставки
|
||||
|
||||
**ЭТАП 1: Создание заказа**
|
||||
|
||||
1. Селлер заказывает товар/расходники у поставщика
|
||||
2. Система создает SupplyOrder со статусом `PENDING`
|
||||
3. Автоматическое уведомление поставщику
|
||||
|
||||
**ЭТАП 2: Обработка поставщиком** 4. Поставщик получает оповещение 5. Поставщик нажимает "Одобрить" 6. Статус меняется на `SUPPLIER_APPROVED`
|
||||
|
||||
**ЭТАП 3: Передача в фулфилмент** 7. Поставка отображается в кабинете фулфилмента 8. Фулфилмент выбирает ответственного и логистику 9. Статус меняется на `CONFIRMED`
|
||||
|
||||
**ЭТАП 4: Логистическое подтверждение** 10. Логистика подтверждает доставку 11. Статус меняется на `LOGISTICS_CONFIRMED`
|
||||
|
||||
**ЭТАП 5: Отгрузка** 12. Поставщик отгружает товар 13. Статус меняется на `SHIPPED`, затем `IN_TRANSIT`
|
||||
|
||||
**ЭТАП 6: Доставка и приемка** 14. Логистика доставляет на фулфилмент 15. Фулфилмент принимает товар 16. Статус меняется на `DELIVERED`
|
||||
|
||||
### 5.3 Система уведомлений
|
||||
|
||||
**Обязательные уведомления:**
|
||||
|
||||
- Поставщику: о новом заказе
|
||||
- Фулфилменту: о подтвержденной поставке
|
||||
- Логистике: о назначении на заявку
|
||||
- Селлеру: об изменении каждого статуса
|
||||
См. детальное описание в [workflow-catalog.md#1-workflow-поставок](./workflow-catalog.md#1-workflow-поставок)
|
||||
|
||||
---
|
||||
|
||||
@ -1974,351 +1950,114 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
|
||||
|
||||
## 11. 🏭 КАБИНЕТ ФУЛФИЛМЕНТА
|
||||
|
||||
### 11.1 Общие характеристики кабинета фулфилмента
|
||||
> 📖 **Технические детали кабинета**: См. [fulfillment-cabinet-rules.md](./fulfillment-cabinet-rules.md) для компонентов, GraphQL, UI/UX и всех технических деталей реализации
|
||||
|
||||
#### 11.1.1 Принципы доступа
|
||||
### 11.1 Основные функции фулфилмента
|
||||
|
||||
- **МАКСИМАЛЬНЫЕ ПРАВА**: Фулфилмент имеет доступ ко ВСЕМ разделам системы
|
||||
- **АДАПТИВНАЯ НАВИГАЦИЯ**: Sidebar изменяется в зависимости от `user.organization.type === "FULFILLMENT"`
|
||||
- **ЭКСКЛЮЗИВНЫЕ КОМПОНЕНТЫ**: Услуги, Сотрудники, Статистика фулфилмента доступны ТОЛЬКО фулфилменту
|
||||
- **СПЕЦИАЛЬНЫЙ РОУТИНГ**: При нажатии "Поставки" → `/fulfillment-supplies` (не `/supplies`)
|
||||
**РОЛЬ В СИСТЕМЕ**: Обработка товаров и предоставление услуг
|
||||
|
||||
#### 11.1.2 Архитектурные особенности
|
||||
**ОСНОВНЫЕ ФУНКЦИИ**:
|
||||
|
||||
- **GraphQL проверки**: `skip: user?.organization?.type !== 'FULFILLMENT'` в запросах
|
||||
- **Условное отображение**: `{user?.organization?.type === "FULFILLMENT" && (...)}`
|
||||
- **Адаптивные отступы**: `getSidebarMargin()` для responsive design
|
||||
- **Полинг данных**: Статистика обновляется каждую минуту (`pollInterval: 60000`)
|
||||
- **ПРИЕМКА ТОВАРОВ**: Получение и обработка поставок от поставщиков
|
||||
- **СОЗДАНИЕ ПРОДУКТОВ**: Превращение товаров в готовые продукты по рецептурам
|
||||
- **ПРЕДОСТАВЛЕНИЕ УСЛУГ**: Услуги обработки для селлеров
|
||||
- **УПРАВЛЕНИЕ РАСХОДНИКАМИ**: Установка цен и контроль наличия
|
||||
- **ЛОГИСТИЧЕСКИЕ УСЛУГИ**: Маршруты доставки и тарификация
|
||||
|
||||
### 11.2 Структура раздела склад фулфилмента
|
||||
### 11.2 Workflow фулфилмента
|
||||
|
||||
> 📌 **ВИЗУАЛЬНЫЕ ПРАВИЛА**: См. [visual-design-rules.md - Кабинет фулфилмента](#141-кабинет-фулфилмента)
|
||||
См. детальное описание в [workflow-catalog.md#4-workflow-фулфилмента](./workflow-catalog.md#4-workflow-фулфилмента)
|
||||
|
||||
#### 11.2.1 Структура склада по модулям (ОБЯЗАТЕЛЬНАЯ ПОСЛЕДОВАТЕЛЬНОСТЬ)
|
||||
### 11.3 Основные разделы кабинета
|
||||
|
||||
1. **📦 ПРОДУКТЫ** - готовые к отправке товары
|
||||
2. **🛒 ТОВАРЫ** - базовые товары от поставщиков
|
||||
- **"На складе"** - готовы к обработке
|
||||
- **"В обработке"** - в процессе создания продукта
|
||||
3. **❌ БРАК** - товары с дефектами, требуют утилизации
|
||||
4. **↩️ ВОЗВРАТЫ С ПВЗ** - возвращенные товары, к обработке
|
||||
5. **🎯 РАСХОДНИКИ СЕЛЛЕРОВ** - материалы для селлеров (тип `CONSUMABLE`, заказанные селлерами)
|
||||
6. **⚙️ РАСХОДНИКИ ФУЛФИЛМЕНТА** - операционные материалы (тип `CONSUMABLE`, заказанные фулфилментом, КЛИКАБЕЛЬНЫЙ модуль)
|
||||
**СТРУКТУРА ДОСТУПА**:
|
||||
|
||||
#### 11.2.2 Система учета склада
|
||||
- **Склад** - управление товарами по модулям
|
||||
- **Поставки** - обработка входящих товаров
|
||||
- **Услуги** - каталог услуг и расходники
|
||||
- **Сотрудники** - управление персоналом
|
||||
- **Статистика** - аналитика деятельности
|
||||
|
||||
**Дополнительные значения** (показатели движения):
|
||||
### 11.4 Правила фулфилмента
|
||||
|
||||
- **ПРИБЫЛО** - количество поступивших на склад за период
|
||||
- **УБЫЛО** - количество списанных со склада за период
|
||||
**ОБЯЗАТЕЛЬНО**:
|
||||
- Установка цен на расходники перед доступностью селлерам
|
||||
- Контроль качества товаров при приемке
|
||||
- Своевременная обработка возвратов
|
||||
- Ведение учета движения товаров
|
||||
- Управление персоналом и рабочим временем
|
||||
|
||||
**Основные значения** (текущие остатки):
|
||||
|
||||
- **ФОРМУЛА**: Основные значения = Предыдущие остатки + Прибыло - Убыло
|
||||
- **ОБНОВЛЕНИЕ**: В реальном времени с изменениями за сутки
|
||||
- **ИСТОЧНИК**: GraphQL query `GET_FULFILLMENT_WAREHOUSE_STATS`
|
||||
|
||||
#### 11.2.3 Структура данных склада (3-уровневая иерархия)
|
||||
|
||||
```
|
||||
🔵 УРОВЕНЬ 1: МАГАЗИНЫ
|
||||
├── ТехноМир (синий - blue-400/500)
|
||||
├── Стиль и Комфорт (розовый - pink-400/500)
|
||||
└── Зелёный Дом (изумрудный - emerald-400/500)
|
||||
↓
|
||||
🟢 УРОВЕНЬ 2: ТОВАРЫ (зеленый - green-500)
|
||||
↓
|
||||
🟠 УРОВЕНЬ 3: ВАРИАНТЫ ТОВАРОВ (оранжевый - orange-500)
|
||||
```
|
||||
|
||||
**Цветовое кодирование**:
|
||||
|
||||
- Каждый уровень имеет цветной индикатор увеличивающегося размера
|
||||
- Цветная левая граница с увеличивающимся отступом и толщиной
|
||||
- Скроллбары в цвете уровня
|
||||
- Контрастный цвет текста для читаемости
|
||||
|
||||
### 11.3 Движение товаров в фулфилменте
|
||||
|
||||
#### **Поступление товаров**:
|
||||
|
||||
- **ПОСТАВКИ**: От поставщиков через систему заказов
|
||||
- **ВОЗВРАТЫ**: Товары, возвращенные с ПВЗ
|
||||
- **ПЕРЕМЕЩЕНИЯ**: Между складами и магазинами
|
||||
|
||||
#### **Расход товаров**:
|
||||
|
||||
- **ОТГРУЗКА**: Товары отправлены селлерам
|
||||
- **СПИСАНИЕ**: Брак, утрата, утилизация
|
||||
- **ВОЗВРАТ**: Возврат поставщику
|
||||
- **ИСПОЛЬЗОВАНИЕ**: Расходники для операций
|
||||
|
||||
### 11.4 Модуль "Расходники фулфилмента"
|
||||
|
||||
**ОСОБЕННОСТИ**:
|
||||
|
||||
- **ИНТЕРАКТИВНОСТЬ**: Кликабельный элемент в статистике
|
||||
- **ФУНКЦИОНАЛЬНОСТЬ**: Полноценный раздел учёта
|
||||
- **СОДЕРЖАНИЕ**: Управление расходниками фулфилмента
|
||||
|
||||
### 11.5 Поставки фулфилмента (`/fulfillment-supplies`)
|
||||
|
||||
#### 11.5.1 Структура: 2 основные вкладки
|
||||
|
||||
**A) 🛒 ПОСТАВКИ ТОВАРОВ**:
|
||||
|
||||
- **Детализированные товары ФФ** - планы и факты поставок с маршрутами
|
||||
- **Товары ФФ** - общие поставки товаров от селлеров
|
||||
- **Возвраты с ПВЗ** - обработка возвращенных товаров
|
||||
|
||||
**B) 🔧 ПОСТАВКИ РАСХОДНИКОВ**:
|
||||
|
||||
- **Заказы расходников** - управление заказами от селлеров
|
||||
- **Расходники селлеров** - материалы для клиентов
|
||||
- **Создание поставок** - формирование новых поставок расходников
|
||||
|
||||
#### 11.5.2 Workflow поставок товаров
|
||||
|
||||
**Этапы обработки**:
|
||||
|
||||
1. **Planned** - поставка запланирована
|
||||
2. **In-transit** - товар в пути
|
||||
3. **Delivered** - доставлен на склад
|
||||
4. **Completed** - обработка завершена
|
||||
|
||||
### 11.6 Статистика фулфилмента (`/fulfillment-statistics`)
|
||||
|
||||
#### 11.6.1 Блоки аналитики (сворачиваемые)
|
||||
|
||||
**1. НАКОПЛЕННАЯ СТАТИСТИКА** (`allTime: true`):
|
||||
|
||||
- Обработано товаров (общий объем)
|
||||
- Выявлено брака (всего единиц)
|
||||
- Поставок получено
|
||||
- Общий доход (за все время)
|
||||
- Выполнено заказов (успешных отгрузок)
|
||||
- Удовлетворенность клиентов (средний рейтинг)
|
||||
|
||||
**2. ОТГРУЗКА НА ПЛОЩАДКИ** (`marketplaces: true`):
|
||||
|
||||
- Отправлено на Wildberries
|
||||
- Отправлено на Ozon
|
||||
- Отправлено на другие площадки
|
||||
|
||||
**3. АНАЛИТИКА ПРОИЗВОДИТЕЛЬНОСТИ** (`performance: false`):
|
||||
|
||||
- Среднее время обработки (на единицу товара)
|
||||
- Уровень брака (от общего объема)
|
||||
- Уровень возвратов (возвраты с площадок)
|
||||
- Удовлетворенность клиентов
|
||||
|
||||
### 11.7 Услуги фулфилмента (`/services`)
|
||||
|
||||
#### 11.7.1 Архитектура интеграции с системой
|
||||
|
||||
**СВЯЗЬ С РЕЦЕПТУРАМИ СЕЛЛЕРОВ:**
|
||||
|
||||
```
|
||||
СЕЛЛЕР (создание поставки)
|
||||
└── Рецептура
|
||||
├── Товар (от поставщика)
|
||||
├── Услуги фулфилмента ← CRUD в разделе Услуги
|
||||
├── Расходники селлера
|
||||
└── Расходники фулфилмента ← ТОЛЬКО с установленной ценой
|
||||
↓
|
||||
ФУЛФИЛМЕНТ (обработка)
|
||||
├── Входящие поставки → Поставки расходников (создание)
|
||||
└── Услуги → Расходники (установка цены за единицу)
|
||||
```
|
||||
|
||||
#### 11.7.2 Структура: 3 обязательные вкладки
|
||||
|
||||
**A) 🛠️ УСЛУГИ** (`defaultValue="services"`):
|
||||
|
||||
- **Полный CRUD**: создание, редактирование, удаление услуг
|
||||
- **Поля**: `name`, `description`, `price`, `imageUrl`
|
||||
- **Glass Upload Zone**: элегантная загрузка изображений
|
||||
- **Назначение**: каталог услуг для рецептур селлеров
|
||||
- **GraphQL**: `GET_MY_SERVICES`, `CREATE_SERVICE`, `UPDATE_SERVICE`, `DELETE_SERVICE`
|
||||
|
||||
**B) 🚚 ЛОГИСТИКА**:
|
||||
|
||||
- **Полный CRUD**: маршруты доставки
|
||||
- **Поля**: откуда → куда, тарификация до/свыше 1м³
|
||||
- **Группированные локации**:
|
||||
- Мой фулфилмент (название организации)
|
||||
- Рынки (предустановленные)
|
||||
- Склады Wildberries
|
||||
- Склады Ozon
|
||||
- **Glass Upload Zone**: для изображений маршрутов
|
||||
- **GraphQL**: `GET_MY_LOGISTICS`, `CREATE_LOGISTICS`, `UPDATE_LOGISTICS`, `DELETE_LOGISTICS`
|
||||
|
||||
**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 Разделение цен закупки и продажи
|
||||
|
||||
**КРИТИЧЕСКОЕ ПРАВИЛО**: Расходники фулфилмента имеют **ДВЕ РАЗНЫЕ ЦЕНЫ** для разных бизнес-процессов:
|
||||
|
||||
1. **ЦЕНА ЗАКУПКИ** (`Supply.price`) - цена, по которой фулфилмент купил расходник у поставщика
|
||||
2. **ЦЕНА ПРОДАЖИ** (`Supply.pricePerUnit`) - цена, по которой фулфилмент продает расходник селлерам
|
||||
|
||||
**ПОЛЯ В БАЗЕ ДАННЫХ**:
|
||||
|
||||
```prisma
|
||||
model Supply {
|
||||
price Decimal @db.Decimal(10, 2) // Цена закупки у поставщика (НЕИЗМЕННАЯ)
|
||||
pricePerUnit Decimal? @db.Decimal(10, 2) // Цена продажи селлерам (устанавливается фулфилментом)
|
||||
}
|
||||
```
|
||||
|
||||
**ПРАВИЛА ОТОБРАЖЕНИЯ ПО РАЗДЕЛАМ**:
|
||||
|
||||
**РАЗДЕЛ "СКЛАД → РАСХОДНИКИ ФУЛФИЛМЕНТА"**:
|
||||
|
||||
- Показывает `Supply.price` (цена закупки)
|
||||
- Цена ТОЛЬКО ДЛЯ ЧТЕНИЯ, нельзя изменять
|
||||
- Отражает историческую стоимость приобретения
|
||||
|
||||
**РАЗДЕЛ "УСЛУГИ → РАСХОДНИКИ"**:
|
||||
|
||||
- Показывает и редактирует `Supply.pricePerUnit` (цена продажи)
|
||||
- Единственное место где можно изменить цену для селлеров
|
||||
- Влияет на рецептуры и расчеты для селлеров
|
||||
|
||||
**БИЗНЕС-ЛОГИКА СОЗДАНИЯ**:
|
||||
|
||||
ПРИ ПОСТУПЛЕНИИ ОТ ПОСТАВЩИКА:
|
||||
|
||||
```typescript
|
||||
const supply = await prisma.supply.create({
|
||||
data: {
|
||||
price: item.price, // Цена поставщика → ЗАФИКСИРОВАНА
|
||||
pricePerUnit: null, // Цена продажи → ПУСТАЯ
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
УСТАНОВКА ЦЕНЫ ПРОДАЖИ (в разделе "Услуги"):
|
||||
|
||||
```typescript
|
||||
const updated = await prisma.supply.update({
|
||||
data: {
|
||||
pricePerUnit: newPrice, // ТОЛЬКО цена продажи
|
||||
// price НЕ ТРОГАЕМ - остается цена закупки
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### 11.7.6 Технические требования
|
||||
|
||||
**GraphQL типы:**
|
||||
|
||||
```graphql
|
||||
# Для рецептур - только доступные с ценой
|
||||
getAvailableSuppliesForRecipe: [SupplyForRecipe!]!
|
||||
|
||||
# В разделе Услуги - все расходники
|
||||
getMySupplies: [Supply!]!
|
||||
|
||||
type Supply {
|
||||
pricePerUnit: Float # Может быть null
|
||||
unit: String! # "шт", "кг", "м"
|
||||
isAvailable: Boolean! # Статус на складе
|
||||
warehouseConsumableId: ID! # Связь со складом
|
||||
}
|
||||
```
|
||||
|
||||
**Экономический учет:**
|
||||
|
||||
- Создание: через поставки расходников
|
||||
- Ценообразование: в разделе Услуги
|
||||
- Списание: со склада при использовании
|
||||
- Стоимость = количество × цена за единицу
|
||||
|
||||
### 11.8 Сотрудники фулфилмента (`/employees`)
|
||||
|
||||
#### 11.8.1 Структура: 2 основные вкладки
|
||||
|
||||
**A) 👥 СОТРУДНИКИ** (`defaultValue="combined"`):
|
||||
|
||||
**Управление персоналом**:
|
||||
|
||||
- **CRUD операции**: создание, редактирование, удаление сотрудников
|
||||
- **Статусы сотрудников**: `ACTIVE`, `VACATION`, `SICK`, `FIRED`
|
||||
- **Формы добавления**: Компактная (`showCompactForm`) / Полная форма
|
||||
- **Поиск и фильтрация** по имени, должности, статусу
|
||||
|
||||
**Табель рабочего времени**:
|
||||
|
||||
- **Навигация по месяцам**: текущий год/месяц с кнопками ←/→
|
||||
- **Отметки по дням**: статус дня и количество отработанных часов
|
||||
- **GraphQL**: `GET_EMPLOYEE_SCHEDULE`, `UPDATE_EMPLOYEE_SCHEDULE`
|
||||
|
||||
**B) 📋 ОТЧЕТЫ** (`value="reports"`):
|
||||
|
||||
- **Сводные отчеты** по сотрудникам за период
|
||||
- **Экспорт данных** табеля
|
||||
- **Аналитика рабочего времени**
|
||||
|
||||
### 11.9 Блок детализации по магазинам
|
||||
|
||||
**НАЗНАЧЕНИЕ**: Распределение товаров по торговым точкам/магазинам
|
||||
|
||||
**ФУНКЦИИ**:
|
||||
|
||||
- **ОСТАТКИ ПО МАГАЗИНАМ**: Отображение количества товаров в каждом магазине
|
||||
- **УПРАВЛЕНИЕ РАСПРЕДЕЛЕНИЕМ**: Перемещение товаров между точками
|
||||
- **КОНТРОЛЬ ДВИЖЕНИЯ**: Отслеживание перемещений между складами и магазинами
|
||||
- **АНАЛИТИКА**: Сравнение эффективности разных точек
|
||||
- **ПЛАНИРОВАНИЕ**: Оптимизация распределения товаров
|
||||
**ЗАПРЕЩЕНО**:
|
||||
- Отгружать товары без подтверждения наличия
|
||||
- Создавать расходники минуя систему поставок
|
||||
- Изменять цены закупки после поступления товара
|
||||
- Показывать в рецептурах неактивные расходники
|
||||
|
||||
---
|
||||
|
||||
## 12. 🚚 КАБИНЕТ ЛОГИСТИКИ
|
||||
|
||||
> 📖 **Технические детали кабинета**: См. [logist-cabinet-rules.md](./logist-cabinet-rules.md) для компонентов, GraphQL, UI/UX и всех технических деталей реализации
|
||||
### 12.1 Основные функции логистики
|
||||
|
||||
**КРАТКАЯ РОЛЬ В СИСТЕМЕ**: Управление доставками и транспортировкой
|
||||
**РОЛЬ В СИСТЕМЕ**: Управление доставками и транспортировкой
|
||||
|
||||
**КЛЮЧЕВЫЕ ФУНКЦИИ**:
|
||||
**ОСНОВНЫЕ ФУНКЦИИ**:
|
||||
|
||||
- Подтверждение возможности доставки поставок
|
||||
- Организация и выполнение доставки товаров
|
||||
- Управление логистическими маршрутами
|
||||
- Мониторинг грузов в пути
|
||||
- **ПОДТВЕРЖДЕНИЕ ДОСТАВКИ**: Подтверждение возможности доставки поставок
|
||||
- **ТРАНСПОРТИРОВКА**: Организация и выполнение доставки товаров
|
||||
- **КОНТРОЛЬ МАРШРУТОВ**: Управление логистическими маршрутами
|
||||
- **ОТСЛЕЖИВАНИЕ**: Мониторинг грузов в пути
|
||||
|
||||
### 12.2 Workflow для логистики
|
||||
|
||||
См. детальное описание в [workflow-catalog.md#5-workflow-логистики](./workflow-catalog.md#5-workflow-логистики)
|
||||
|
||||
### 12.3 Система тарификации
|
||||
|
||||
**ПАРАМЕТРЫ ТАРИФИКАЦИИ**:
|
||||
|
||||
- **Тариф до 1м³** - базовая стоимость для малых грузов
|
||||
- **Тариф свыше 1м³** - стоимость для крупных грузов
|
||||
- **Маршруты доставки** - от точки отправления до точки назначения
|
||||
- **Описание услуг** - дополнительные условия доставки
|
||||
|
||||
**РАСЧЕТ СТОИМОСТИ**:
|
||||
|
||||
- Автоматический расчет стоимости доставки по объему груза
|
||||
- Отображение примерной стоимости при создании заказа
|
||||
- Учет специфики маршрута и условий доставки
|
||||
|
||||
### 12.4 Управление заявками
|
||||
|
||||
**РАЗДЕЛЫ КАБИНЕТА ЛОГИСТИКИ**:
|
||||
|
||||
- **НОВЫЕ ЗАЯВКИ** - поступившие заявки на доставку
|
||||
- **В РАБОТЕ** - принятые к исполнению заявки
|
||||
- **ВЫПОЛНЕННЫЕ** - завершенные доставки
|
||||
- **ОТКЛОНЕННЫЕ** - заявки, которые не могут быть выполнены
|
||||
|
||||
**ИНФОРМАЦИЯ О ЗАЯВКЕ**:
|
||||
|
||||
- Детали груза (объем, вес, габариты)
|
||||
- Маршрут доставки (откуда - куда)
|
||||
- Срочность доставки
|
||||
- Особые требования к транспортировке
|
||||
- Контактная информация участников
|
||||
|
||||
### 12.5 Правила логистики
|
||||
|
||||
**ОБЯЗАТЕЛЬНО**:
|
||||
|
||||
- Своевременное подтверждение заявок
|
||||
- Соблюдение сроков доставки
|
||||
- Бережная транспортировка товаров
|
||||
- Уведомление о статусе доставки
|
||||
|
||||
**ЗАПРЕЩЕНО**:
|
||||
|
||||
- Принятие заявок без подтверждения возможности выполнения
|
||||
- Нарушение сроков доставки без уведомления
|
||||
- Повреждение товаров при транспортировке
|
||||
|
||||
---
|
||||
|
||||
@ -2498,6 +2237,115 @@ const wholesalePartners = await prisma.counterparty.findMany({
|
||||
- [ ] Проверить данные в базе через Prisma Studio
|
||||
- [ ] Использовать `fetchPolicy: 'network-only'` для обхода кеша
|
||||
|
||||
### 13.6 Различие партнерских и реферальных ссылок
|
||||
|
||||
⚠️ **КРИТИЧЕСКИ ВАЖНО**: НЕ ПУТАТЬ два различных типа ссылок в системе!
|
||||
|
||||
#### **13.6.1 Партнерские ссылки**
|
||||
|
||||
**НАЗНАЧЕНИЕ**: Бизнес-партнерство с автоматическим добавлением в контрагенты
|
||||
|
||||
**ФОРМАТ URL**: `?partner=REFERRAL_CODE`
|
||||
```
|
||||
http://localhost:3000/register?partner=SF2X9K4M7P
|
||||
```
|
||||
|
||||
**ЧТО ПРОИСХОДИТ**:
|
||||
1. ✅ Начисляется 100 сфер (⚡) реферальная награда
|
||||
2. ✅ **Автоматически создается партнерство**: взаимное добавление в контрагенты
|
||||
3. ✅ Устанавливается реферальная связь (`referredById`)
|
||||
4. ✅ Создаются записи в таблице `Counterparty` (двусторонние)
|
||||
5. ✅ Организации видят друг друга в разделе "Партнеры"
|
||||
|
||||
**ИСПОЛЬЗОВАНИЕ**: Когда нужно сразу стать деловыми партнерами и начать работать
|
||||
|
||||
#### **13.6.2 Реферальные ссылки**
|
||||
|
||||
**НАЗНАЧЕНИЕ**: Маркетинговое привлечение с наградой, БЕЗ автоматического партнерства
|
||||
|
||||
**ФОРМАТ URL**: `?ref=REFERRAL_CODE`
|
||||
```
|
||||
http://localhost:3000/register?ref=SF2X9K4M7P
|
||||
```
|
||||
|
||||
**ЧТО ПРОИСХОДИТ**:
|
||||
1. ✅ Начисляется 100 сфер (⚡) реферальная награда
|
||||
2. ✅ Устанавливается реферальная связь (`referredById`)
|
||||
3. ❌ **НЕ создается партнерство**: организации НЕ добавляются в контрагенты
|
||||
4. ❌ Организации НЕ видят друг друга в разделе "Партнеры"
|
||||
|
||||
**ИСПОЛЬЗОВАНИЕ**: Маркетинговые кампании, блогеры, инфлюенсеры
|
||||
|
||||
#### **13.6.3 Технические различия в коде**
|
||||
|
||||
**В резолверах регистрации**:
|
||||
|
||||
```typescript
|
||||
// Обработка партнерского кода (создает партнерство)
|
||||
if (partnerCode) {
|
||||
// 1. Найти организацию-партнера
|
||||
// 2. Создать реферальную транзакцию
|
||||
// 3. Установить реферальную связь
|
||||
// 4. СОЗДАТЬ ВЗАИМНОЕ ПАРТНЕРСТВО ← Ключевое отличие!
|
||||
}
|
||||
|
||||
// Обработка реферального кода (только награда)
|
||||
if (referralCode) {
|
||||
// 1. Найти организацию-реферера
|
||||
// 2. Создать реферальную транзакцию
|
||||
// 3. Установить реферальную связь
|
||||
// 4. БЕЗ создания партнерства ← Ключевое отличие!
|
||||
}
|
||||
```
|
||||
|
||||
#### **13.6.4 UI различия**
|
||||
|
||||
**В разделе "Партнеры"**:
|
||||
|
||||
**Вкладка "Мои партнеры"**:
|
||||
- Партнерская ссылка: `?partner=CODE` (автоматическое партнерство)
|
||||
- Заголовок: "Пригласить партнера"
|
||||
- Описание: "Для прямого делового сотрудничества"
|
||||
|
||||
**Вкладка "Рефералы"**:
|
||||
- Реферальная ссылка: `?ref=CODE` (только маркетинг)
|
||||
- Заголовок: "Реферальная ссылка"
|
||||
- Описание: "Для маркетинговых кампаний"
|
||||
|
||||
#### **13.6.5 Правила именования**
|
||||
|
||||
**В коде ВСЕГДА использовать**:
|
||||
- `partnerCode` / `partner=` → бизнес-партнерство
|
||||
- `referralCode` / `ref=` → маркетинговое привлечение
|
||||
|
||||
**В комментариях и документации**:
|
||||
- "Партнерская ссылка" → автоматическое партнерство
|
||||
- "Реферальная ссылка" → только маркетинг
|
||||
|
||||
**ЗАПРЕЩЕНО**:
|
||||
- ❌ Называть партнерские ссылки "реферальными"
|
||||
- ❌ Называть реферальные ссылки "партнерскими"
|
||||
- ❌ Использовать термины взаимозаменяемо
|
||||
- ❌ Путать логику обработки в резолверах
|
||||
|
||||
#### **13.6.6 Примеры использования**
|
||||
|
||||
**Сценарий 1 - Деловое партнерство**:
|
||||
```
|
||||
Фулфилмент-центр хочет пригласить логистическую компанию
|
||||
→ Использует партнерскую ссылку ?partner=CODE
|
||||
→ Логисты регистрируются и сразу становятся партнерами
|
||||
→ Могут сразу работать друг с другом
|
||||
```
|
||||
|
||||
**Сценарий 2 - Маркетинговая кампания**:
|
||||
```
|
||||
Организация запускает рекламу в соцсетях
|
||||
→ Использует реферальную ссылку ?ref=CODE
|
||||
→ Люди регистрируются, организация получает сферы
|
||||
→ Партнерство НЕ создается (это маркетинг)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 🌐 ИНТЕГРАЦИИ С СИСТЕМОЙ
|
||||
@ -3464,7 +3312,7 @@ const handleSuppliesClick = () => {
|
||||
|
||||
_Эта база знаний создана путем объединения rules-unified.md (v3.0) и fulfillment-cabinet-rules.md (v1.0) с устранением всех несоответствий и добавлением критически важных улучшений: быстрый справочник, глоссарий терминов, детальные алгоритмы процессов, edge cases._
|
||||
|
||||
_Версия: 10.1_
|
||||
_Версия: 10.2_
|
||||
_Дата создания: 2025_
|
||||
_Статус: ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ - ГОТОВ К РАЗРАБОТКЕ_
|
||||
|
||||
@ -3560,3 +3408,12 @@ _Статус: ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ - ГОТОВ К РАЗ
|
||||
- ✅ **СИСТЕМА МАРКИРОВКИ**: 📖 ФАКТ, 🧠 ИНТЕРПРЕТАЦИЯ, ❓ ПРЕДПОЛОЖЕНИЕ, ⚠️ НЕ НАЙДЕНО
|
||||
- ✅ **СТОП-СЛОВА**: Список категоричных утверждений для избегания без доказательств
|
||||
- ✅ **ОБЯЗАТЕЛЬНЫЕ ПРАВИЛА 11-13**: Указание источников и осторожные формулировки
|
||||
|
||||
### 🔗 ПАРТНЕРСКАЯ И РЕФЕРАЛЬНАЯ СИСТЕМА v10.2:
|
||||
|
||||
- ✅ **ДОБАВЛЕН РАЗДЕЛ 13.6**: Критическое различие партнерских и реферальных ссылок
|
||||
- ✅ **ЧЕТКИЕ ОПРЕДЕЛЕНИЯ**: Партнерские (?partner=) vs Реферальные (?ref=) ссылки
|
||||
- ✅ **ТЕХНИЧЕСКИЕ ПРАВИЛА**: Различия в обработке кодов в резолверах
|
||||
- ✅ **UI СПЕЦИФИКАЦИИ**: Разные интерфейсы для партнерства и маркетинга
|
||||
- ✅ **ЗАПРЕТЫ ПУТАНИЦЫ**: Строгие правила именования и терминологии
|
||||
- ✅ **ПРИМЕРЫ СЦЕНАРИЕВ**: Деловое партнерство vs маркетинговые кампании
|
||||
|
500
seller-ui-rules.md
Normal file
500
seller-ui-rules.md
Normal file
@ -0,0 +1,500 @@
|
||||
# ПРАВИЛА UI/UX КАБИНЕТА СЕЛЛЕРА (SELLER)
|
||||
|
||||
> ⚠️ **ВАЖНО**: Это файл с UI/UX деталями кабинета селлера.
|
||||
> Общие бизнес-правила находятся в **[rules-complete.md](./rules-complete.md)**
|
||||
|
||||
## Когда использовать этот файл:
|
||||
|
||||
- Работа с компонентами `/supplies`, `/my-supplies`
|
||||
- UI/UX специфика кабинета селлера
|
||||
- Интерфейсы создания поставок
|
||||
- Визуальные правила компонентов селлера
|
||||
|
||||
## 1. 🛍️ СТРУКТУРА КАБИНЕТА СЕЛЛЕРА
|
||||
|
||||
### 1.1 Основные разделы
|
||||
|
||||
**СЕЛЛЕР (`SELLER`)** имеет доступ к следующим разделам:
|
||||
|
||||
- **Мои поставки** (`/my-supplies`) - управление поставками
|
||||
- **Маркет** (`/market`) - просмотр глобального каталога
|
||||
- **Партнеры** (`/partners`) - управление контрагентами
|
||||
- **Мессенджер** (`/messenger`) - связь с партнерами
|
||||
- **Настройки** (`/settings`) - профиль и настройки
|
||||
- **Экономика** (`/economics`) - финансовая аналитика
|
||||
|
||||
### 1.2 Навигация и роутинг
|
||||
|
||||
#### При входе в систему:
|
||||
|
||||
```typescript
|
||||
switch (user?.organization?.type) {
|
||||
case 'SELLER':
|
||||
router.push('/my-supplies') // Направляем на страницу поставок
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
#### Специальная логика роутинга:
|
||||
|
||||
> 📖 **Бизнес-логика роутинга**: См. [rules-complete.md#4-система-ролей-и-доступов](./rules-complete.md#4--система-ролей-и-доступов)
|
||||
|
||||
## 2. 🎨 UI/UX КОМПОНЕНТЫ
|
||||
|
||||
### 2.1 Dashboard компоненты
|
||||
|
||||
#### Основные компоненты кабинета:
|
||||
|
||||
- `SellerHomePage` - главный компонент селлера
|
||||
- `SellerEconomicsPage` - экономическая аналитика
|
||||
- `MySuppliesDashboard` - управление поставками
|
||||
|
||||
#### Wrapper-компоненты:
|
||||
|
||||
- `HomePageWrapper` - маршрутизация по типам организаций
|
||||
- `EconomicsPageWrapper` - адаптивная экономика по кабинетам
|
||||
|
||||
### 2.2 Детальные правила горизонтального скролла поставщиков
|
||||
|
||||
**СТРУКТУРА И ОТОБРАЖЕНИЕ:**
|
||||
|
||||
- **Источник данных**: Партнеры типа `WHOLESALE` из раздела "Партнеры"
|
||||
- **Контейнер**: Фиксированная высота 176px (h-44) с горизонтальным скроллом
|
||||
- **Блок поставщиков**: Общая высота 180px, включает заголовок + контейнер скролла
|
||||
- **Направление**: Слева направо (LTR)
|
||||
- **Поведение**: Плавный скролл с автоскрытием полосы прокрутки
|
||||
|
||||
**РАЗМЕРЫ И АДАПТИВНОСТЬ:**
|
||||
|
||||
- **Десктоп**: Карточка 216×92px, отступы 12px между карточками, 16px от краев
|
||||
- **Планшет**: Карточка 200×92px, отступы 12px между карточками
|
||||
- **Мобильный**: Карточка 184×92px, отступы 12px между карточками
|
||||
- **Высота блока**: 180px фиксированная для всего блока поставщиков
|
||||
|
||||
**ВЗАИМОДЕЙСТВИЕ:**
|
||||
|
||||
- **Навигация**: Колесо мыши (Shift+скролл), стрелки клавиатуры, свайп на тач
|
||||
- **Выбор**: Клик по карточке → активная рамка + загрузка товаров в блок 2
|
||||
- **Состояния**: Default, Hover (box-shadow), Active (цветная рамка), Loading (скелетон)
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
|
||||
- **1-4 карточки**: Выравнивание по левому краю, скролл неактивен
|
||||
- **5+ карточек**: Полный горизонтальный скролл
|
||||
- **Нет партнеров**: Заглушка с ссылкой на раздел "Партнеры"
|
||||
|
||||
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
|
||||
|
||||
**Критическая Flex-архитектура:**
|
||||
|
||||
```css
|
||||
.parent-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.left-block {
|
||||
flex: 1;
|
||||
min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для overflow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suppliers-container {
|
||||
height: 180px; /* Общая высота блока */
|
||||
flex-shrink: 0;
|
||||
min-width: 0; /* Предотвращает растяжение */
|
||||
}
|
||||
|
||||
.right-block {
|
||||
width: 384px; /* w-96 */
|
||||
flex-shrink: 0; /* Защита от сжатия */
|
||||
}
|
||||
```
|
||||
|
||||
**Контейнер скролла:**
|
||||
|
||||
```css
|
||||
.suppliers-block {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-behavior: smooth;
|
||||
gap: 12px;
|
||||
padding: 0 16px 8px 16px; /* px-4 pb-2 */
|
||||
height: 176px; /* h-44 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #64748b33 transparent;
|
||||
}
|
||||
|
||||
.suppliers-block:hover {
|
||||
scrollbar-color: #cbd5e0 #64748b22;
|
||||
}
|
||||
|
||||
.supplier-card {
|
||||
flex-shrink: 0;
|
||||
width: 216px; /* Десктоп */
|
||||
height: 92px; /* Фиксированная высота */
|
||||
padding: 8px; /* p-2 */
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
```
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ПОСТАВЩИКА:**
|
||||
|
||||
**Структура (3 строки в 92px высоты):**
|
||||
|
||||
- **Строка 1**: Название + рейтинг (справа, если есть)
|
||||
- **Строка 2**: ИНН (формат "ИНН: 1234567890")
|
||||
- **Строка 3**: Бейдж рынка (отдельная строка)
|
||||
|
||||
**Элементы:**
|
||||
|
||||
- **Аватар**: Размер xs, слева с gap-2
|
||||
- **Текст**: text-xs для компактности
|
||||
- **Отступы**: mb-1 между строками 1-2, mb-0.5 между строками 2-3
|
||||
- **Padding карточки**: 8px (p-2)
|
||||
|
||||
**ЦВЕТОВАЯ СХЕМА РЫНКОВ:**
|
||||
|
||||
- **"Садовод"** (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`
|
||||
- **Другие/не указан**: Серый `bg-gray-500/20 text-gray-300 border-gray-500/30`
|
||||
|
||||
**ДОСТУПНОСТЬ:**
|
||||
|
||||
- `role="tablist"` для контейнера
|
||||
- `role="tab"` для карточек
|
||||
- `aria-selected="true/false"` для выбранной карточки
|
||||
- `tabindex="0"` для активной, `-1` для неактивных
|
||||
|
||||
### 2.3 Правила блока "Карточки товаров" (Блок 2)
|
||||
|
||||
**НАЗНАЧЕНИЕ И ЛОГИКА:**
|
||||
|
||||
- **Источник данных**: Товары выбранного поставщика из Блока 1
|
||||
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
|
||||
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
|
||||
- **Поведение**: Горизонтальный скролл при множестве товаров
|
||||
|
||||
**АРХИТЕКТУРА И РАЗМЕРЫ:**
|
||||
|
||||
- **Внешний контейнер**: 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 (w-20 h-28), соотношение 5:7
|
||||
- **Адаптивность**: фиксированный размер для всех устройств
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
|
||||
|
||||
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
|
||||
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
|
||||
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
|
||||
- **Рамка**: border-white/10, при выборе border-white/30
|
||||
- **Фон**: bg-white/5 полупрозрачный
|
||||
|
||||
**ДЕЙСТВИЕ:**
|
||||
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
|
||||
|
||||
### 2.4 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
|
||||
|
||||
#### **2.4.1 Размеры и позиционирование**
|
||||
|
||||
```tsx
|
||||
<div className="w-72 flex-shrink-0">
|
||||
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
|
||||
```
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
|
||||
|
||||
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
|
||||
- **Флекс**: `flex-shrink-0` - корзина не сжимается
|
||||
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
|
||||
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
|
||||
|
||||
#### **2.4.2 Автодобавление товаров**
|
||||
|
||||
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
|
||||
|
||||
```tsx
|
||||
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
|
||||
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value
|
||||
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
|
||||
|
||||
if (newQuantity > 0) {
|
||||
// Автоматически добавляем товар в корзину
|
||||
updateProductQuantity(product.id, newQuantity)
|
||||
} else {
|
||||
// Удаляем товар из корзины при количестве 0
|
||||
removeFromCart(product.id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
|
||||
|
||||
#### **2.4.3 Структура корзины**
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
|
||||
|
||||
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
|
||||
2. **Список товаров**:
|
||||
- Название товара (БЕЗ суффикса "(с рецептурой)")
|
||||
- Цена за единицу × количество
|
||||
- Кнопка удаления (X справа)
|
||||
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
|
||||
4. **Итого**: Общая сумма с выделением зелёным цветом
|
||||
5. **Кнопка действия**: "Создать поставку" с градиентом
|
||||
|
||||
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
|
||||
|
||||
#### **2.4.4 Единая функция расчета стоимости**
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
|
||||
|
||||
```tsx
|
||||
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
|
||||
const product = products.find((p) => p.id === productId)
|
||||
if (!product) return 0
|
||||
|
||||
// Базовая цена товара
|
||||
let total = (product.pricePerUnit || 0) * quantity
|
||||
|
||||
// Добавляем услуги
|
||||
if (product.services && product.services.length > 0) {
|
||||
const servicesTotal = product.services.reduce((sum, service) => {
|
||||
return sum + (service.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += servicesTotal
|
||||
}
|
||||
|
||||
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
|
||||
if (product.ffConsumables && product.ffConsumables.length > 0) {
|
||||
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.price || 0) * quantity // ВАЖНО: .price!
|
||||
}, 0)
|
||||
total += ffConsumablesTotal
|
||||
}
|
||||
|
||||
// Добавляем расходники продавца
|
||||
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
|
||||
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += sellerConsumablesTotal
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
#### **2.4.5 Синхронизация данных между блоками**
|
||||
|
||||
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
|
||||
|
||||
1. **Дата поставки**: Из Блока 3 (дата пикер)
|
||||
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
|
||||
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
|
||||
|
||||
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
|
||||
|
||||
```
|
||||
Дата поставки: 08.08.2025
|
||||
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
|
||||
Логистическая компания: [Выпадающий список]
|
||||
```
|
||||
|
||||
#### **2.4.6 Критические требования**
|
||||
|
||||
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
|
||||
|
||||
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
|
||||
- Использовать optional chaining для всех вложенных объектов
|
||||
|
||||
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
|
||||
|
||||
- Мемоизация расчетов: `useMemo` для дорогих вычислений
|
||||
- Debounce для инпутов количества
|
||||
|
||||
🚨 **UX КОНСИСТЕНТНОСТЬ**:
|
||||
|
||||
- Единые стили для всех корзин в системе
|
||||
- Одинаковое поведение auto-add во всех формах
|
||||
- Синхронная валидация данных
|
||||
|
||||
### 2.5 Трёхблочная архитектура страницы "Мои поставки"
|
||||
|
||||
**ПРИНЦИП**: Страница состоит из трёх визуально разделённых блоков
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 1. БЛОК ТАБОВ (навигация) │
|
||||
│ - Фиксированная высота │
|
||||
│ - Glass-эффект │
|
||||
│ - Иерархическая структура │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 2. БЛОК СТАТИСТИКИ (метрики) │
|
||||
│ - Контекстные данные │
|
||||
│ - 4 карточки в ряд (desktop) │
|
||||
│ - Динамическое обновление │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 3. ОСНОВНОЙ БЛОК (контент) │
|
||||
│ - Сохраняет весь функционал │
|
||||
│ - Таблицы, фильтры, действия │
|
||||
│ - Высота до низа sidebar │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**ПРАВИЛО**: Статистика меняется в зависимости от выбранных табов
|
||||
|
||||
**Для путей "Фулфилмент → Товар → Карточки/Поставщики":**
|
||||
|
||||
- Всего поставок
|
||||
- Активных поставок
|
||||
- Сумма активных поставок
|
||||
- В пути
|
||||
|
||||
**ФОРМУЛА РАСЧЕТА ВЫСОТЫ**:
|
||||
|
||||
```css
|
||||
height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
|
||||
```
|
||||
|
||||
**ПРАВИЛО ВЫРАВНИВАНИЯ**:
|
||||
|
||||
- Нижняя граница основного блока должна быть на одном уровне с нижней границей sidebar
|
||||
- При изменении размера окна высота пересчитывается
|
||||
- Внутренний скролл: `overflow-y-auto`
|
||||
|
||||
### 2.6 Четырёхблочная архитектура создания поставки расходников
|
||||
|
||||
#### **Структура страницы**:
|
||||
|
||||
**БЛОК 1: ПОСТАВЩИКИ** _(обязательный, 180px)_:
|
||||
|
||||
- Карточки поставщиков из раздела "Партнеры"
|
||||
- Горизонтальный скролл при превышении ширины
|
||||
- Выбор только одного поставщика одновременно
|
||||
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(адаптивная высота - НОВЫЙ БЛОК)_:
|
||||
|
||||
- ТОЛЬКО минималистичные карточки товаров 80×112px
|
||||
- ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
|
||||
- Горизонтальный скролл при множестве товаров
|
||||
- Клик добавляет товар в блок 3
|
||||
|
||||
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(flex-1, детальный каталог)_:
|
||||
|
||||
- Детальные карточки выбранных товаров
|
||||
- Управление количеством и параметрами поставки
|
||||
|
||||
**БЛОК 4: КОРЗИНА И НАСТРОЙКИ** _(правая панель, 384px)_:
|
||||
|
||||
- Корзина поставки с выбранными товарами
|
||||
- Настройки поставки (фулфилмент-центр, дата, логистика)
|
||||
- Сортировка: цена, название, категория
|
||||
- Фильтры: категория, ценовой диапазон
|
||||
- Карточка с полем ввода количества и кнопками +/-
|
||||
|
||||
**БЛОК 3: КОРЗИНА** _(правая часть)_:
|
||||
|
||||
- **РАСПОЛОЖЕНИЕ**: Правая часть экрана
|
||||
- **СОДЕРЖАНИЕ**:
|
||||
- Счетчик видов расходников
|
||||
- Детализация по каждому расходнику (название, количество, цена, сумма)
|
||||
- Общая сумма всех расходников
|
||||
- **УПРАВЛЕНИЕ**:
|
||||
- Изменение количества (с валидацией остатков)
|
||||
- Удаление позиций
|
||||
- **ОБЯЗАТЕЛЬНЫЕ ПОЛЯ**:
|
||||
- Выбор фулфилмент-центра (из партнеров)
|
||||
- Дата поставки (не прошедшая, по умолчанию - текущая)
|
||||
|
||||
### 2.7 Многоуровневая таблица поставок
|
||||
|
||||
#### **ПЕРВЫЙ УРОВЕНЬ** _(основной список)_:
|
||||
|
||||
- **СОРТИРОВКА**: Номер поставки от большего к меньшему
|
||||
- **ОБЯЗАТЕЛЬНЫЕ КОЛОНКИ**:
|
||||
- Порядковый номер поставки
|
||||
- Количество видов расходников
|
||||
- Стоимость всей поставки
|
||||
- Количество категорий
|
||||
- Статус поставки
|
||||
|
||||
#### **ВТОРОЙ УРОВЕНЬ** _(детализация по клику)_:
|
||||
|
||||
- **АКТИВАЦИЯ**: По клику на строку первого уровня
|
||||
- **СОДЕРЖАНИЕ**:
|
||||
- Название расходника
|
||||
- Количество
|
||||
- Цена
|
||||
- Категория
|
||||
- Поставщик
|
||||
- **ОГРАНИЧЕНИЯ**: Только просмотр, редактирование запрещено
|
||||
|
||||
## 3. 🛠️ ГРАФИЧЕСКИЙ ИНТЕРФЕЙС И КОМПОНЕНТЫ
|
||||
|
||||
### 3.1 React компоненты селлера
|
||||
|
||||
**Основные компоненты:**
|
||||
- `SellerHomePage` - главная страница селлера (4 типо-зависимых компонента)
|
||||
- `SellerEconomicsPage` - экономическая аналитика селлера
|
||||
|
||||
### 3.2 Роутинг для селлера
|
||||
|
||||
```typescript
|
||||
switch (user?.organization?.type) {
|
||||
case 'SELLER':
|
||||
router.push('/supplies')
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Структура разделов кабинета
|
||||
|
||||
**🛍️ СЕЛЛЕР (`SELLER`):**
|
||||
|
||||
- Мои поставки (`/supplies`) - управление заказами товаров
|
||||
- WB Интеграция (`/wb-integration`) - связь с Wildberries
|
||||
|
||||
## 4. 🛠️ GRAPHQL API
|
||||
|
||||
### 4.1 Основные запросы (Queries)
|
||||
|
||||
#### Получение карточек селлера:
|
||||
|
||||
```graphql
|
||||
query GetSellerCards {
|
||||
myMarketplaceCards {
|
||||
id
|
||||
title
|
||||
marketplace
|
||||
article
|
||||
linkedProductId # null если свободна
|
||||
linkedProduct {
|
||||
# для отображения занятости
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Последнее обновление**: Август 2025
|
||||
**Связанные файлы**:
|
||||
|
||||
- [rules-complete.md](./rules-complete.md) - Общие бизнес-правила
|
||||
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила
|
3644
server.log
3644
server.log
@ -1,3644 +0,0 @@
|
||||
|
||||
> sferav@0.1.0 dev
|
||||
> next dev --turbopack
|
||||
|
||||
▲ Next.js 15.4.1 (Turbopack)
|
||||
- Local: http://localhost:3000
|
||||
- Network: http://192.168.0.104:3000
|
||||
- Environments: .env
|
||||
|
||||
✓ Starting...
|
||||
✓ Ready in 823ms
|
||||
○ Compiling /api/graphql ...
|
||||
✓ Compiled /api/graphql in 1234ms
|
||||
🚀 Проверка инициализации базы данных...
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 582ms
|
||||
✨ Инициализация базы данных завершена
|
||||
POST /api/graphql 200 in 3932ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 4435ms
|
||||
POST /api/graphql 200 in 4521ms
|
||||
POST /api/graphql 200 in 1014ms
|
||||
POST /api/graphql 200 in 4627ms
|
||||
POST /api/graphql 200 in 1194ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 776ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 466ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 288ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 716ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 1026ms
|
||||
POST /api/graphql 200 in 605ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
POST /api/graphql 200 in 369ms
|
||||
POST /api/graphql 200 in 782ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 302ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 381ms
|
||||
POST /api/graphql 200 in 389ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 275ms
|
||||
POST /api/graphql 200 in 326ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 738ms
|
||||
POST /api/graphql 200 in 1119ms
|
||||
POST /api/graphql 200 in 1131ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 1185ms
|
||||
POST /api/graphql 200 in 1052ms
|
||||
POST /api/graphql 200 in 1198ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 482ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 286ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 710ms
|
||||
POST /api/graphql 200 in 622ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 601ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
POST /api/graphql 200 in 458ms
|
||||
POST /api/graphql 200 in 1121ms
|
||||
○ Compiling / ...
|
||||
✓ Compiled / in 1642ms
|
||||
GET / 200 in 1852ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 397ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 343ms
|
||||
POST /api/graphql 200 in 341ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 265ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 279ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 1281ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 683ms
|
||||
POST /api/graphql 200 in 589ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 602ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 1193ms
|
||||
POST /api/graphql 200 in 602ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 379ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 267ms
|
||||
○ Compiling /supplies/create-suppliers ...
|
||||
✓ Compiled /supplies/create-suppliers in 580ms
|
||||
GET /supplies/create-suppliers 200 in 797ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
✓ Compiled /favicon.ico in 130ms
|
||||
GET /favicon.ico?favicon.45db1c09.ico 200 in 393ms
|
||||
POST /api/graphql 200 in 413ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
POST /api/graphql 200 in 325ms
|
||||
POST /api/graphql 200 in 499ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 827ms
|
||||
POST /api/graphql 200 in 833ms
|
||||
POST /api/graphql 200 in 629ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 615ms
|
||||
POST /api/graphql 200 in 619ms
|
||||
GET /supplies/create-suppliers 200 in 118ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GET /favicon.ico?favicon.45db1c09.ico 200 in 245ms
|
||||
POST /api/graphql 200 in 371ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
POST /api/graphql 200 in 321ms
|
||||
POST /api/graphql 200 in 335ms
|
||||
POST /api/graphql 200 in 724ms
|
||||
POST /api/graphql 200 in 950ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
🏢 ORGANIZATION_PRODUCTS RESOLVER - ВЫЗВАН: {
|
||||
userId: 'cmdxbhacs0004y52vqs3cu86k',
|
||||
organizationId: 'cmdxbievj000ay52vliq4h23r',
|
||||
search: '',
|
||||
category: '',
|
||||
type: 'ТОВАР',
|
||||
timestamp: '2025-08-04T17:38:34.516Z'
|
||||
}
|
||||
POST /api/graphql 200 in 58ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 387ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 285ms
|
||||
POST /api/graphql 200 in 358ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 285ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 282ms
|
||||
POST /api/graphql 200 in 705ms
|
||||
POST /api/graphql 200 in 818ms
|
||||
POST /api/graphql 200 in 819ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 638ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 602ms
|
||||
POST /api/graphql 200 in 601ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 408ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 385ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 794ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 606ms
|
||||
POST /api/graphql 200 in 692ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
|
||||
POST /api/graphql 200 in 291ms
|
||||
POST /api/graphql 200 in 822ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 273ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 273ms
|
||||
POST /api/graphql 200 in 359ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 295ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
POST /api/graphql 200 in 699ms
|
||||
POST /api/graphql 200 in 695ms
|
||||
POST /api/graphql 200 in 810ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 293ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
|
||||
POST /api/graphql 200 in 1482ms
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
|
||||
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
|
||||
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
|
||||
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
|
||||
POST /api/graphql 200 in 932ms
|
||||
POST /api/graphql 200 in 932ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
○ Compiling /_error ...
|
||||
✓ Compiled /_error in 847ms
|
||||
POST /api/graphql 500 in 1332ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 92ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 70ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 132ms
|
||||
POST /api/graphql 500 in 129ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 118ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 85ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 114ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 138ms
|
||||
POST /api/graphql 500 in 138ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 77ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 143ms
|
||||
POST /api/graphql 500 in 149ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 51ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 95ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 61ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 114ms
|
||||
POST /api/graphql 500 in 119ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 92ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 80ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 105ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 121ms
|
||||
POST /api/graphql 500 in 124ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 93ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 85ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 94ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 141ms
|
||||
POST /api/graphql 500 in 145ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 83ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 91ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 97ms
|
||||
POST /api/graphql 500 in 106ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 113ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 95ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 188ms
|
||||
POST /api/graphql 500 in 188ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 90ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 92ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 77ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 283ms
|
||||
POST /api/graphql 500 in 295ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 222ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 84ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 152ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 101ms
|
||||
POST /api/graphql 500 in 109ms
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
POST /api/graphql 500 in 205ms
|
||||
POST /api/graphql 500 in 60ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ [Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
|
||||
}
|
||||
POST /api/graphql 500 in 336ms
|
||||
POST /api/graphql 500 in 42ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 108ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 115ms
|
||||
POST /api/graphql 500 in 118ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 75ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 65ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 103ms
|
||||
POST /api/graphql 500 in 101ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 86ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 56ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 93ms
|
||||
POST /api/graphql 500 in 95ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 53ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 140ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 98ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 84ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 108ms
|
||||
POST /api/graphql 500 in 110ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 119ms
|
||||
POST /api/graphql 500 in 116ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 94ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 83ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 94ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 113ms
|
||||
POST /api/graphql 500 in 116ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 70ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 160ms
|
||||
POST /api/graphql 500 in 171ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 110ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 119ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 66ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 122ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 115ms
|
||||
POST /api/graphql 500 in 124ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 115ms
|
||||
POST /api/graphql 500 in 117ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 90ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 82ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 79ms
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
⨯ GraphQLError: Syntax Error: Unexpected character: U+0422.
|
||||
at parseDocument (../src/index.ts:96:25)
|
||||
at Module.gql (../src/index.ts:134:9)
|
||||
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
|
||||
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
|
||||
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
|
||||
1 | import { gql } from "graphql-tag";
|
||||
2 |
|
||||
> 3 | export const typeDefs = gql`
|
||||
| ^
|
||||
4 | scalar DateTime
|
||||
5 |
|
||||
6 | type Query { {
|
||||
path: undefined,
|
||||
locations: [Array],
|
||||
extensions: [Object: null prototype] {},
|
||||
page: '/api/graphql'
|
||||
}
|
||||
POST /api/graphql 500 in 106ms
|
||||
POST /api/graphql 500 in 110ms
|
||||
[?25h
|
@ -51,11 +51,22 @@ const handler = startServerAndCreateNextHandler<NextRequest, Context>(server, {
|
||||
prisma,
|
||||
}
|
||||
} else if (decoded.userId && decoded.phone) {
|
||||
// Получаем пользователя с организацией из базы
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: decoded.userId },
|
||||
include: {
|
||||
organization: {
|
||||
select: { id: true, type: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: decoded.userId,
|
||||
user: user ? {
|
||||
id: user.id,
|
||||
phone: decoded.phone,
|
||||
},
|
||||
organizationId: user.organization?.id
|
||||
} : null,
|
||||
admin: null,
|
||||
prisma,
|
||||
}
|
||||
|
@ -9,9 +9,50 @@ import { AuthGuard } from '@/components/auth-guard'
|
||||
function RegisterContent() {
|
||||
const searchParams = useSearchParams()
|
||||
const partnerCode = searchParams.get('partner')
|
||||
const referralCode = searchParams.get('ref')
|
||||
|
||||
console.log('🔍 RegisterContent - URL параметры:', {
|
||||
partnerCode,
|
||||
referralCode,
|
||||
searchParams: Object.fromEntries(searchParams.entries())
|
||||
})
|
||||
|
||||
// Валидация: нельзя использовать оба параметра одновременно
|
||||
if (partnerCode && referralCode) {
|
||||
console.error('Попытка использовать и ref и partner одновременно')
|
||||
redirect('/register') // Редирект на чистую регистрацию
|
||||
return null
|
||||
}
|
||||
|
||||
// Валидация формата кода (10 символов, только разрешенные)
|
||||
const isValidCode = (code: string | null): boolean => {
|
||||
if (!code) return true // null/undefined разрешены
|
||||
return /^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{10}$/.test(code)
|
||||
}
|
||||
|
||||
if (referralCode && !isValidCode(referralCode)) {
|
||||
console.error(`Недействительный реферальный код: ${referralCode}`)
|
||||
redirect('/register')
|
||||
return null
|
||||
}
|
||||
|
||||
if (partnerCode && !isValidCode(partnerCode)) {
|
||||
console.error(`Недействительный партнерский код: ${partnerCode}`)
|
||||
redirect('/register')
|
||||
return null
|
||||
}
|
||||
|
||||
console.log('🚀 RegisterContent - Передача в AuthFlow:', { partnerCode, referralCode })
|
||||
|
||||
// Если есть реферальный или партнерский код, всегда показываем AuthFlow
|
||||
// даже для авторизованных пользователей (для создания дополнительных организаций)
|
||||
if (partnerCode || referralCode) {
|
||||
console.log('🎯 RegisterContent - Принудительный показ AuthFlow из-за наличия кода')
|
||||
return <AuthFlow partnerCode={partnerCode} referralCode={referralCode} />
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthGuard fallback={<AuthFlow partnerCode={partnerCode} />}>
|
||||
<AuthGuard fallback={<AuthFlow partnerCode={partnerCode} referralCode={referralCode} />}>
|
||||
{/* Если пользователь авторизован, перенаправляем в дашборд */}
|
||||
{redirect('/dashboard')}
|
||||
</AuthGuard>
|
||||
|
@ -3,6 +3,8 @@
|
||||
import { CheckCircle } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
|
||||
import { CabinetSelectStep } from './cabinet-select-step'
|
||||
import { ConfirmationStep } from './confirmation-step'
|
||||
import { InnStep } from './inn-step'
|
||||
@ -39,14 +41,31 @@ interface AuthData {
|
||||
ozonApiValidation: ApiKeyValidation | null
|
||||
isAuthenticated: boolean
|
||||
partnerCode?: string | null
|
||||
referralCode?: string | null
|
||||
}
|
||||
|
||||
interface AuthFlowProps {
|
||||
partnerCode?: string | null
|
||||
referralCode?: string | null
|
||||
}
|
||||
|
||||
export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
const [step, setStep] = useState<AuthStep>('phone')
|
||||
export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
const { isAuthenticated, user } = useAuth()
|
||||
|
||||
console.log('🎢 AuthFlow - Полученные props:', { partnerCode, referralCode })
|
||||
console.log('🎢 AuthFlow - Статус авторизации:', { isAuthenticated, hasUser: !!user })
|
||||
|
||||
// Определяем начальный шаг в зависимости от авторизации
|
||||
const initialStep = isAuthenticated ? 'cabinet-select' : 'phone'
|
||||
const [step, setStep] = useState<AuthStep>(initialStep)
|
||||
|
||||
// Определяем тип регистрации на основе параметров
|
||||
// Только один из них должен быть активен (валидация уже прошла в RegisterPage)
|
||||
const registrationType = partnerCode ? 'PARTNER' : (referralCode ? 'REFERRAL' : null)
|
||||
const activeCode = partnerCode || referralCode || null
|
||||
|
||||
console.log('🎢 AuthFlow - Обработанные данные:', { registrationType, activeCode })
|
||||
|
||||
const [authData, setAuthData] = useState<AuthData>({
|
||||
phone: '',
|
||||
smsCode: '',
|
||||
@ -58,9 +77,24 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
ozonApiKey: '',
|
||||
ozonApiValidation: null,
|
||||
isAuthenticated: false,
|
||||
partnerCode: partnerCode,
|
||||
// Сохраняем только активный код в правильное поле
|
||||
partnerCode: registrationType === 'PARTNER' ? activeCode : null,
|
||||
referralCode: registrationType === 'REFERRAL' ? activeCode : null,
|
||||
})
|
||||
|
||||
console.log('🎢 AuthFlow - Сохраненные в authData:', {
|
||||
partnerCode: authData.partnerCode,
|
||||
referralCode: authData.referralCode
|
||||
})
|
||||
|
||||
// Обновляем шаг при изменении статуса авторизации
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && step === 'phone') {
|
||||
console.log('🎢 AuthFlow - Пользователь авторизовался, переход к выбору кабинета')
|
||||
setStep('cabinet-select')
|
||||
}
|
||||
}, [isAuthenticated, step])
|
||||
|
||||
// При завершении авторизации инициируем проверку и перенаправление
|
||||
useEffect(() => {
|
||||
if (step === 'complete') {
|
||||
@ -197,7 +231,13 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{step === 'phone' && <PhoneStep onNext={handlePhoneNext} />}
|
||||
{step === 'phone' && (
|
||||
<PhoneStep
|
||||
onNext={handlePhoneNext}
|
||||
registrationType={registrationType}
|
||||
referrerCode={activeCode}
|
||||
/>
|
||||
)}
|
||||
{step === 'sms' && <SmsStep phone={authData.phone} onNext={handleSmsNext} onBack={handleSmsBack} />}
|
||||
{step === 'cabinet-select' && <CabinetSelectStep onNext={handleCabinetNext} onBack={handleCabinetBack} />}
|
||||
{step === 'inn' && <InnStep onNext={handleInnNext} onBack={handleInnBack} />}
|
||||
@ -208,13 +248,15 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
<ConfirmationStep
|
||||
data={{
|
||||
phone: authData.phone,
|
||||
cabinetType: authData.cabinetType!,
|
||||
cabinetType: authData.cabinetType as 'fulfillment' | 'seller' | 'logist' | 'wholesale',
|
||||
inn: authData.inn || undefined,
|
||||
organizationData: authData.organizationData || undefined,
|
||||
wbApiKey: authData.wbApiKey || undefined,
|
||||
wbApiValidation: authData.wbApiValidation || undefined,
|
||||
ozonApiKey: authData.ozonApiKey || undefined,
|
||||
ozonApiValidation: authData.ozonApiValidation || undefined,
|
||||
referralCode: authData.referralCode,
|
||||
partnerCode: authData.partnerCode,
|
||||
}}
|
||||
onConfirm={handleConfirmation}
|
||||
onBack={handleConfirmationBack}
|
||||
|
@ -33,6 +33,8 @@ interface ConfirmationStepProps {
|
||||
wbApiValidation?: ApiKeyValidation
|
||||
ozonApiKey?: string
|
||||
ozonApiValidation?: ApiKeyValidation
|
||||
referralCode?: string | null
|
||||
partnerCode?: string | null
|
||||
}
|
||||
onConfirm: () => void
|
||||
onBack: () => void
|
||||
@ -66,6 +68,13 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
console.log('📝 ConfirmationStep - Данные для регистрации:', {
|
||||
cabinetType: data.cabinetType,
|
||||
inn: data.inn,
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode
|
||||
})
|
||||
|
||||
try {
|
||||
let result
|
||||
|
||||
@ -73,16 +82,25 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
|
||||
(data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') &&
|
||||
data.inn
|
||||
) {
|
||||
console.log('📝 ConfirmationStep - Вызов registerFulfillmentOrganization с кодами:', {
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode
|
||||
})
|
||||
|
||||
result = await registerFulfillmentOrganization(
|
||||
data.phone.replace(/\D/g, ''),
|
||||
data.inn,
|
||||
getOrganizationType(data.cabinetType),
|
||||
data.referralCode,
|
||||
data.partnerCode,
|
||||
)
|
||||
} else if (data.cabinetType === 'seller') {
|
||||
result = await registerSellerOrganization({
|
||||
phone: data.phone.replace(/\D/g, ''),
|
||||
wbApiKey: data.wbApiKey,
|
||||
ozonApiKey: data.ozonApiKey,
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,11 @@ import { AuthLayout } from './auth-layout'
|
||||
|
||||
interface PhoneStepProps {
|
||||
onNext: (phone: string) => void
|
||||
registrationType?: 'REFERRAL' | 'PARTNER' | null
|
||||
referrerCode?: string | null
|
||||
}
|
||||
|
||||
export function PhoneStep({ onNext }: PhoneStepProps) {
|
||||
export function PhoneStep({ onNext, registrationType, referrerCode }: PhoneStepProps) {
|
||||
const [phone, setPhone] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@ -96,6 +98,35 @@ export function PhoneStep({ onNext }: PhoneStepProps) {
|
||||
totalSteps={5}
|
||||
stepName="Авторизация"
|
||||
>
|
||||
{/* Индикатор типа регистрации */}
|
||||
{registrationType && (
|
||||
<div className="mb-6 p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/20">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg ${
|
||||
registrationType === 'PARTNER'
|
||||
? 'bg-purple-500/20 border border-purple-500/30'
|
||||
: 'bg-blue-500/20 border border-blue-500/30'
|
||||
}`}>
|
||||
<span className="text-xl">
|
||||
{registrationType === 'PARTNER' ? '🤝' : '📎'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-white">
|
||||
{registrationType === 'PARTNER'
|
||||
? 'Регистрация по партнерской ссылке'
|
||||
: 'Регистрация по реферальной ссылке'}
|
||||
</p>
|
||||
<p className="text-xs text-white/60 mt-1">
|
||||
{registrationType === 'PARTNER'
|
||||
? 'Вы получите +100 сфер ⚡ и автоматически станете партнером'
|
||||
: 'Вы получите +100 сфер ⚡ за регистрацию'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone" className="text-white text-sm font-medium flex items-center gap-2">
|
||||
|
@ -10,8 +10,6 @@ import {
|
||||
CreditCard,
|
||||
Key,
|
||||
Edit3,
|
||||
ExternalLink,
|
||||
Copy,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
MessageCircle,
|
||||
@ -53,8 +51,6 @@ export function UserSettings() {
|
||||
type: 'success' | 'error'
|
||||
text: string
|
||||
} | null>(null)
|
||||
const [partnerLink, setPartnerLink] = useState('')
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
|
||||
const [localAvatarUrl, setLocalAvatarUrl] = useState<string | null>(null)
|
||||
const phoneInputRef = useRef<HTMLInputElement | null>(null)
|
||||
@ -278,56 +274,6 @@ export function UserSettings() {
|
||||
const profileStatus = checkProfileCompleteness()
|
||||
const isIncomplete = profileStatus.percentage < 100
|
||||
|
||||
const generatePartnerLink = async () => {
|
||||
if (!user?.id) return
|
||||
|
||||
setIsGenerating(true)
|
||||
setSaveMessage(null)
|
||||
|
||||
try {
|
||||
// Генерируем уникальный код партнера
|
||||
const partnerCode = btoa(user.id + Date.now())
|
||||
.replace(/[^a-zA-Z0-9]/g, '')
|
||||
.substring(0, 12)
|
||||
const link = `${window.location.origin}/register?partner=${partnerCode}`
|
||||
|
||||
setPartnerLink(link)
|
||||
setSaveMessage({
|
||||
type: 'success',
|
||||
text: 'Партнерская ссылка сгенерирована!',
|
||||
})
|
||||
|
||||
// TODO: Сохранить партнерский код в базе данных
|
||||
} catch (error) {
|
||||
console.error('Error generating partner link:', error)
|
||||
setSaveMessage({ type: 'error', text: 'Ошибка при генерации ссылки' })
|
||||
} finally {
|
||||
setIsGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyLink = async () => {
|
||||
if (!partnerLink) {
|
||||
await generatePartnerLink()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(partnerLink)
|
||||
setSaveMessage({ type: 'success', text: 'Ссылка скопирована!' })
|
||||
} catch (error) {
|
||||
console.error('Error copying to clipboard:', error)
|
||||
setSaveMessage({ type: 'error', text: 'Ошибка при копировании' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenLink = async () => {
|
||||
if (!partnerLink) {
|
||||
await generatePartnerLink()
|
||||
return
|
||||
}
|
||||
window.open(partnerLink, '_blank')
|
||||
}
|
||||
|
||||
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
@ -1589,61 +1535,23 @@ export function UserSettings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(user?.organization?.type === 'FULFILLMENT' ||
|
||||
user?.organization?.type === 'LOGIST' ||
|
||||
user?.organization?.type === 'WHOLESALE') && (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="text-white font-medium mb-2">Партнерская программа</h4>
|
||||
<p className="text-white/70 text-sm mb-4">
|
||||
Приглашайте новых контрагентов по уникальной ссылке. При регистрации они автоматически
|
||||
становятся вашими партнерами.
|
||||
<div className="text-center py-12">
|
||||
<Settings className="h-16 w-16 text-white/20 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-white mb-2">
|
||||
Инструменты в разработке
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm max-w-md mx-auto">
|
||||
Здесь будут размещены полезные бизнес-инструменты:
|
||||
калькуляторы, аналитика, планировщики и автоматизация процессов.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="glass-secondary text-white hover:text-white cursor-pointer text-xs px-3 py-2"
|
||||
onClick={generatePartnerLink}
|
||||
disabled={isGenerating}
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 mr-1 ${isGenerating ? 'animate-spin' : ''}`} />
|
||||
{isGenerating ? 'Генерируем...' : 'Сгенерировать ссылку'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{partnerLink && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="glass-secondary text-white hover:text-white cursor-pointer text-xs px-3 py-2"
|
||||
onClick={handleOpenLink}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
Открыть ссылку
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="glass-secondary text-white hover:text-white cursor-pointer px-2"
|
||||
onClick={handleCopyLink}
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-white/60 text-xs">
|
||||
Ваша партнерская ссылка сгенерирована и готова к использованию
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-6">
|
||||
<Badge variant="outline" className="bg-blue-500/20 text-blue-300 border-blue-500/30">
|
||||
Скоро появится
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
@ -15,11 +15,15 @@ import {
|
||||
Mail,
|
||||
MapPin,
|
||||
X,
|
||||
Copy,
|
||||
Gift,
|
||||
} from 'lucide-react'
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { GlassInput } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
@ -29,6 +33,7 @@ import {
|
||||
GET_INCOMING_REQUESTS,
|
||||
GET_OUTGOING_REQUESTS,
|
||||
SEARCH_ORGANIZATIONS,
|
||||
GET_MY_PARTNER_LINK,
|
||||
} from '@/graphql/queries'
|
||||
|
||||
import { OrganizationAvatar } from './organization-avatar'
|
||||
@ -68,6 +73,7 @@ export function MarketCounterparties() {
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS)
|
||||
const { data: outgoingData, loading: outgoingLoading } = useQuery(GET_OUTGOING_REQUESTS)
|
||||
const { data: partnerLinkData } = useQuery(GET_MY_PARTNER_LINK)
|
||||
|
||||
const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, {
|
||||
refetchQueries: [
|
||||
@ -103,6 +109,23 @@ export function MarketCounterparties() {
|
||||
awaitRefetchQueries: true,
|
||||
})
|
||||
|
||||
// Функция копирования партнерской ссылки
|
||||
const copyPartnerLink = async () => {
|
||||
try {
|
||||
const partnerLink = partnerLinkData?.myPartnerLink
|
||||
if (!partnerLink) {
|
||||
toast.error('Партнерская ссылка недоступна')
|
||||
return
|
||||
}
|
||||
await navigator.clipboard.writeText(partnerLink)
|
||||
toast.success('Партнерская ссылка скопирована!', {
|
||||
description: 'Поделитесь ей для прямого делового сотрудничества'
|
||||
})
|
||||
} catch {
|
||||
toast.error('Не удалось скопировать ссылку')
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтрация и сортировка контрагентов
|
||||
const filteredAndSortedCounterparties = useMemo(() => {
|
||||
const filtered = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => {
|
||||
@ -298,6 +321,28 @@ export function MarketCounterparties() {
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="counterparties" className="flex-1 overflow-hidden mt-3 flex flex-col">
|
||||
{/* Блок с партнерской ссылкой */}
|
||||
<Card className="glass-card p-4 mb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-purple-500/20 border border-purple-500/30">
|
||||
<Gift className="h-5 w-5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-white font-medium">Пригласить партнера</h3>
|
||||
<p className="text-white/60 text-sm">Прямое деловое сотрудничество с автоматическим добавлением в партнеры</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={copyPartnerLink}
|
||||
className="glass-button hover:bg-white/20 transition-all duration-200"
|
||||
>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Копировать ссылку
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Компактная панель фильтров */}
|
||||
<div className="glass-card p-3 mb-3 space-y-3">
|
||||
<div className="flex flex-col xl:flex-row gap-3">
|
||||
|
@ -13,6 +13,7 @@ import { MarketFulfillment } from '../market/market-fulfillment'
|
||||
import { MarketLogistics } from '../market/market-logistics'
|
||||
import { MarketSellers } from '../market/market-sellers'
|
||||
import { MarketSuppliers } from '../market/market-suppliers'
|
||||
import { ReferralsTab } from './referrals-tab'
|
||||
|
||||
export function PartnersDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
@ -36,7 +37,7 @@ export function PartnersDashboard() {
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Tabs defaultValue="counterparties" className="h-full flex flex-col">
|
||||
<TabsList
|
||||
className={`grid w-full grid-cols-5 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 ${
|
||||
className={`grid w-full grid-cols-6 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 ${
|
||||
hasIncomingRequests ? 'ring-2 ring-blue-400/50' : ''
|
||||
}`}
|
||||
>
|
||||
@ -75,6 +76,12 @@ export function PartnersDashboard() {
|
||||
>
|
||||
Поставщик
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="referrals"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70"
|
||||
>
|
||||
Рефералы
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="counterparties" className="flex-1 overflow-hidden mt-6">
|
||||
@ -106,6 +113,10 @@ export function PartnersDashboard() {
|
||||
<MarketSuppliers />
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="referrals" className="flex-1 overflow-hidden mt-6">
|
||||
<ReferralsTab />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
420
src/components/partners/referrals-tab.tsx
Normal file
420
src/components/partners/referrals-tab.tsx
Normal file
@ -0,0 +1,420 @@
|
||||
'use client'
|
||||
|
||||
import { useQuery } from '@apollo/client'
|
||||
import {
|
||||
Copy,
|
||||
Gift,
|
||||
Users,
|
||||
TrendingUp,
|
||||
Zap,
|
||||
UserPlus,
|
||||
ShoppingCart,
|
||||
Search,
|
||||
Filter,
|
||||
Calendar,
|
||||
Building,
|
||||
CheckCircle,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { GlassInput } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { GET_REFERRAL_DASHBOARD_DATA } from '@/graphql/referral-queries'
|
||||
|
||||
|
||||
export function ReferralsTab() {
|
||||
console.log('🚀 ReferralsTab COMPONENT RENDERED!')
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [typeFilter, setTypeFilter] = useState<string>('all')
|
||||
const [sourceFilter, setSourceFilter] = useState<string>('all')
|
||||
|
||||
// GraphQL запрос для получения данных
|
||||
const { data, loading, error } = useQuery(GET_REFERRAL_DASHBOARD_DATA, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
errorPolicy: 'all',
|
||||
})
|
||||
|
||||
console.log('🔥 ReferralsTab - useQuery result:', {
|
||||
loading,
|
||||
hasData: !!data,
|
||||
error: error?.message,
|
||||
data
|
||||
})
|
||||
|
||||
// Извлекаем данные из GraphQL ответа или используем fallback для разработки
|
||||
const referralLink = data?.myReferralLink || 'http://localhost:3000/register?ref=LOADING'
|
||||
const stats = data?.myReferralStats || {
|
||||
totalPartners: 0,
|
||||
totalSpheres: 0,
|
||||
monthlyPartners: 0,
|
||||
monthlySpheres: 0,
|
||||
}
|
||||
const allReferrals = useMemo(() => data?.myReferrals?.referrals || [], [data])
|
||||
|
||||
const copyReferralLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(referralLink)
|
||||
toast.success('Реферальная ссылка скопирована!', {
|
||||
description: 'Теперь вы можете поделиться ей с партнерами',
|
||||
})
|
||||
} catch {
|
||||
toast.error('Не удалось скопировать ссылку')
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтрация и поиск
|
||||
const filteredReferrals = useMemo(() => {
|
||||
return allReferrals.filter((referral) => {
|
||||
const matchesSearch =
|
||||
!searchQuery ||
|
||||
referral.organization.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
referral.organization.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
referral.organization.inn.includes(searchQuery)
|
||||
|
||||
const matchesType = typeFilter === 'all' || referral.organization.type === typeFilter
|
||||
const matchesSource = sourceFilter === 'all' || referral.source === sourceFilter
|
||||
|
||||
return matchesSearch && matchesType && matchesSource
|
||||
})
|
||||
}, [allReferrals, searchQuery, typeFilter, sourceFilter])
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case 'SELLER': return 'Селлер'
|
||||
case 'WHOLESALE': return 'Поставщик'
|
||||
case 'FULFILLMENT': return 'Фулфилмент'
|
||||
case 'LOGIST': return 'Логистика'
|
||||
default: return type
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeBadgeStyles = (type: string) => {
|
||||
switch (type) {
|
||||
case 'SELLER': return 'bg-green-500/20 text-green-300 border-green-500/30'
|
||||
case 'WHOLESALE': return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
|
||||
case 'FULFILLMENT': return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
|
||||
case 'LOGIST': return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
|
||||
default: return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
setSearchQuery('')
|
||||
setTypeFilter('all')
|
||||
setSourceFilter('all')
|
||||
}
|
||||
|
||||
const hasActiveFilters = searchQuery || typeFilter !== 'all' || sourceFilter !== 'all'
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col space-y-6">
|
||||
{/* Блок с реферальной ссылкой */}
|
||||
<Card className="glass-card p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
|
||||
<Gift className="h-5 w-5 text-yellow-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white">Ваша реферальная ссылка</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="flex-1 px-4 py-3 glass-input rounded-lg text-white/60 font-mono text-sm">
|
||||
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
|
||||
</div>
|
||||
<Button
|
||||
onClick={copyReferralLink}
|
||||
className="glass-button hover:bg-white/20 transition-all duration-200"
|
||||
>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Копировать ссылку
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-white/60">
|
||||
Поделитесь ссылкой с партнерами и получайте <span className="text-yellow-400 font-medium">100 сфер ⚡</span> за каждую регистрацию.
|
||||
Также получайте <span className="text-yellow-400 font-medium">50 сфер ⚡</span> при одобрении заявок от новых клиентов.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Статистика */}
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 rounded-lg bg-blue-500/20 border border-blue-500/30">
|
||||
<Users className="h-4 w-4 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/60 uppercase tracking-wide">Всего партнеров</p>
|
||||
<p className="text-2xl font-bold text-white">
|
||||
{loading ? (
|
||||
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
|
||||
) : (
|
||||
stats.totalPartners
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/60 uppercase tracking-wide">Сфер заработано</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-2xl font-bold text-white">
|
||||
{loading ? (
|
||||
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
|
||||
) : (
|
||||
stats.totalSpheres
|
||||
)}
|
||||
</p>
|
||||
<Zap className="h-5 w-5 text-yellow-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 rounded-lg bg-green-500/20 border border-green-500/30">
|
||||
<TrendingUp className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/60 uppercase tracking-wide">Партнеров за месяц</p>
|
||||
<p className="text-2xl font-bold text-white">
|
||||
{loading ? (
|
||||
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
|
||||
) : (
|
||||
stats.monthlyPartners
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/60 uppercase tracking-wide">Сфер за месяц</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="text-2xl font-bold text-white">
|
||||
{loading ? (
|
||||
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
|
||||
) : (
|
||||
stats.monthlySpheres
|
||||
)}
|
||||
</p>
|
||||
<Zap className="h-5 w-5 text-yellow-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Фильтры */}
|
||||
<Card className="glass-card p-4">
|
||||
<div className="flex flex-col xl:flex-row gap-4">
|
||||
{/* Поиск */}
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
|
||||
<GlassInput
|
||||
placeholder="Поиск по названию или ИНН..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Фильтры */}
|
||||
<div className="flex gap-2">
|
||||
<Select value={typeFilter} onValueChange={setTypeFilter}>
|
||||
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[130px]">
|
||||
<Filter className="h-3 w-3 mr-1" />
|
||||
<SelectValue placeholder="Тип" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="all">Все типы</SelectItem>
|
||||
<SelectItem value="SELLER">Селлер</SelectItem>
|
||||
<SelectItem value="WHOLESALE">Поставщик</SelectItem>
|
||||
<SelectItem value="FULFILLMENT">Фулфилмент</SelectItem>
|
||||
<SelectItem value="LOGIST">Логистика</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={sourceFilter} onValueChange={setSourceFilter}>
|
||||
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[140px]">
|
||||
<Filter className="h-3 w-3 mr-1" />
|
||||
<SelectValue placeholder="Источник" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="all">Все источники</SelectItem>
|
||||
<SelectItem value="REFERRAL_LINK">Реферальная ссылка</SelectItem>
|
||||
<SelectItem value="AUTO_BUSINESS">Бизнес-сделка</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{hasActiveFilters && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10 h-9 w-9 p-0"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Краткая статистика */}
|
||||
<div className="flex items-center justify-between mt-3 text-xs">
|
||||
<div className="text-white/60">
|
||||
Показано {filteredReferrals.length} из {allReferrals.length} партнеров
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-blue-500/10 text-blue-300">
|
||||
<UserPlus className="h-3 w-3" />
|
||||
<span>Рефералы: {allReferrals.filter(r => r.source === 'REFERRAL_LINK').length}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-orange-500/10 text-orange-300">
|
||||
<ShoppingCart className="h-3 w-3" />
|
||||
<span>Бизнес: {allReferrals.filter(r => r.source === 'AUTO_BUSINESS').length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Таблица партнеров */}
|
||||
<Card className="glass-card flex-1 overflow-hidden">
|
||||
<div className="h-full overflow-auto">
|
||||
<div className="p-6 space-y-3">
|
||||
{/* Заголовок таблицы */}
|
||||
<div className="p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/10">
|
||||
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-white/80">
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-blue-400" />
|
||||
<span>Дата регистрации</span>
|
||||
</div>
|
||||
<div className="col-span-3 flex items-center gap-2">
|
||||
<Building className="h-4 w-4 text-green-400" />
|
||||
<span>Организация</span>
|
||||
</div>
|
||||
<div className="col-span-1 text-center flex items-center justify-center">
|
||||
<span>Тип</span>
|
||||
</div>
|
||||
<div className="col-span-2 text-center flex items-center justify-center gap-1">
|
||||
<UserPlus className="h-4 w-4 text-purple-400" />
|
||||
<span>Источник</span>
|
||||
</div>
|
||||
<div className="col-span-2 text-center flex items-center justify-center gap-1">
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
<span>Начислено</span>
|
||||
</div>
|
||||
<div className="col-span-2 text-center flex items-center justify-center">
|
||||
<span>Статус</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Строки партнеров */}
|
||||
{filteredReferrals.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64">
|
||||
<Gift className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">
|
||||
{loading ? 'Загрузка...' : allReferrals.length === 0 ? 'У вас пока нет партнеров' : 'Ничего не найдено'}
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mt-1">
|
||||
{loading
|
||||
? 'Получаем данные о ваших партнерах...'
|
||||
: allReferrals.length === 0
|
||||
? 'Поделитесь реферальной ссылкой или начните работать с клиентами'
|
||||
: 'Попробуйте изменить параметры поиска'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredReferrals.map((referral) => (
|
||||
<div key={referral.id} className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10">
|
||||
<div className="grid grid-cols-12 gap-4 items-center">
|
||||
<div className="col-span-2 text-white/80">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-3 w-3 text-white/40" />
|
||||
<span className="text-sm">{formatDate(referral.registeredAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<div>
|
||||
<p className="text-white font-medium text-sm">
|
||||
{referral.organization.name || referral.organization.fullName}
|
||||
</p>
|
||||
<p className="text-white/60 text-xs flex items-center gap-1">
|
||||
<Building className="h-3 w-3" />
|
||||
{referral.organization.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<Badge className={getTypeBadgeStyles(referral.organization.type) + ' text-xs'}>
|
||||
{getTypeLabel(referral.organization.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="col-span-2 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{referral.source === 'REFERRAL_LINK' ? (
|
||||
<>
|
||||
<UserPlus className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-blue-300 text-sm">Ссылка</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ShoppingCart className="h-4 w-4 text-orange-400" />
|
||||
<span className="text-orange-300 text-sm">Бизнес</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span className="text-green-400 font-semibold">+{referral.spheresEarned}</span>
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<span className="text-green-300 text-sm">Активен</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -3,6 +3,8 @@ import { PrismaClient } from '@prisma/client'
|
||||
export interface Context {
|
||||
user: {
|
||||
id: string
|
||||
phone?: string
|
||||
organizationId?: string
|
||||
organization?: {
|
||||
id: string
|
||||
type: string
|
||||
|
@ -115,6 +115,7 @@ export const REGISTER_FULFILLMENT_ORGANIZATION = gql`
|
||||
marketplace
|
||||
isActive
|
||||
}
|
||||
referralPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,6 +164,7 @@ export const REGISTER_SELLER_ORGANIZATION = gql`
|
||||
marketplace
|
||||
isActive
|
||||
}
|
||||
referralPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1232,3 +1232,19 @@ export const GET_FULFILLMENT_WAREHOUSE_STATS = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Запрос партнерской ссылки
|
||||
export const GET_MY_PARTNER_LINK = gql`
|
||||
query GetMyPartnerLink {
|
||||
myPartnerLink
|
||||
}
|
||||
`
|
||||
|
||||
// Экспорт реферальных запросов
|
||||
export {
|
||||
GET_MY_REFERRAL_LINK,
|
||||
GET_MY_REFERRAL_STATS,
|
||||
GET_MY_REFERRALS,
|
||||
GET_MY_REFERRAL_TRANSACTIONS,
|
||||
GET_REFERRAL_DASHBOARD_DATA,
|
||||
} from './referral-queries'
|
||||
|
135
src/graphql/referral-queries.ts
Normal file
135
src/graphql/referral-queries.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
// Получение реферальной ссылки
|
||||
export const GET_MY_REFERRAL_LINK = gql`
|
||||
query GetMyReferralLink {
|
||||
myReferralLink
|
||||
}
|
||||
`
|
||||
|
||||
// Получение статистики по рефералам
|
||||
export const GET_MY_REFERRAL_STATS = gql`
|
||||
query GetMyReferralStats {
|
||||
myReferralStats {
|
||||
totalPartners
|
||||
totalSpheres
|
||||
monthlyPartners
|
||||
monthlySpheres
|
||||
referralsByType {
|
||||
type
|
||||
count
|
||||
spheres
|
||||
}
|
||||
referralsBySource {
|
||||
source
|
||||
count
|
||||
spheres
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Получение списка рефералов
|
||||
export const GET_MY_REFERRALS = gql`
|
||||
query GetMyReferrals(
|
||||
$dateFrom: DateTime
|
||||
$dateTo: DateTime
|
||||
$type: OrganizationType
|
||||
$source: ReferralSource
|
||||
$search: String
|
||||
$limit: Int
|
||||
$offset: Int
|
||||
) {
|
||||
myReferrals(
|
||||
dateFrom: $dateFrom
|
||||
dateTo: $dateTo
|
||||
type: $type
|
||||
source: $source
|
||||
search: $search
|
||||
limit: $limit
|
||||
offset: $offset
|
||||
) {
|
||||
referrals {
|
||||
id
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
inn
|
||||
type
|
||||
createdAt
|
||||
}
|
||||
source
|
||||
spheresEarned
|
||||
registeredAt
|
||||
status
|
||||
}
|
||||
totalCount
|
||||
totalPages
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Получение истории транзакций
|
||||
export const GET_MY_REFERRAL_TRANSACTIONS = gql`
|
||||
query GetMyReferralTransactions($limit: Int, $offset: Int) {
|
||||
myReferralTransactions(limit: $limit, offset: $offset) {
|
||||
transactions {
|
||||
id
|
||||
spheres
|
||||
type
|
||||
description
|
||||
createdAt
|
||||
referral {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
inn
|
||||
type
|
||||
}
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Получение данных для дашборда рефералов (комбинированный запрос)
|
||||
export const GET_REFERRAL_DASHBOARD_DATA = gql`
|
||||
query GetReferralDashboardData {
|
||||
myReferralLink
|
||||
myReferralStats {
|
||||
totalPartners
|
||||
totalSpheres
|
||||
monthlyPartners
|
||||
monthlySpheres
|
||||
referralsByType {
|
||||
type
|
||||
count
|
||||
spheres
|
||||
}
|
||||
referralsBySource {
|
||||
source
|
||||
count
|
||||
spheres
|
||||
}
|
||||
}
|
||||
myReferrals(limit: 50) {
|
||||
referrals {
|
||||
id
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
inn
|
||||
type
|
||||
createdAt
|
||||
}
|
||||
source
|
||||
spheresEarned
|
||||
registeredAt
|
||||
status
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
@ -9,13 +9,41 @@ import { MarketplaceService } from '@/services/marketplace-service'
|
||||
import { SmsService } from '@/services/sms-service'
|
||||
import { WildberriesService } from '@/services/wildberries-service'
|
||||
|
||||
import '@/lib/seed-init'; // Автоматическая инициализация БД
|
||||
import '@/lib/seed-init' // Автоматическая инициализация БД
|
||||
|
||||
// Сервисы
|
||||
const smsService = new SmsService()
|
||||
const dadataService = new DaDataService()
|
||||
const marketplaceService = new MarketplaceService()
|
||||
|
||||
// Функция генерации уникального реферального кода
|
||||
const generateReferralCode = async (): Promise<string> => {
|
||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||
let attempts = 0
|
||||
const maxAttempts = 10
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
let code = ''
|
||||
for (let i = 0; i < 10; i++) {
|
||||
code += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
|
||||
// Проверяем уникальность
|
||||
const existing = await prisma.organization.findUnique({
|
||||
where: { referralCode: code }
|
||||
})
|
||||
|
||||
if (!existing) {
|
||||
return code
|
||||
}
|
||||
|
||||
attempts++
|
||||
}
|
||||
|
||||
// Если не удалось сгенерировать уникальный код, используем cuid как fallback
|
||||
return `REF${Date.now()}${Math.random().toString(36).substr(2, 5).toUpperCase()}`
|
||||
}
|
||||
|
||||
// Интерфейсы для типизации
|
||||
interface Context {
|
||||
user?: {
|
||||
@ -2023,6 +2051,53 @@ export const resolvers = {
|
||||
|
||||
return scheduleRecords
|
||||
},
|
||||
|
||||
// Получить партнерскую ссылку текущего пользователя
|
||||
myPartnerLink: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user?.organizationId) {
|
||||
throw new GraphQLError('Требуется авторизация и организация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: { id: context.user.organizationId },
|
||||
select: { referralCode: true }
|
||||
})
|
||||
|
||||
if (!organization?.referralCode) {
|
||||
throw new GraphQLError('Реферальный код не найден')
|
||||
}
|
||||
|
||||
return `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?partner=${organization.referralCode}`
|
||||
},
|
||||
|
||||
// ВРЕМЕННЫЙ myReferralLink для отладки
|
||||
myReferralLink: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log('🔥 OLD RESOLVER - myReferralLink called!')
|
||||
|
||||
if (!context.user?.organizationId) {
|
||||
console.log('❌ OLD RESOLVER - NO organizationId!')
|
||||
throw new GraphQLError('Требуется авторизация и организация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: { id: context.user.organizationId },
|
||||
select: { referralCode: true }
|
||||
})
|
||||
|
||||
if (!organization?.referralCode) {
|
||||
console.log('❌ OLD RESOLVER - NO referralCode!')
|
||||
throw new GraphQLError('Реферальный код не найден')
|
||||
}
|
||||
|
||||
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?ref=${organization.referralCode}`
|
||||
console.log('✅ OLD RESOLVER - Generated link:', link)
|
||||
|
||||
return link
|
||||
},
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@ -2139,17 +2214,27 @@ export const resolvers = {
|
||||
phone: string
|
||||
inn: string
|
||||
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE'
|
||||
referralCode?: string
|
||||
partnerCode?: string
|
||||
}
|
||||
},
|
||||
context: Context,
|
||||
) => {
|
||||
console.log('🚀 registerFulfillmentOrganization called with:', {
|
||||
inn: args.input.inn,
|
||||
type: args.input.type,
|
||||
referralCode: args.input.referralCode,
|
||||
partnerCode: args.input.partnerCode,
|
||||
userId: context.user?.id
|
||||
})
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
const { inn, type } = args.input
|
||||
const { inn, type, referralCode, partnerCode } = args.input
|
||||
|
||||
// Валидируем ИНН
|
||||
if (!dadataService.validateInn(inn)) {
|
||||
@ -2181,6 +2266,9 @@ export const resolvers = {
|
||||
}
|
||||
}
|
||||
|
||||
// Генерируем уникальный реферальный код
|
||||
const generatedReferralCode = await generateReferralCode()
|
||||
|
||||
// Создаем организацию со всеми данными из DaData
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
@ -2225,6 +2313,9 @@ export const resolvers = {
|
||||
|
||||
type: type,
|
||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)),
|
||||
|
||||
// Реферальная система - генерируем код автоматически
|
||||
referralCode: generatedReferralCode,
|
||||
},
|
||||
})
|
||||
|
||||
@ -2241,6 +2332,106 @@ export const resolvers = {
|
||||
},
|
||||
})
|
||||
|
||||
// Обрабатываем реферальные коды
|
||||
if (referralCode) {
|
||||
try {
|
||||
// Находим реферера по реферальному коду
|
||||
const referrer = await prisma.organization.findUnique({
|
||||
where: { referralCode: referralCode }
|
||||
})
|
||||
|
||||
if (referrer) {
|
||||
// Создаем реферальную транзакцию (100 сфер)
|
||||
await prisma.referralTransaction.create({
|
||||
data: {
|
||||
referrerId: referrer.id,
|
||||
referralId: organization.id,
|
||||
points: 100,
|
||||
type: 'REGISTRATION',
|
||||
description: `Регистрация ${type.toLowerCase()} организации по реферальной ссылке`
|
||||
}
|
||||
})
|
||||
|
||||
// Увеличиваем счетчик сфер у реферера
|
||||
await prisma.organization.update({
|
||||
where: { id: referrer.id },
|
||||
data: { referralPoints: { increment: 100 } }
|
||||
})
|
||||
|
||||
// Устанавливаем связь реферала
|
||||
await prisma.organization.update({
|
||||
where: { id: organization.id },
|
||||
data: { referredById: referrer.id }
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error processing referral code:', error)
|
||||
// Не прерываем регистрацию из-за ошибки реферальной системы
|
||||
}
|
||||
}
|
||||
|
||||
if (partnerCode) {
|
||||
try {
|
||||
console.log(`🔍 Processing partner code: ${partnerCode}`)
|
||||
|
||||
// Находим партнера по партнерскому коду
|
||||
const partner = await prisma.organization.findUnique({
|
||||
where: { referralCode: partnerCode }
|
||||
})
|
||||
|
||||
console.log(`🏢 Partner found:`, partner ? `${partner.name} (${partner.id})` : 'NOT FOUND')
|
||||
|
||||
if (partner) {
|
||||
// Создаем реферальную транзакцию (100 сфер)
|
||||
await prisma.referralTransaction.create({
|
||||
data: {
|
||||
referrerId: partner.id,
|
||||
referralId: organization.id,
|
||||
points: 100,
|
||||
type: 'AUTO_PARTNERSHIP',
|
||||
description: `Регистрация ${type.toLowerCase()} организации по партнерской ссылке`
|
||||
}
|
||||
})
|
||||
|
||||
// Увеличиваем счетчик сфер у партнера
|
||||
await prisma.organization.update({
|
||||
where: { id: partner.id },
|
||||
data: { referralPoints: { increment: 100 } }
|
||||
})
|
||||
|
||||
// Устанавливаем связь реферала
|
||||
await prisma.organization.update({
|
||||
where: { id: organization.id },
|
||||
data: { referredById: partner.id }
|
||||
})
|
||||
|
||||
// Создаем партнерскую связь (автоматическое добавление в контрагенты)
|
||||
await prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: partner.id,
|
||||
counterpartyId: organization.id,
|
||||
type: 'AUTO',
|
||||
triggeredBy: 'PARTNER_LINK'
|
||||
}
|
||||
})
|
||||
|
||||
await prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
counterpartyId: partner.id,
|
||||
type: 'AUTO',
|
||||
triggeredBy: 'PARTNER_LINK'
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`✅ Partnership created: ${organization.name} <-> ${partner.name}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error processing partner code:', error)
|
||||
// Не прерываем регистрацию из-за ошибки партнерской системы
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Организация успешно зарегистрирована',
|
||||
@ -2263,17 +2454,28 @@ export const resolvers = {
|
||||
wbApiKey?: string
|
||||
ozonApiKey?: string
|
||||
ozonClientId?: string
|
||||
referralCode?: string
|
||||
partnerCode?: string
|
||||
}
|
||||
},
|
||||
context: Context,
|
||||
) => {
|
||||
console.log('🚀 registerSellerOrganization called with:', {
|
||||
phone: args.input.phone,
|
||||
hasWbApiKey: !!args.input.wbApiKey,
|
||||
hasOzonApiKey: !!args.input.ozonApiKey,
|
||||
referralCode: args.input.referralCode,
|
||||
partnerCode: args.input.partnerCode,
|
||||
userId: context.user?.id
|
||||
})
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
const { wbApiKey, ozonApiKey, ozonClientId } = args.input
|
||||
const { wbApiKey, ozonApiKey, ozonClientId, referralCode, partnerCode } = args.input
|
||||
|
||||
if (!wbApiKey && !ozonApiKey) {
|
||||
return {
|
||||
@ -2321,12 +2523,18 @@ export const resolvers = {
|
||||
const sellerName = validationResults[0]?.data?.sellerName
|
||||
const shopName = tradeMark || sellerName || 'Магазин'
|
||||
|
||||
// Генерируем уникальный реферальный код
|
||||
const generatedReferralCode = await generateReferralCode()
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
inn: (validationResults[0]?.data?.inn as string) || `SELLER_${Date.now()}`,
|
||||
name: shopName, // Используем tradeMark как основное название
|
||||
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
|
||||
type: 'SELLER',
|
||||
|
||||
// Реферальная система - генерируем код автоматически
|
||||
referralCode: generatedReferralCode,
|
||||
},
|
||||
})
|
||||
|
||||
@ -2355,6 +2563,106 @@ export const resolvers = {
|
||||
},
|
||||
})
|
||||
|
||||
// Обрабатываем реферальные коды
|
||||
if (referralCode) {
|
||||
try {
|
||||
// Находим реферера по реферальному коду
|
||||
const referrer = await prisma.organization.findUnique({
|
||||
where: { referralCode: referralCode }
|
||||
})
|
||||
|
||||
if (referrer) {
|
||||
// Создаем реферальную транзакцию (100 сфер)
|
||||
await prisma.referralTransaction.create({
|
||||
data: {
|
||||
referrerId: referrer.id,
|
||||
referralId: organization.id,
|
||||
points: 100,
|
||||
type: 'REGISTRATION',
|
||||
description: 'Регистрация селлер организации по реферальной ссылке'
|
||||
}
|
||||
})
|
||||
|
||||
// Увеличиваем счетчик сфер у реферера
|
||||
await prisma.organization.update({
|
||||
where: { id: referrer.id },
|
||||
data: { referralPoints: { increment: 100 } }
|
||||
})
|
||||
|
||||
// Устанавливаем связь реферала
|
||||
await prisma.organization.update({
|
||||
where: { id: organization.id },
|
||||
data: { referredById: referrer.id }
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error processing referral code:', error)
|
||||
// Не прерываем регистрацию из-за ошибки реферальной системы
|
||||
}
|
||||
}
|
||||
|
||||
if (partnerCode) {
|
||||
try {
|
||||
console.log(`🔍 Processing partner code: ${partnerCode}`)
|
||||
|
||||
// Находим партнера по партнерскому коду
|
||||
const partner = await prisma.organization.findUnique({
|
||||
where: { referralCode: partnerCode }
|
||||
})
|
||||
|
||||
console.log(`🏢 Partner found:`, partner ? `${partner.name} (${partner.id})` : 'NOT FOUND')
|
||||
|
||||
if (partner) {
|
||||
// Создаем реферальную транзакцию (100 сфер)
|
||||
await prisma.referralTransaction.create({
|
||||
data: {
|
||||
referrerId: partner.id,
|
||||
referralId: organization.id,
|
||||
points: 100,
|
||||
type: 'AUTO_PARTNERSHIP',
|
||||
description: 'Регистрация селлер организации по партнерской ссылке'
|
||||
}
|
||||
})
|
||||
|
||||
// Увеличиваем счетчик сфер у партнера
|
||||
await prisma.organization.update({
|
||||
where: { id: partner.id },
|
||||
data: { referralPoints: { increment: 100 } }
|
||||
})
|
||||
|
||||
// Устанавливаем связь реферала
|
||||
await prisma.organization.update({
|
||||
where: { id: organization.id },
|
||||
data: { referredById: partner.id }
|
||||
})
|
||||
|
||||
// Создаем партнерскую связь (автоматическое добавление в контрагенты)
|
||||
await prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: partner.id,
|
||||
counterpartyId: organization.id,
|
||||
type: 'AUTO',
|
||||
triggeredBy: 'PARTNER_LINK'
|
||||
}
|
||||
})
|
||||
|
||||
await prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
counterpartyId: partner.id,
|
||||
type: 'AUTO',
|
||||
triggeredBy: 'PARTNER_LINK'
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`✅ Partnership created: ${organization.name} <-> ${partner.name}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error processing partner code:', error)
|
||||
// Не прерываем регистрацию из-за ошибки партнерской системы
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Селлер организация успешно зарегистрирована',
|
||||
|
@ -5,6 +5,7 @@ import { authResolvers } from './auth'
|
||||
import { employeeResolvers } from './employees'
|
||||
import { logisticsResolvers } from './logistics'
|
||||
import { suppliesResolvers } from './supplies'
|
||||
import { referralResolvers } from './referrals'
|
||||
|
||||
// Типы для резолверов
|
||||
interface ResolverObject {
|
||||
@ -22,6 +23,7 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
|
||||
|
||||
for (const resolver of resolvers) {
|
||||
if (resolver?.Query) {
|
||||
console.log('🔀 MERGING QUERY RESOLVERS:', Object.keys(resolver.Query))
|
||||
Object.assign(result.Query, resolver.Query)
|
||||
}
|
||||
if (resolver?.Mutation) {
|
||||
@ -40,6 +42,7 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ FINAL MERGED Query RESOLVERS:', Object.keys(result.Query || {}))
|
||||
return result
|
||||
}
|
||||
|
||||
@ -47,35 +50,26 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
|
||||
// TODO: Постепенно убрать это после полного рефакторинга
|
||||
|
||||
// Объединяем новые модульные резолверы с остальными старыми
|
||||
export const resolvers = mergeResolvers(
|
||||
const mergedResolvers = mergeResolvers(
|
||||
// Скалярные типы
|
||||
{
|
||||
JSON: JSONScalar,
|
||||
DateTime: DateTimeScalar,
|
||||
},
|
||||
|
||||
// Новые модульные резолверы
|
||||
authResolvers,
|
||||
employeeResolvers,
|
||||
logisticsResolvers,
|
||||
suppliesResolvers,
|
||||
|
||||
// Временно добавляем старые резолверы, исключая уже вынесенные
|
||||
// Временно добавляем старые резолверы ПЕРВЫМИ, чтобы новые их перезаписали
|
||||
{
|
||||
Query: {
|
||||
...oldResolvers.Query,
|
||||
// Исключаем уже вынесенные Query
|
||||
myEmployees: undefined,
|
||||
logisticsPartners: undefined,
|
||||
pendingSuppliesCount: undefined,
|
||||
},
|
||||
Query: (() => {
|
||||
const { myEmployees, logisticsPartners, pendingSuppliesCount, myReferralLink, myPartnerLink, myReferralStats, myReferrals, ...filteredQuery } = oldResolvers.Query || {}
|
||||
return filteredQuery
|
||||
})(),
|
||||
Mutation: {
|
||||
...oldResolvers.Mutation,
|
||||
// Исключаем уже вынесенные Mutation
|
||||
sendSmsCode: undefined,
|
||||
verifySmsCode: undefined,
|
||||
// verifySmsCode: undefined, // НЕ исключаем - пока в старых резолверах
|
||||
verifyInn: undefined,
|
||||
registerFulfillmentOrganization: undefined,
|
||||
// registerFulfillmentOrganization: undefined, // НЕ исключаем - резолвер нужен!
|
||||
createEmployee: undefined,
|
||||
updateEmployee: undefined,
|
||||
deleteEmployee: undefined,
|
||||
@ -91,4 +85,18 @@ export const resolvers = mergeResolvers(
|
||||
// Employee берем из нового модуля
|
||||
Employee: undefined,
|
||||
},
|
||||
|
||||
// НОВЫЕ модульные резолверы ПОСЛЕ старых - чтобы они перезаписали старые
|
||||
authResolvers,
|
||||
employeeResolvers,
|
||||
logisticsResolvers,
|
||||
suppliesResolvers,
|
||||
referralResolvers,
|
||||
)
|
||||
|
||||
// Добавляем debug логирование для проверки резолверов
|
||||
console.log('🔍 DEBUG: referralResolvers.Query keys:', Object.keys(referralResolvers.Query || {}))
|
||||
console.log('🔍 DEBUG: mergedResolvers.Query has myReferralStats:', 'myReferralStats' in (mergedResolvers.Query || {}))
|
||||
console.log('🔍 DEBUG: mergedResolvers.Query.myReferralStats type:', typeof mergedResolvers.Query?.myReferralStats)
|
||||
|
||||
export const resolvers = mergedResolvers
|
||||
|
203
src/graphql/resolvers/referrals.ts
Normal file
203
src/graphql/resolvers/referrals.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
interface Context {
|
||||
user: {
|
||||
id: string
|
||||
phone?: string
|
||||
organizationId?: string
|
||||
organization?: {
|
||||
id: string
|
||||
type: string
|
||||
}
|
||||
} | null
|
||||
}
|
||||
|
||||
export const referralResolvers = {
|
||||
Query: {
|
||||
// Тестовый резолвер для проверки подключения
|
||||
testReferral: () => {
|
||||
console.log('🔥 TEST REFERRAL RESOLVER WORKS!')
|
||||
return 'TEST OK'
|
||||
},
|
||||
|
||||
// Простой тест резолвер для отладки
|
||||
debugTest: () => {
|
||||
console.log('🔥 DEBUG TEST RESOLVER CALLED!')
|
||||
return 'DEBUG OK'
|
||||
},
|
||||
|
||||
// Получить реферальную ссылку текущего пользователя
|
||||
myReferralLink: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log('🔥 REFERRAL RESOLVER CALLED!')
|
||||
console.log('🔥 Process env APP_URL:', process.env.NEXT_PUBLIC_APP_URL)
|
||||
console.log('🔗 myReferralLink DEBUG - context.user:', context.user)
|
||||
|
||||
if (!context.user?.organizationId) {
|
||||
console.log('❌ myReferralLink DEBUG - NO organizationId! Returning placeholder')
|
||||
return 'http://localhost:3000/register?ref=PLEASE_LOGIN'
|
||||
}
|
||||
|
||||
console.log('🔍 myReferralLink DEBUG - Looking for organization:', context.user.organizationId)
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: { id: context.user.organizationId },
|
||||
select: { referralCode: true }
|
||||
})
|
||||
|
||||
console.log('🏢 myReferralLink DEBUG - Found organization:', organization)
|
||||
|
||||
if (!organization?.referralCode) {
|
||||
console.log('❌ myReferralLink DEBUG - NO referralCode!')
|
||||
throw new GraphQLError('Реферальный код не найден')
|
||||
}
|
||||
|
||||
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?ref=${organization.referralCode}`
|
||||
console.log('✅ myReferralLink DEBUG - Generated link:', link)
|
||||
|
||||
// Гарантированно возвращаем строку, не null
|
||||
return link || 'http://localhost:3000/register?ref=ERROR'
|
||||
},
|
||||
|
||||
// Получить партнерскую ссылку текущего пользователя
|
||||
myPartnerLink: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log('🔗 myPartnerLink DEBUG - context.user:', context.user)
|
||||
|
||||
if (!context.user?.organizationId) {
|
||||
console.log('❌ myPartnerLink DEBUG - NO organizationId!')
|
||||
throw new GraphQLError('Требуется авторизация и организация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
console.log('🔍 myPartnerLink DEBUG - Looking for organization:', context.user.organizationId)
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: { id: context.user.organizationId },
|
||||
select: { referralCode: true }
|
||||
})
|
||||
|
||||
console.log('🏢 myPartnerLink DEBUG - Found organization:', organization)
|
||||
|
||||
if (!organization?.referralCode) {
|
||||
console.log('❌ myPartnerLink DEBUG - NO referralCode!')
|
||||
throw new GraphQLError('Реферальный код не найден')
|
||||
}
|
||||
|
||||
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?partner=${organization.referralCode}`
|
||||
console.log('✅ myPartnerLink DEBUG - Generated link:', link)
|
||||
|
||||
return link
|
||||
},
|
||||
|
||||
// Получить статистику по рефералам
|
||||
myReferralStats: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log('🔥🔥🔥 NEW myReferralStats RESOLVER CALLED!')
|
||||
console.log('🔗 myReferralStats DEBUG - context.user:', context.user)
|
||||
|
||||
try {
|
||||
// Если пользователь не авторизован, возвращаем дефолтные значения
|
||||
if (!context.user?.organizationId) {
|
||||
console.log('❌ myReferralStats DEBUG - NO USER OR organizationId!')
|
||||
const defaultResult = {
|
||||
totalPartners: 0,
|
||||
totalSpheres: 0,
|
||||
monthlyPartners: 0,
|
||||
monthlySpheres: 0,
|
||||
referralsByType: [
|
||||
{ type: 'SELLER', count: 0, spheres: 0 },
|
||||
{ type: 'WHOLESALE', count: 0, spheres: 0 },
|
||||
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
|
||||
{ type: 'LOGIST', count: 0, spheres: 0 }
|
||||
],
|
||||
referralsBySource: [
|
||||
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
|
||||
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
|
||||
]
|
||||
}
|
||||
console.log('✅ myReferralStats DEBUG - returning default result for unauth user:', defaultResult)
|
||||
return defaultResult
|
||||
}
|
||||
|
||||
// TODO: Реальная логика подсчета статистики
|
||||
const result = {
|
||||
totalPartners: 0,
|
||||
totalSpheres: 0,
|
||||
monthlyPartners: 0,
|
||||
monthlySpheres: 0,
|
||||
referralsByType: [
|
||||
{ type: 'SELLER', count: 0, spheres: 0 },
|
||||
{ type: 'WHOLESALE', count: 0, spheres: 0 },
|
||||
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
|
||||
{ type: 'LOGIST', count: 0, spheres: 0 }
|
||||
],
|
||||
referralsBySource: [
|
||||
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
|
||||
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
|
||||
]
|
||||
}
|
||||
console.log('✅ myReferralStats DEBUG - returning result:', result)
|
||||
return result
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ myReferralStats ERROR:', error)
|
||||
// В случае ошибки всегда возвращаем валидную структуру
|
||||
const fallbackResult = {
|
||||
totalPartners: 0,
|
||||
totalSpheres: 0,
|
||||
monthlyPartners: 0,
|
||||
monthlySpheres: 0,
|
||||
referralsByType: [
|
||||
{ type: 'SELLER', count: 0, spheres: 0 },
|
||||
{ type: 'WHOLESALE', count: 0, spheres: 0 },
|
||||
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
|
||||
{ type: 'LOGIST', count: 0, spheres: 0 }
|
||||
],
|
||||
referralsBySource: [
|
||||
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
|
||||
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
|
||||
]
|
||||
}
|
||||
console.log('✅ myReferralStats DEBUG - returning fallback result after error:', fallbackResult)
|
||||
return fallbackResult
|
||||
}
|
||||
},
|
||||
|
||||
// Получить список рефералов
|
||||
myReferrals: async (_: unknown, args: any, context: Context) => {
|
||||
if (!context.user?.organizationId) {
|
||||
throw new GraphQLError('Требуется авторизация и организация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
const referrals = await prisma.organization.findMany({
|
||||
where: { referredById: context.user.organizationId },
|
||||
include: {
|
||||
referralTransactions: {
|
||||
where: { referrerId: context.user.organizationId }
|
||||
}
|
||||
},
|
||||
take: args.limit || 50,
|
||||
skip: args.offset || 0
|
||||
})
|
||||
|
||||
const totalCount = await prisma.organization.count({
|
||||
where: { referredById: context.user.organizationId }
|
||||
})
|
||||
|
||||
return {
|
||||
referrals: referrals.map(org => ({
|
||||
id: org.id,
|
||||
organization: org,
|
||||
source: org.referralTransactions[0]?.type === 'AUTO_PARTNERSHIP' ? 'AUTO_BUSINESS' : 'REFERRAL_LINK',
|
||||
spheresEarned: org.referralTransactions.reduce((sum, t) => sum + t.points, 0),
|
||||
registeredAt: org.createdAt,
|
||||
status: 'ACTIVE'
|
||||
})),
|
||||
totalCount,
|
||||
totalPages: Math.ceil(totalCount / (args.limit || 50))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -119,6 +119,24 @@ export const typeDefs = gql`
|
||||
|
||||
# Типы для кеша склада WB
|
||||
getWBWarehouseData: WBWarehouseCacheResponse!
|
||||
|
||||
# Реферальная система
|
||||
myReferralLink: String!
|
||||
myPartnerLink: String!
|
||||
myReferrals(
|
||||
dateFrom: DateTime
|
||||
dateTo: DateTime
|
||||
type: OrganizationType
|
||||
source: ReferralSource
|
||||
search: String
|
||||
limit: Int
|
||||
offset: Int
|
||||
): ReferralsResponse!
|
||||
myReferralStats: ReferralStats!
|
||||
myReferralTransactions(
|
||||
limit: Int
|
||||
offset: Int
|
||||
): ReferralTransactionsResponse!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@ -306,6 +324,12 @@ export const typeDefs = gql`
|
||||
isCurrentUser: Boolean
|
||||
hasOutgoingRequest: Boolean
|
||||
hasIncomingRequest: Boolean
|
||||
# Реферальная система
|
||||
referralCode: String
|
||||
referredBy: Organization
|
||||
referrals: [Organization!]!
|
||||
referralPoints: Int!
|
||||
isMyReferral: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
@ -346,6 +370,8 @@ export const typeDefs = gql`
|
||||
phone: String!
|
||||
inn: String!
|
||||
type: OrganizationType!
|
||||
referralCode: String
|
||||
partnerCode: String
|
||||
}
|
||||
|
||||
input SellerRegistrationInput {
|
||||
@ -353,6 +379,8 @@ export const typeDefs = gql`
|
||||
wbApiKey: String
|
||||
ozonApiKey: String
|
||||
ozonClientId: String
|
||||
referralCode: String
|
||||
partnerCode: String
|
||||
}
|
||||
|
||||
input MarketplaceApiKeyInput {
|
||||
@ -1430,4 +1458,81 @@ export const typeDefs = gql`
|
||||
extend type Query {
|
||||
fulfillmentWarehouseStats: FulfillmentWarehouseStats!
|
||||
}
|
||||
|
||||
# Типы для реферальной системы
|
||||
type ReferralsResponse {
|
||||
referrals: [Referral!]!
|
||||
totalCount: Int!
|
||||
totalPages: Int!
|
||||
}
|
||||
|
||||
type Referral {
|
||||
id: ID!
|
||||
organization: Organization!
|
||||
source: ReferralSource!
|
||||
spheresEarned: Int!
|
||||
registeredAt: DateTime!
|
||||
status: ReferralStatus!
|
||||
transactions: [ReferralTransaction!]!
|
||||
}
|
||||
|
||||
type ReferralStats {
|
||||
totalPartners: Int!
|
||||
totalSpheres: Int!
|
||||
monthlyPartners: Int!
|
||||
monthlySpheres: Int!
|
||||
referralsByType: [ReferralTypeStats!]!
|
||||
referralsBySource: [ReferralSourceStats!]!
|
||||
}
|
||||
|
||||
type ReferralTypeStats {
|
||||
type: OrganizationType!
|
||||
count: Int!
|
||||
spheres: Int!
|
||||
}
|
||||
|
||||
type ReferralSourceStats {
|
||||
source: ReferralSource!
|
||||
count: Int!
|
||||
spheres: Int!
|
||||
}
|
||||
|
||||
type ReferralTransactionsResponse {
|
||||
transactions: [ReferralTransaction!]!
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
type ReferralTransaction {
|
||||
id: ID!
|
||||
referrer: Organization!
|
||||
referral: Organization!
|
||||
spheres: Int!
|
||||
type: ReferralTransactionType!
|
||||
description: String
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
enum ReferralSource {
|
||||
REFERRAL_LINK
|
||||
AUTO_BUSINESS
|
||||
}
|
||||
|
||||
enum ReferralStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
BLOCKED
|
||||
}
|
||||
|
||||
enum ReferralTransactionType {
|
||||
REGISTRATION
|
||||
AUTO_PARTNERSHIP
|
||||
FIRST_ORDER
|
||||
MONTHLY_BONUS
|
||||
}
|
||||
|
||||
enum CounterpartyType {
|
||||
MANUAL
|
||||
REFERRAL
|
||||
AUTO_BUSINESS
|
||||
}
|
||||
`
|
||||
|
@ -73,6 +73,8 @@ interface UseAuthReturn {
|
||||
phone: string,
|
||||
inn: string,
|
||||
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
|
||||
referralCode?: string | null,
|
||||
partnerCode?: string | null,
|
||||
) => Promise<{
|
||||
success: boolean
|
||||
message: string
|
||||
@ -83,6 +85,8 @@ interface UseAuthReturn {
|
||||
wbApiKey?: string
|
||||
ozonApiKey?: string
|
||||
ozonClientId?: string
|
||||
referralCode?: string | null
|
||||
partnerCode?: string | null
|
||||
}) => Promise<{
|
||||
success: boolean
|
||||
message: string
|
||||
@ -290,7 +294,13 @@ export const useAuth = (): UseAuthReturn => {
|
||||
phone: string,
|
||||
inn: string,
|
||||
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
|
||||
referralCode?: string | null,
|
||||
partnerCode?: string | null,
|
||||
) => {
|
||||
console.log('🎬 useAuth - registerFulfillmentOrganization вызван с параметрами:', {
|
||||
phone, inn, type, referralCode, partnerCode
|
||||
})
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
@ -301,12 +311,18 @@ export const useAuth = (): UseAuthReturn => {
|
||||
currentToken ? `${currentToken.substring(0, 20)}...` : 'No token',
|
||||
)
|
||||
|
||||
console.log('🎬 useAuth - Отправка GraphQL мутации с input:', {
|
||||
phone, inn, type, referralCode, partnerCode
|
||||
})
|
||||
|
||||
const { data } = await registerFulfillmentMutation({
|
||||
variables: {
|
||||
input: { phone, inn, type },
|
||||
input: { phone, inn, type, referralCode, partnerCode },
|
||||
},
|
||||
})
|
||||
|
||||
console.log('🎬 useAuth - Ответ GraphQL мутации:', data)
|
||||
|
||||
const result = data.registerFulfillmentOrganization
|
||||
|
||||
if (result.success && result.user) {
|
||||
@ -340,6 +356,8 @@ export const useAuth = (): UseAuthReturn => {
|
||||
wbApiKey?: string
|
||||
ozonApiKey?: string
|
||||
ozonClientId?: string
|
||||
referralCode?: string | null
|
||||
partnerCode?: string | null
|
||||
}) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
1806
workflow-catalog.md
Normal file
1806
workflow-catalog.md
Normal file
@ -0,0 +1,1806 @@
|
||||
# КАТАЛОГ ВСЕХ БИЗНЕС-ПРОЦЕССОВ СИСТЕМЫ
|
||||
|
||||
> ⚠️ **НАЗНАЧЕНИЕ**: Полный каталог всех бизнес-процессов и workflow системы управления складами.
|
||||
> **Источник**: Все процессы из [rules-complete.md](./rules-complete.md) собраны в одном месте для удобного поиска.
|
||||
|
||||
---
|
||||
|
||||
## 📋 **ОГЛАВЛЕНИЕ**
|
||||
|
||||
### Основные процессы:
|
||||
- [1. Workflow поставок](#1-workflow-поставок) - 8 статусов, 6 этапов
|
||||
- [2. Процесс создания продукта](#2-процесс-создания-продукта) - 5 шагов с SLA
|
||||
- [3. UI процессы селлера](#3-ui-процессы-селлера) - 4-блочная система
|
||||
- [4. Workflow фулфилмента](#4-workflow-фулфилмента) - 3 этапа обработки
|
||||
- [5. Workflow логистики](#5-workflow-логистики) - 4 этапа доставки
|
||||
- [6. Система партнерства](#6-система-партнерства) - 2 способа, 4 статуса
|
||||
- [7. Интеграция с услугами](#7-интеграция-с-услугами) - 5-шаговый workflow
|
||||
- [8. Критические ситуации](#8-критические-ситуации) - отмены и чрезвычайные
|
||||
|
||||
### Вспомогательные процессы:
|
||||
- [9. Протоколы разработки](#9-протоколы-разработки) - последовательность работы
|
||||
- [10. Система учета движения товаров](#10-система-учета-движения-товаров)
|
||||
|
||||
### Индекс по ролям:
|
||||
- **Селлер**: процессы 1, 2, 3, 6
|
||||
- **Поставщик**: процессы 1, 6
|
||||
- **Фулфилмент**: процессы 1, 2, 4, 7
|
||||
- **Логистика**: процессы 1, 5
|
||||
|
||||
---
|
||||
|
||||
## 1. 🚚 **WORKFLOW ПОСТАВОК**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 5, строки 519-563
|
||||
|
||||
### 1.1 Детализированная система статусов
|
||||
|
||||
**Статусы SupplyOrder (Заказ поставки):**
|
||||
|
||||
1. **PENDING** - Ожидает подтверждения поставщиком
|
||||
2. **SUPPLIER_APPROVED** - Одобрено поставщиком
|
||||
3. **CONFIRMED** - Подтвержден (готов к обработке)
|
||||
4. **LOGISTICS_CONFIRMED** - Подтверждено логистикой
|
||||
5. **SHIPPED** - Отгружено поставщиком
|
||||
6. **IN_TRANSIT** - В пути (логистика доставляет)
|
||||
7. **DELIVERED** - Доставлен на фулфилмент
|
||||
8. **CANCELLED** - Отменен
|
||||
|
||||
### 1.2 Пошаговый процесс поставки
|
||||
|
||||
**ЭТАП 1: Создание заказа**
|
||||
|
||||
1. Селлер заказывает товар/расходники у поставщика
|
||||
2. Система создает SupplyOrder со статусом `PENDING`
|
||||
3. Автоматическое уведомление поставщику
|
||||
|
||||
**ЭТАП 2: Обработка поставщиком**
|
||||
|
||||
4. Поставщик получает оповещение
|
||||
5. Поставщик нажимает "Одобрить"
|
||||
6. Статус меняется на `SUPPLIER_APPROVED`
|
||||
|
||||
**ЭТАП 3: Передача в фулфилмент**
|
||||
|
||||
7. Поставка отображается в кабинете фулфилмента
|
||||
8. Фулфилмент выбирает ответственного и логистику
|
||||
9. Статус меняется на `CONFIRMED`
|
||||
|
||||
**ЭТАП 4: Логистическое подтверждение**
|
||||
|
||||
10. Логистика подтверждает доставку
|
||||
11. Статус меняется на `LOGISTICS_CONFIRMED`
|
||||
|
||||
**ЭТАП 5: Отгрузка**
|
||||
|
||||
12. Поставщик отгружает товар
|
||||
13. Статус меняется на `SHIPPED`, затем `IN_TRANSIT`
|
||||
|
||||
**ЭТАП 6: Доставка и приемка**
|
||||
|
||||
14. Логистика доставляет на фулфилмент
|
||||
15. Фулфилмент принимает товар
|
||||
16. Статус меняется на `DELIVERED`
|
||||
|
||||
### 1.3 Система уведомлений
|
||||
|
||||
**Обязательные уведомления:**
|
||||
|
||||
- Поставщику: о новом заказе
|
||||
- Фулфилменту: о подтвержденной поставке
|
||||
- Логистике: о назначении на заявку
|
||||
- Селлеру: об изменении каждого статуса
|
||||
|
||||
---
|
||||
|
||||
## 2. 🔄 **ПРОЦЕСС СОЗДАНИЯ ПРОДУКТА**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 6, строки 565-740
|
||||
|
||||
### 2.1 5-шаговый алгоритм создания
|
||||
|
||||
**ПРЕДВАРИТЕЛЬНОЕ УСЛОВИЕ**: Рецептура задана селлером
|
||||
|
||||
**ШАГ 1: Поступление на склад (автоматически)**
|
||||
- Товар поступает на склад фулфилмента
|
||||
- Система фиксирует поступление
|
||||
- Товар получает статус "доступен для обработки"
|
||||
|
||||
**ШАГ 2: Планирование работы (менеджер фулфилмента)**
|
||||
- Менеджер фулфилмента видит товар в интерфейсе
|
||||
- Планирует обработку согласно рецептуре
|
||||
- Назначает исполнителя
|
||||
|
||||
**ШАГ 3: Обработка товара (исполнитель)**
|
||||
- Исполнитель берет товар в работу
|
||||
- Применяет услуги согласно рецептуре
|
||||
- Использует расходники селлера и фулфилмента
|
||||
- Товар превращается в продукт
|
||||
|
||||
**ШАГ 4: Контроль качества (менеджер/отдел качества)**
|
||||
- Проверка соответствия рецептуре
|
||||
- Контроль качества обработки
|
||||
- Подтверждение или возврат на доработку
|
||||
|
||||
**ШАГ 5: Завершение (система + менеджер)**
|
||||
- Система создает запись о готовом продукте
|
||||
- Продукт получает статус FINISHED_PRODUCT
|
||||
- Готов к отправке селлеру
|
||||
|
||||
### 2.2 Временные рамки и SLA
|
||||
|
||||
| Этап | Время выполнения | Ответственный | KPI |
|
||||
|------|------------------|---------------|-----|
|
||||
| Поступление | Мгновенно | Система | 100% автоматизация |
|
||||
| Планирование | До 2 часов | Менеджер ФФ | 95% в срок |
|
||||
| Обработка | 1-3 дня | Исполнитель | Согласно сложности |
|
||||
| Контроль | До 4 часов | ОТК | 99% точность |
|
||||
|
||||
### 2.3 Детальная рецептура продукта
|
||||
|
||||
**РЕЦЕПТУРА ПРОДУКТА** (задается селлером при создании поставки):
|
||||
|
||||
- **БАЗОВЫЙ ТОВАР**: Исходный материал (обязательно)
|
||||
- Артикул товара
|
||||
- Количество единиц
|
||||
- Размерная сетка (если применимо)
|
||||
|
||||
- **УСЛУГА ФУЛФИЛМЕНТА**: Из каталога услуг фулфилмента
|
||||
- Тип услуги (глажка, упаковка, маркировка и т.д.)
|
||||
- Количество применений
|
||||
- Специальные требования
|
||||
|
||||
- **РАСХОДНИК СЕЛЛЕРА**: Материалы селлера (опционально)
|
||||
- Фирменная упаковка
|
||||
- Этикетки, бирки
|
||||
- Дополнительные аксессуары
|
||||
|
||||
- **РАСХОДНИК ФУЛФИЛМЕНТА**: Материалы фулфилмента (опционально)
|
||||
- Стандартная упаковка
|
||||
- Защитные материалы
|
||||
- Маркировочные элементы
|
||||
|
||||
- **СВЯЗЬ С МАРКЕТПЛЕЙСОМ**: Привязка к карточке (опционально)
|
||||
- ID карточки на маркетплейсе
|
||||
- Артикул маркетплейса
|
||||
- Особые требования МП
|
||||
|
||||
**ФОРМУЛА**: ПРОДУКТ = Товар + Услуга(и) + Расходники селлера + Расходники ФФ
|
||||
|
||||
### 2.4 Пошаговый алгоритм создания продукта
|
||||
|
||||
#### **ПРЕДВАРИТЕЛЬНОЕ УСЛОВИЕ: РЕЦЕПТУРА ЗАДАНА** (селлер)
|
||||
|
||||
```
|
||||
Время: при создании заявки на поставку
|
||||
Действие: селлер указывает рецептуру продукта
|
||||
Обязательные компоненты:
|
||||
✓ Базовый товар (от поставщика)
|
||||
✓ Услуги фулфилмента (упаковка, маркировка и т.д.)
|
||||
✓ Расходники (материалы для производства)
|
||||
Результат: рецептура сохраняется в заявке и передается фулфилменту
|
||||
```
|
||||
|
||||
#### **ШАГ 1: ПОСТУПЛЕНИЕ НА СКЛАД** (автоматически)
|
||||
|
||||
```
|
||||
Время: при смене статуса поставки DELIVERED
|
||||
Действие: товар переходит в статус "на складе"
|
||||
Ответственный: система
|
||||
Результат: +Прибыло в статистике товаров
|
||||
```
|
||||
|
||||
#### **ШАГ 2: ПЛАНИРОВАНИЕ РАБОТЫ** (менеджер фулфилмента)
|
||||
|
||||
```
|
||||
Время: в течение 2 рабочих дней после поступления
|
||||
Действие: назначение параметров обработки
|
||||
Ответственный: менеджер фулфилмента
|
||||
Обязательные поля:
|
||||
✓ Дедлайн выполнения (не более 5 рабочих дней)
|
||||
✓ Ответственный исполнитель (из списка сотрудников)
|
||||
✓ Рецептура (товар + услуги + расходники, указанная селлером в заявке на поставку)
|
||||
Опциональные поля:
|
||||
- Место хранения готовых продуктов (зона склада, стеллаж, ячейка)
|
||||
- Комментарии к работе
|
||||
Результат: поставка переходит во вкладку "В работе"
|
||||
```
|
||||
|
||||
#### **ШАГ 3: ОБРАБОТКА ТОВАРА** (исполнитель)
|
||||
|
||||
```
|
||||
Время: согласно дедлайну (обычно 1-3 дня)
|
||||
Действие: физическая обработка товара
|
||||
Ответственный: назначенный сотрудник
|
||||
Обязательные действия:
|
||||
1. Проверка качества товара
|
||||
2. Фиксация фактического количества
|
||||
3. Выявление и учет брака
|
||||
4. Применение рецептуры (услуги + расходники)
|
||||
5. Создание готового продукта
|
||||
Точки контроля:
|
||||
- Соответствие плану/факту
|
||||
- Качество выполнения услуг
|
||||
- Расход материалов по норме
|
||||
|
||||
УЧЕТ ПЛАН/ФАКТ:
|
||||
- ПЛАН: Количество товаров из поставки селлера (указано в заказе)
|
||||
- ФАКТ: Реальное количество после обработки = Брак + Хороший товар
|
||||
- ДЕТАЛИЗАЦИЯ: Учет ведется по каждому размеру/объему/варианту
|
||||
- КОРРЕКТИРОВКА: Статистика автоматически обновляется на фактические данные
|
||||
```
|
||||
|
||||
#### **ШАГ 4: КОНТРОЛЬ КАЧЕСТВА** (менеджер/отдел качества)
|
||||
|
||||
```
|
||||
Время: сразу после завершения ШАГ 3
|
||||
Действие: приемка готовой продукции
|
||||
Ответственный: менеджер или контролер качества
|
||||
Критерии приемки:
|
||||
✓ Соответствие рецептуре селлера
|
||||
✓ Качество выполненных услуг
|
||||
✓ Правильность упаковки/маркировки
|
||||
✓ Полнота комплектации
|
||||
Результат: продукт готов к отправке или отправлен на доработку
|
||||
```
|
||||
|
||||
#### **ШАГ 5: ЗАВЕРШЕНИЕ** (система + менеджер)
|
||||
|
||||
```
|
||||
Время: после успешного прохождения контроля качества
|
||||
Действие: финализация процесса
|
||||
Автоматические действия:
|
||||
- Создание записи FINISHED_PRODUCT в БД
|
||||
- Обновление статистики: товар "на складе" → продукт "готов"
|
||||
- Списание использованных расходников
|
||||
- Уведомление селлера о готовности
|
||||
Ручные действия менеджера:
|
||||
- Подтверждение перехода во вкладку "Выполнено"
|
||||
- Указание фактических расходов материалов
|
||||
- Добавление комментариев о выполненной работе
|
||||
```
|
||||
|
||||
### 2.5 Временные рамки и SLA
|
||||
|
||||
| Этап | Стандартное время | Максимальное время | Ответственный |
|
||||
| ----------------- | ----------------- | ------------------ | -------------- |
|
||||
| Планирование | 1 рабочий день | 2 рабочих дня | Менеджер ФФ |
|
||||
| Обработка | 2-3 рабочих дня | 5 рабочих дней | Исполнитель |
|
||||
| Контроль качества | 4 часа | 1 рабочий день | Отдел качества |
|
||||
| Завершение | 2 часа | 4 часа | Менеджер ФФ |
|
||||
|
||||
### 2.6 Детальная рецептура продукта
|
||||
|
||||
**РЕЦЕПТУРА ПРОДУКТА** (задается селлером при создании поставки):
|
||||
|
||||
- **БАЗОВЫЙ ТОВАР**: Исходный материал (обязательно)
|
||||
- Артикул товара
|
||||
- Количество единиц
|
||||
- Размерная сетка (если применимо)
|
||||
|
||||
- **УСЛУГА ФУЛФИЛМЕНТА**: Из каталога услуг фулфилмента
|
||||
- Тип услуги (глажка, упаковка, маркировка и т.д.)
|
||||
- Количество применений
|
||||
- Специальные требования
|
||||
|
||||
- **РАСХОДНИК СЕЛЛЕРА**: Материалы селлера (опционально)
|
||||
- Фирменная упаковка
|
||||
- Этикетки, бирки
|
||||
- Дополнительные аксессуары
|
||||
|
||||
- **РАСХОДНИК ФУЛФИЛМЕНТА**: Материалы фулфилмента (опционально)
|
||||
- Стандартная упаковка
|
||||
- Защитные материалы
|
||||
- Маркировочные элементы
|
||||
|
||||
- **СВЯЗЬ С МАРКЕТПЛЕЙСОМ**: Привязка к карточке (опционально)
|
||||
- ID карточки на маркетплейсе
|
||||
- Артикул маркетплейса
|
||||
- Особые требования МП
|
||||
|
||||
**ФОРМУЛА**: ПРОДУКТ = Товар + Услуга(и) + Расходники селлера + Расходники ФФ
|
||||
|
||||
### 2.7 Учет план/факт в процессе работы
|
||||
|
||||
**ПЛАН**: Количество товара из поставки селлера
|
||||
**ФАКТ**: Реальное количество после пересчета (работник фулфилмента производит сортировку при пересчете)
|
||||
|
||||
**ФИКСАЦИЯ ПОТЕРЬ:**
|
||||
|
||||
- **КОГДА**: В процессе работы (вкладка "В работе")
|
||||
- **ЧТО**: Недостача, повреждения (без создания записей брака)
|
||||
- **КАК**: Корректировка количества в статистике
|
||||
|
||||
**WORKFLOW СОЗДАНИЯ ПРОДУКТА:**
|
||||
|
||||
1. Товар поступает на склад фулфилмента (статус "на складе")
|
||||
2. Товар берется в работу (переход в статус "в обработке")
|
||||
3. Исполнитель производит пересчет и сортировку
|
||||
4. Создается готовый продукт (тип FINISHED_PRODUCT)
|
||||
5. Продукт готов к отправке на маркетплейсы
|
||||
|
||||
**ВЛИЯНИЕ НА СТАТИСТИКУ:**
|
||||
|
||||
- При принятии поставки: +План в статистику
|
||||
- При выявлении факта: корректировка на реальные данные
|
||||
- **ФОРМУЛА**: Факт = Потери + Хороший товар
|
||||
_Где потери - это недостача/повреждения, выявленные при пересчете и сортировке_
|
||||
- **ЛОГИКА**: Фактическое количество = сумма всех пересчитанных предметов
|
||||
- **ПЛАН/ФАКТ**: Корректировка статистики при выявлении расхождений
|
||||
|
||||
---
|
||||
|
||||
## 3. 🎨 **UI ПРОЦЕССЫ СЕЛЛЕРА**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 9, строки 871-1885 (1015 строк)
|
||||
|
||||
### 3.1 Структура раздела "Мои поставки"
|
||||
|
||||
#### **🏢 ПОСТАВКИ НА ФУЛФИЛМЕНТ**:
|
||||
|
||||
- **Товар** - поставка товаров для создания продуктов
|
||||
- **Карточки** - поставка через WB API с рецептурой (результат: WildberriesSupply)
|
||||
- **Поставщики** - заказ товаров у поставщиков с рецептурой (результат: SupplyOrder)
|
||||
- **Расходники селлера** - поставка материалов для товаров селлера
|
||||
|
||||
#### **🛒 ПОСТАВКИ НА МАРКЕТПЛЕЙСЫ** _(планируется)_:
|
||||
|
||||
- **Wildberries** - прямые поставки на WB
|
||||
- **Ozon** - прямые поставки на Ozon
|
||||
|
||||
### 3.2 UI структура создания поставки расходников селлера
|
||||
|
||||
#### **📄 Структура страницы создания поставки:**
|
||||
|
||||
**ОБНОВЛЕННАЯ СТРУКТУРА СИСТЕМЫ (4 БЛОКА):**
|
||||
|
||||
**БЛОК 1: ПОСТАВЩИКИ** _(адаптивная сетка)_
|
||||
|
||||
- **Заголовок**: Минималистичный "🏢 Поставщики" без лишних элементов
|
||||
- **Поиск**: Компактное поле справа "Поиск поставщиков..." (w-64)
|
||||
- **Отображение**: Карточки поставщиков из раздела "Партнеры" в адаптивной сетке
|
||||
- **Выбор**: Клик выделяет карточку поставщика
|
||||
- **Результат**: Загружаются карточки товаров выбранного поставщика в блок 2
|
||||
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(горизонтальный скролл - НОВЫЙ)_
|
||||
|
||||
- **Отображение**: ТОЛЬКО минималистичные карточки товаров 80×112px
|
||||
- **Содержание**: ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
|
||||
- **Навигация**: Горизонтальный скролл при множестве товаров
|
||||
- **Выбор**: Клик добавляет товар в детальный каталог
|
||||
- **Результат**: Товар добавляется в блок 3 для управления поставкой
|
||||
|
||||
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(детальный каталог)_
|
||||
|
||||
- **Отображение**: Детальные карточки выбранных товаров
|
||||
- **Управление**: Количество, параметры, настройки поставки
|
||||
- **Результат**: Формирование окончательной поставки
|
||||
|
||||
**БЛОК 4: КОРЗИНА И НАСТРОЙКИ** _(правая панель)_
|
||||
|
||||
- **Отображение**: Корзина поставки + настройки
|
||||
- **Управление**: Фулфилмент-центр, дата, логистика
|
||||
|
||||
#### **3.2.1 Детальные правила горизонтального скролла поставщиков**
|
||||
|
||||
**СТРУКТУРА И ОТОБРАЖЕНИЕ:**
|
||||
|
||||
- **Источник данных**: Партнеры типа `WHOLESALE` из раздела "Партнеры"
|
||||
- **Контейнер**: Фиксированная высота 176px (h-44) с горизонтальным скроллом
|
||||
- **Блок поставщиков**: Общая высота 180px, включает заголовок + контейнер скролла
|
||||
- **Направление**: Слева направо (LTR)
|
||||
- **Поведение**: Плавный скролл с автоскрытием полосы прокрутки
|
||||
|
||||
**РАЗМЕРЫ И АДАПТИВНОСТЬ:**
|
||||
|
||||
- **Десктоп**: Карточка 216×92px, отступы 12px между карточками, 16px от краев
|
||||
- **Планшет**: Карточка 200×92px, отступы 12px между карточками
|
||||
- **Мобильный**: Карточка 184×92px, отступы 12px между карточками
|
||||
- **Высота блока**: 180px фиксированная для всего блока поставщиков
|
||||
|
||||
**ВЗАИМОДЕЙСТВИЕ:**
|
||||
|
||||
- **Навигация**: Колесо мыши (Shift+скролл), стрелки клавиатуры, свайп на тач
|
||||
- **Выбор**: Клик по карточке → активная рамка + загрузка товаров в блок 2
|
||||
- **Состояния**: Default, Hover (box-shadow), Active (цветная рамка), Loading (скелетон)
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
|
||||
- **1-4 карточки**: Выравнивание по левому краю, скролл неактивен
|
||||
- **5+ карточек**: Полный горизонтальный скролл
|
||||
- **Нет партнеров**: Заглушка с ссылкой на раздел "Партнеры"
|
||||
|
||||
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
|
||||
|
||||
**Критическая Flex-архитектура:**
|
||||
|
||||
```css
|
||||
.parent-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.left-block {
|
||||
flex: 1;
|
||||
min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для overflow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suppliers-container {
|
||||
height: 180px; /* Общая высота блока */
|
||||
flex-shrink: 0;
|
||||
min-width: 0; /* Предотвращает растяжение */
|
||||
}
|
||||
|
||||
.right-block {
|
||||
width: 384px; /* w-96 */
|
||||
flex-shrink: 0; /* Защита от сжатия */
|
||||
}
|
||||
```
|
||||
|
||||
**Контейнер скролла:**
|
||||
|
||||
```css
|
||||
.suppliers-block {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-behavior: smooth;
|
||||
gap: 12px;
|
||||
padding: 0 16px 8px 16px; /* px-4 pb-2 */
|
||||
height: 176px; /* h-44 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #64748b33 transparent;
|
||||
}
|
||||
|
||||
.suppliers-block:hover {
|
||||
scrollbar-color: #cbd5e0 #64748b22;
|
||||
}
|
||||
|
||||
.supplier-card {
|
||||
flex-shrink: 0;
|
||||
width: 216px; /* Десктоп */
|
||||
height: 92px; /* Фиксированная высота */
|
||||
padding: 8px; /* p-2 */
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
```
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ПОСТАВЩИКА:**
|
||||
|
||||
**Структура (3 строки в 92px высоты):**
|
||||
|
||||
- **Строка 1**: Название + рейтинг (справа, если есть)
|
||||
- **Строка 2**: ИНН (формат "ИНН: 1234567890")
|
||||
- **Строка 3**: Бейдж рынка (отдельная строка)
|
||||
|
||||
**Элементы:**
|
||||
|
||||
- **Аватар**: Размер xs, слева с gap-2
|
||||
- **Текст**: text-xs для компактности
|
||||
- **Отступы**: mb-1 между строками 1-2, mb-0.5 между строками 2-3
|
||||
- **Padding карточки**: 8px (p-2)
|
||||
|
||||
**ЦВЕТОВАЯ СХЕМА РЫНКОВ:**
|
||||
|
||||
- **"Садовод"** (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`
|
||||
- **Другие/не указан**: Серый `bg-gray-500/20 text-gray-300 border-gray-500/30`
|
||||
|
||||
**ДОСТУПНОСТЬ:**
|
||||
|
||||
- `role="tablist"` для контейнера
|
||||
- `role="tab"` для карточек
|
||||
- `aria-selected="true/false"` для выбранной карточки
|
||||
- `tabindex="0"` для активной, `-1` для неактивных
|
||||
|
||||
#### **3.2.2 Правила блока "Карточки товаров" (Блок 2)**
|
||||
|
||||
**НАЗНАЧЕНИЕ И ЛОГИКА:**
|
||||
|
||||
- **Источник данных**: Товары выбранного поставщика из Блока 1
|
||||
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
|
||||
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
|
||||
- **Поведение**: Горизонтальный скролл при множестве товаров
|
||||
|
||||
**АРХИТЕКТУРА И РАЗМЕРЫ:**
|
||||
|
||||
- **Внешний контейнер**: 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 (w-20 h-28), соотношение 5:7
|
||||
- **Адаптивность**: фиксированный размер для всех устройств
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
|
||||
|
||||
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
|
||||
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
|
||||
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
|
||||
- **Рамка**: border-white/10, при выборе border-white/30
|
||||
- **Фон**: bg-white/5 полупрозрачный
|
||||
|
||||
**ДЕЙСТВИЕ:**
|
||||
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
|
||||
|
||||
#### **3.2.3 Правила Блока 3 "Детальный каталог товаров"**
|
||||
|
||||
**НАЗНАЧЕНИЕ И СТРУКТУРА:**
|
||||
|
||||
- **Контент**: Детальные карточки выбранных товаров с полным управлением
|
||||
- **Верхняя панель**: Выбор даты + Выбор Fulfillment + Поиск
|
||||
- **Основная область**: Сетка карточек товаров с детальной информацией
|
||||
|
||||
#### **3.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>
|
||||
```
|
||||
|
||||
#### **3.2.3.2 Структура основной области карточек**
|
||||
|
||||
**СЕТКА ТОВАРОВ:**
|
||||
|
||||
- **Адаптивная сетка**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`
|
||||
- **Детальные карточки**: Полная информация + количество + управление
|
||||
- **Состояния**: Default, Selected, Editing
|
||||
- **Интерактивность**: Изменение количества, удаление, настройки рецептуры
|
||||
|
||||
**ФУНКЦИОНАЛЬНОСТЬ ПАНЕЛИ:**
|
||||
|
||||
- **Выбор даты**: Планирование времени поставки (обязательное поле)
|
||||
- **Выбор фулфилмента**: Определение исполнителя поставки (обязательное поле)
|
||||
- **Поиск**: Фильтрация товаров в каталоге по названию/артикулу
|
||||
- **Валидация**: Блокировка создания поставки без заполнения даты и фулфилмента
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
|
||||
- **Пустой каталог**: Заглушка "Добавьте товары"
|
||||
- **Нет фулфилментов**: Сообщение "Настройте партнерство с фулфилмент-центрами"
|
||||
- **Поиск без результатов**: "По запросу ничего не найдено"
|
||||
|
||||
#### **3.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)
|
||||
- **Сообщения об ошибках**: Локализованные уведомления при проблемах загрузки
|
||||
|
||||
#### **3.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
|
||||
|
||||
#### **3.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 для соответствия ИНН
|
||||
- **Позиция**: Справа от ИНН в той же строке
|
||||
- **Приоритет**: Рынок важнее типа организации для селлера
|
||||
|
||||
### 3.3 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ
|
||||
|
||||
**🎯 ОСНОВНОЙ ПРИНЦИП:**
|
||||
Выбранные товары в детальном каталоге (блок 3) сохраняются при смене поставщика и могут быть удалены только явным действием пользователя.
|
||||
|
||||
**🔄 WORKFLOW СЦЕНАРИИ:**
|
||||
|
||||
**СЦЕНАРИЙ 1: Добавление товаров от разных поставщиков**
|
||||
|
||||
1. Пользователь выбирает Поставщика А
|
||||
2. Добавляет Товар 1 и Товар 2 в детальный каталог
|
||||
3. Переключается на Поставщика Б
|
||||
4. Товар 1 и Товар 2 остаются в блоке 3
|
||||
5. Добавляет Товар 3 от Поставщика Б
|
||||
6. В блоке 3: Товар 1, Товар 2 (от А) + Товар 3 (от Б)
|
||||
|
||||
**СЦЕНАРИЙ 2: Визуальная индикация в блоке 2**
|
||||
|
||||
- При переключении на поставщика, товары которого уже есть в блоке 3, показываются как "выбранные"
|
||||
- Товары от других поставщиков в блоке 2 не отображаются
|
||||
|
||||
**🛠️ ТЕХНИЧЕСКИЕ ПРАВИЛА:**
|
||||
|
||||
**Состояние selectedProductsForDetailView:**
|
||||
|
||||
- Глобальное состояние всех выбранных товаров
|
||||
- НЕ зависит от текущего поставщика
|
||||
- НЕ очищается при смене поставщика
|
||||
- Очищается только явными действиями пользователя
|
||||
|
||||
**Единственные способы удаления:**
|
||||
|
||||
1. Кнопка "Удалить из каталога" в карточке товара (блок 3)
|
||||
2. Кнопка "Очистить каталог" в заголовке блока 3
|
||||
3. НЕ при смене поставщика
|
||||
|
||||
**🎨 UX ПРАВИЛА:**
|
||||
|
||||
- Счетчик товаров: "Детальный каталог (X товаров от Y поставщиков)"
|
||||
- Визуальная индикация выбранных товаров в блоке 2
|
||||
- Информация о поставщике для каждого товара в блоке 3
|
||||
|
||||
### 3.4 Правила кнопки "Создать поставку" в разделе "Мои поставки"
|
||||
|
||||
#### **3.4.1 Общие принципы**
|
||||
|
||||
- **КОНТЕКСТНОСТЬ**: Кнопка создания появляется только в активном табе
|
||||
- **РАСПОЛОЖЕНИЕ**: Правая часть строки таба, на том же уровне что и название
|
||||
- **СТИЛИСТИКА**: В том же стиле что и сами табы (соответствует уровню иерархии)
|
||||
- **ФУНКЦИОНАЛЬНОСТЬ**: Кнопка ведет на страницу создания поставки соответствующего типа
|
||||
|
||||
#### **3.4.2 Размещение кнопок по табам**
|
||||
|
||||
**УРОВЕНЬ 2 (Подтабы фулфилмента):**
|
||||
|
||||
- **📦 Товар → Карточки**: Кнопка "Создать поставку" → `/supplies/create-cards`
|
||||
- **📦 Товар → Поставщики**: Кнопка "Создать поставку" → `/supplies/create-suppliers`
|
||||
- **🔧 Расходники селлера**: Кнопка "Создать поставку" → `/supplies/create-consumables`
|
||||
|
||||
**УРОВЕНЬ 2 (Подтабы маркетплейсов):**
|
||||
|
||||
- **🟣 Wildberries**: Кнопка "Создать поставку" → `/supplies/create-wildberries`
|
||||
- **🔵 Ozon**: Кнопка "Создать поставку" → `/supplies/create-ozon`
|
||||
|
||||
#### **3.4.3 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ**
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
|
||||
|
||||
##### **3.4.3.1 Размеры и позиционирование**
|
||||
|
||||
```tsx
|
||||
<div className="w-72 flex-shrink-0">
|
||||
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
|
||||
```
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
|
||||
|
||||
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
|
||||
- **Флекс**: `flex-shrink-0` - корзина не сжимается
|
||||
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
|
||||
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
|
||||
|
||||
##### **3.4.3.2 Автодобавление товаров**
|
||||
|
||||
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
|
||||
|
||||
```tsx
|
||||
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
|
||||
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value
|
||||
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
|
||||
|
||||
if (newQuantity > 0) {
|
||||
// Автоматически добавляем товар в корзину
|
||||
updateProductQuantity(product.id, newQuantity)
|
||||
} else {
|
||||
// Удаляем товар из корзины при количестве 0
|
||||
removeFromCart(product.id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.7.5 Синхронизация данных между блоками
|
||||
|
||||
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
|
||||
|
||||
1. **Дата поставки**: Из Блока 3 (дата пикер)
|
||||
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
|
||||
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
|
||||
|
||||
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
|
||||
|
||||
```
|
||||
Дата поставки: 08.08.2025
|
||||
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
|
||||
Логистическая компания: [Выпадающий список]
|
||||
```
|
||||
|
||||
#### 3.2.7.6 Критические требования
|
||||
|
||||
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
|
||||
|
||||
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
|
||||
- Использовать optional chaining для всех вложенных объектов
|
||||
|
||||
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
|
||||
|
||||
- Мемоизация расчетов: `useMemo` для дорогих вычислений
|
||||
- Debounce для инпутов количества
|
||||
|
||||
🚨 **UX КОНСИСТЕНТНОСТЬ**:
|
||||
|
||||
- Единые стили для всех корзин в системе
|
||||
- Одинаковое поведение auto-add во всех формах
|
||||
- Синхронная валидация данных
|
||||
```
|
||||
|
||||
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
|
||||
|
||||
##### **3.4.3.3 Структура корзины**
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
|
||||
|
||||
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
|
||||
2. **Список товаров**:
|
||||
- Название товара (БЕЗ суффикса "(с рецептурой)")
|
||||
- Цена за единицу × количество
|
||||
- Кнопка удаления (X справа)
|
||||
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
|
||||
4. **Итого**: Общая сумма с выделением зелёным цветом
|
||||
5. **Кнопка действия**: "Создать поставку" с градиентом
|
||||
|
||||
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
|
||||
|
||||
##### **3.4.3.4 Единая функция расчета стоимости**
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
|
||||
|
||||
```tsx
|
||||
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
|
||||
const product = products.find((p) => p.id === productId)
|
||||
if (!product) return 0
|
||||
|
||||
// Базовая цена товара
|
||||
let total = (product.pricePerUnit || 0) * quantity
|
||||
|
||||
// Добавляем услуги
|
||||
if (product.services && product.services.length > 0) {
|
||||
const servicesTotal = product.services.reduce((sum, service) => {
|
||||
return sum + (service.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += servicesTotal
|
||||
}
|
||||
|
||||
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
|
||||
if (product.ffConsumables && product.ffConsumables.length > 0) {
|
||||
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.price || 0) * quantity // ВАЖНО: .price!
|
||||
}, 0)
|
||||
total += ffConsumablesTotal
|
||||
}
|
||||
|
||||
// Добавляем расходники продавца
|
||||
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
|
||||
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += sellerConsumablesTotal
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Высота основного блока и функционал
|
||||
|
||||
#### **3.4.1 Высота основного блока**
|
||||
|
||||
**ФОРМУЛА РАСЧЕТА**:
|
||||
|
||||
```css
|
||||
height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
|
||||
```
|
||||
|
||||
**ПРАВИЛО ВЫРАВНИВАНИЯ**:
|
||||
|
||||
- Нижняя граница основного блока должна быть на одном уровне с нижней границей sidebar
|
||||
- При изменении размера окна высота пересчитывается
|
||||
- Внутренний скролл: `overflow-y-auto`
|
||||
|
||||
#### **3.4.2 Сохранение функционала**
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: При добавлении блока статистики весь существующий функционал сохраняется:
|
||||
|
||||
- Таблицы с данными поставок
|
||||
- Фильтры и сортировка
|
||||
- Кнопки действий
|
||||
- Детализация при клике
|
||||
- Пагинация
|
||||
- Поиск
|
||||
|
||||
**ЗАПРЕЩЕНО**:
|
||||
|
||||
- Удалять существующие компоненты
|
||||
- Изменять логику работы таблиц
|
||||
- Нарушать существующие API вызовы
|
||||
|
||||
### 3.5 Табы "Карточки" и "Поставщики" - объединённая логика
|
||||
|
||||
#### **3.5.1 Принцип единого типа предмета**
|
||||
|
||||
**КЛЮЧЕВОЕ ПРАВИЛО**: Табы "Карточки" и "Поставщики" - это два способа создания поставок одного типа предмета (ТОВАР)
|
||||
|
||||
**СПОСОБЫ СОЗДАНИЯ**:
|
||||
|
||||
- **Карточки** - импорт товаров через WB API с автоматическим созданием поставки
|
||||
- **Поставщики** - прямой заказ товаров у поставщика с указанием рецептуры
|
||||
|
||||
**РЕЗУЛЬТАТ**: Оба способа создают `SupplyOrder` с товарами типа `PRODUCT`
|
||||
|
||||
#### **3.5.2 Общая статистика**
|
||||
|
||||
**ПРАВИЛО**: Блок статистики показывает ОДИНАКОВЫЕ данные для обоих табов
|
||||
|
||||
**МЕТРИКИ ДЛЯ ТАБОВ "КАРТОЧКИ" И "ПОСТАВЩИКИ"**:
|
||||
|
||||
- Всего поставок товаров (из всех источников)
|
||||
- Активных поставок товаров (в работе)
|
||||
- Сумма активных поставок товаров
|
||||
- Товаров в пути (все способы доставки)
|
||||
|
||||
**ЗАПРЕЩЕНО**: Разделять статистику по способу создания
|
||||
|
||||
#### **3.5.3 Общий основной блок**
|
||||
|
||||
**СОДЕРЖИМОЕ**: Единая таблица всех поставок товаров
|
||||
|
||||
**ИСТОЧНИКИ ДАННЫХ**:
|
||||
|
||||
- Поставки, созданные через импорт карточек WB
|
||||
- Поставки, созданные через заказ у поставщиков
|
||||
- Все промежуточные и завершённые поставки
|
||||
|
||||
**РАЗЛИЧИЯ ТАБОВ**:
|
||||
|
||||
- Только кнопки создания ведут на разные страницы
|
||||
- Таб "Карточки": `/supplies/create-cards`
|
||||
- Таб "Поставщики": `/supplies/create-suppliers`
|
||||
|
||||
### 3.7 Структура страницы создания поставки расходников
|
||||
|
||||
#### **3.7.1 Обязательная структура страницы**
|
||||
|
||||
**ПРИНЦИП**: Страница состоит из трёх визуально разделённых блоков
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 1. БЛОК ТАБОВ (навигация) │
|
||||
│ - Фиксированная высота │
|
||||
│ - Glass-эффект │
|
||||
│ - Иерархическая структура │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 2. БЛОК СТАТИСТИКИ (метрики) │
|
||||
│ - Контекстные данные │
|
||||
│ - 4 карточки в ряд (desktop) │
|
||||
│ - Динамическое обновление │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 3. ОСНОВНОЙ БЛОК (контент) │
|
||||
│ - Сохраняет весь функционал │
|
||||
│ - Таблицы, фильтры, действия │
|
||||
│ - Высота до низа sidebar │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### **3.5.2 Блок статистики - контекстные метрики**
|
||||
|
||||
**ПРАВИЛО**: Статистика меняется в зависимости от выбранных табов
|
||||
|
||||
**Для путей "Фулфилмент → Товар → Карточки/Поставщики":**
|
||||
|
||||
- Всего поставок
|
||||
- Активных поставок
|
||||
- Сумма активных поставок
|
||||
- В пути
|
||||
|
||||
**Для пути "Фулфилмент → Расходники селлера":**
|
||||
|
||||
- Всего поставок
|
||||
- Активных поставок
|
||||
- Видов расходников
|
||||
- Критические остатки
|
||||
|
||||
**Для путей "Маркетплейсы → Wildberries/Ozon":**
|
||||
|
||||
- Поставок на маркетплейс
|
||||
- Товаров отправлено
|
||||
- Возвраты за неделю
|
||||
- Эффективность поставок
|
||||
|
||||
### 3.6 Многоуровневая таблица поставок
|
||||
|
||||
#### **ПЕРВЫЙ УРОВЕНЬ** _(основной список)_:
|
||||
|
||||
- **СОРТИРОВКА**: Номер поставки от большего к меньшему
|
||||
- **ОБЯЗАТЕЛЬНЫЕ КОЛОНКИ**:
|
||||
- Порядковый номер поставки
|
||||
- Количество видов расходников
|
||||
- Стоимость всей поставки
|
||||
- Количество категорий
|
||||
- Статус поставки
|
||||
|
||||
#### **ВТОРОЙ УРОВЕНЬ** _(детализация по клику)_:
|
||||
|
||||
- **АКТИВАЦИЯ**: По клику на строку первого уровня
|
||||
- **СОДЕРЖАНИЕ**:
|
||||
- Название расходника
|
||||
- Количество
|
||||
- Цена
|
||||
- Категория
|
||||
- Поставщик
|
||||
- **ОГРАНИЧЕНИЯ**: Только просмотр, редактирование запрещено
|
||||
|
||||
---
|
||||
|
||||
## 4. 🏭 **WORKFLOW ФУЛФИЛМЕНТА**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 11.2, строки 2003-2022
|
||||
|
||||
### 4.1 Трехэтапный процесс
|
||||
|
||||
**ЭТАП 1: Приемка товаров**
|
||||
|
||||
1. Фулфилмент получает поставки от поставщиков
|
||||
2. Товары размещаются на складе по модулям
|
||||
3. Ведется учет поступлений и остатков
|
||||
|
||||
**ЭТАП 2: Обработка товаров**
|
||||
|
||||
4. Товары обрабатываются согласно рецептурам селлеров
|
||||
5. Применяются услуги фулфилмента
|
||||
6. Создаются готовые продукты
|
||||
|
||||
**ЭТАП 3: Управление услугами**
|
||||
|
||||
7. Фулфилмент предоставляет каталог услуг для рецептур
|
||||
8. Устанавливает цены на расходники
|
||||
9. Управляет логистическими маршрутами
|
||||
|
||||
### 4.2 Движение товаров
|
||||
|
||||
**Поступление товаров**:
|
||||
- **ПОСТАВКИ**: От поставщиков через систему заказов
|
||||
- **ВОЗВРАТЫ**: Товары, возвращенные с ПВЗ
|
||||
- **ПЕРЕМЕЩЕНИЯ**: Между складами и магазинами
|
||||
|
||||
**Расход товаров**:
|
||||
- **ОТГРУЗКА**: Товары отправлены селлерам
|
||||
- **СПИСАНИЕ**: Брак, утрата, утилизация
|
||||
- **ВОЗВРАТ**: Возврат поставщику
|
||||
- **ИСПОЛЬЗОВАНИЕ**: Расходники для операций
|
||||
|
||||
---
|
||||
|
||||
## 5. 🚚 **WORKFLOW ЛОГИСТИКИ**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 12.2, строки 2063-2089
|
||||
|
||||
### 5.1 Четырехэтапный процесс доставки
|
||||
|
||||
**ЭТАП 1: Получение заявки**
|
||||
|
||||
1. Логистика получает уведомление о новой поставке
|
||||
2. Заявка появляется в разделе "Заявки" кабинета логистики
|
||||
3. Логист изучает детали поставки (объем, вес, маршрут)
|
||||
|
||||
**ЭТАП 2: Подтверждение доставки**
|
||||
|
||||
4. Логист нажимает кнопку "Одобрить"
|
||||
5. Статус поставки меняется на `LOGISTICS_CONFIRMED`
|
||||
6. Уведомления отправляются всем участникам
|
||||
|
||||
**ЭТАП 3: Забор товара**
|
||||
|
||||
7. Логист приезжает к поставщику за товаром
|
||||
8. Поставщик отгружает товар логисту
|
||||
9. Поставщик отмечает "Отправлено"
|
||||
10. Статус меняется на `SHIPPED`, затем `IN_TRANSIT`
|
||||
|
||||
**ЭТАП 4: Доставка**
|
||||
|
||||
11. Логистика доставляет товар на фулфилмент-центр
|
||||
12. В кабинете логистики нажимают "Доставлено"
|
||||
13. Фулфилмент принимает товар и отмечает "Принято"
|
||||
|
||||
---
|
||||
|
||||
## 6. 🤝 **СИСТЕМА ПАРТНЕРСТВА**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 13.2, строки 2156-2186
|
||||
|
||||
### 6.1 Два способа установления партнерства
|
||||
|
||||
**СПОСОБ 1: Автоматическое партнерство**
|
||||
|
||||
1. Пользователь А указывает email организации Б при создании поставки/заявки
|
||||
2. Система проверяет: зарегистрирована ли организация с таким email
|
||||
3. Если ДА → автоматически создается связь Partnership
|
||||
4. Если НЕТ → отправляется приглашение на email
|
||||
5. При регистрации по приглашению → автоматически создается Partnership
|
||||
6. Обе организации видят друг друга в разделе "Партнеры"
|
||||
7. Могут создавать заказы друг другу
|
||||
|
||||
**СПОСОБ 2: Заявочная система**
|
||||
|
||||
1. Пользователь А идет в раздел "Партнеры"
|
||||
2. Нажимает "Добавить партнера"
|
||||
3. Указывает данные организации Б (ИНН, название)
|
||||
4. Система создает PartnershipRequest со статусом PENDING
|
||||
5. Организация Б получает уведомление о заявке
|
||||
6. Организация Б принимает (ACCEPTED) или отклоняет (REJECTED) заявку
|
||||
|
||||
### 6.2 Статусы заявок
|
||||
|
||||
**4 статуса PartnershipRequest**:
|
||||
|
||||
- **PENDING** - ожидает ответа
|
||||
- **ACCEPTED** - принята (создается Partnership)
|
||||
- **REJECTED** - отклонена
|
||||
- **CANCELLED** - отменена инициатором
|
||||
|
||||
---
|
||||
|
||||
## 7. 🔧 **ИНТЕГРАЦИЯ С УСЛУГАМИ**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 14.3, строки 2342-2349
|
||||
|
||||
### 7.1 5-шаговый workflow использования услуг
|
||||
|
||||
1. **Селлер выбирает услугу** из каталога при создании рецептуры
|
||||
2. **Система включает услугу** в состав заказа
|
||||
3. **Фулфилмент получает задание** на выполнение услуги
|
||||
4. **Исполнитель применяет услугу** к товару согласно технологии
|
||||
5. **Система создает готовый продукт** с учетом всех услуг
|
||||
|
||||
### 7.2 Связь с рецептурами
|
||||
|
||||
**Архитектура интеграции**:
|
||||
|
||||
```
|
||||
СЕЛЛЕР (создание поставки)
|
||||
└── Рецептура
|
||||
├── Товар (от поставщика)
|
||||
├── Услуги фулфилмента ← Каталог услуг
|
||||
├── Расходники селлера
|
||||
└── Расходники фулфилмента
|
||||
↓
|
||||
ФУЛФИЛМЕНТ (обработка)
|
||||
├── Входящие поставки → Товары на складе
|
||||
└── Услуги → Выполнение по рецептуре
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 🚨 **КРИТИЧЕСКИЕ СИТУАЦИИ**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 24, строки 3151-3190
|
||||
|
||||
### 8.1 Отмена заказов на разных этапах
|
||||
|
||||
**ТИП 1: Отмена до подтверждения поставщиком**
|
||||
- Селлер может отменить заказ в статусе PENDING
|
||||
- Система меняет статус на CANCELLED
|
||||
- Уведомление поставщику об отмене
|
||||
|
||||
**ТИП 2: Отмена после подтверждения поставщиком**
|
||||
- Требуется согласие поставщика
|
||||
- Возможны штрафные санкции
|
||||
- Согласование через мессенджер
|
||||
|
||||
**ТИП 3: Отмена во время транспортировки**
|
||||
- Связь с логистикой для возврата груза
|
||||
- Дополнительные транспортные расходы
|
||||
- Перерасчет стоимости
|
||||
|
||||
**ТИП 4: Отмена после доставки**
|
||||
- Процедура возврата товара
|
||||
- Контроль качества возвращаемого товара
|
||||
- Возмещение понесенных расходов
|
||||
|
||||
### 8.2 Алгоритм частичной доставки
|
||||
|
||||
**ШАГ 1: Выявление недостачи**
|
||||
- Фулфилмент сверяет план и факт
|
||||
- Фиксирует недостающие позиции
|
||||
- Уведомляет всех участников
|
||||
|
||||
**ШАГ 2: Принятие решения**
|
||||
- Селлер выбирает: ждать доставку или принять частично
|
||||
- Поставщик объясняет причины недостачи
|
||||
- Согласование дальнейших действий
|
||||
|
||||
**ШАГ 3: Обработка частичной доставки**
|
||||
- Система разделяет заказ на выполненную и невыполненную части
|
||||
- Перерасчет стоимости и логистики
|
||||
- Создание нового заказа на недостающее
|
||||
|
||||
**ШАГ 4: Документооборот**
|
||||
- Корректировка документов
|
||||
- Фиксация фактических показателей
|
||||
- Закрытие или продление заказа
|
||||
|
||||
---
|
||||
|
||||
## 9. 🛠️ **ПРОТОКОЛЫ РАЗРАБОТКИ**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел "Обязательная последовательность", строки 41-49
|
||||
|
||||
### 9.1 7-шаговый workflow разработки
|
||||
|
||||
1. **Читать rules-complete.md** - перед любым изменением кода
|
||||
2. **Следовать правилам взаимодействия** - честность и прозрачность
|
||||
3. **Проверить специфичные правила кабинета** - если работа с конкретным типом организации
|
||||
4. **Использовать TodoWrite** - для планирования задач
|
||||
5. **Следовать техническим правилам** - GraphQL, TypeScript, система партнерства
|
||||
6. **Проверять реализацию** - соответствие правилам и архитектуре
|
||||
7. **Проводить финальную проверку** - качество и корректность результата
|
||||
|
||||
### 9.2 Протокол высокой сложности
|
||||
|
||||
**3-этапный процесс для сложных задач**:
|
||||
|
||||
1. **СТОП! ГЛУБОКИЙ АНАЛИЗ** - уточнить все требования у пользователя
|
||||
2. **ИССЛЕДОВАНИЕ** - изучить все связанные файлы параллельно
|
||||
3. **ДЕТАЛЬНЫЙ ПЛАН** - с промежуточными проверками и rollback точками
|
||||
|
||||
---
|
||||
|
||||
## 10. 📊 **СИСТЕМА УЧЕТА ДВИЖЕНИЯ ТОВАРОВ**
|
||||
|
||||
> **Источник**: rules-complete.md, раздел 7, строки 742-770
|
||||
|
||||
### 10.1 Принципы учета
|
||||
|
||||
**ПРИНЦИП 1: Полная прозрачность**
|
||||
- Каждое движение товара фиксируется
|
||||
- Доступна история всех операций
|
||||
- Отчетность в реальном времени
|
||||
|
||||
**ПРИНЦИП 2: Двойной контроль**
|
||||
- План и факт сверяются системой
|
||||
- Выявление и анализ расхождений
|
||||
- Автоматические уведомления об отклонениях
|
||||
|
||||
**ПРИНЦИП 3: Статусная модель**
|
||||
- Каждый товар имеет четкий статус
|
||||
- Переходы между статусами контролируются
|
||||
- История изменений сохраняется
|
||||
|
||||
**ПРИНЦИП 4: Интеграция ролей**
|
||||
- Каждая роль видит релевантную информацию
|
||||
- Права доступа разграничены по функциям
|
||||
- Совместная работа через единую систему
|
||||
|
||||
**ПРИНЦИП 5: Автоматизация**
|
||||
- Минимум ручного ввода данных
|
||||
- Автоматические расчеты и уведомления
|
||||
- Система предотвращения ошибок
|
||||
|
||||
---
|
||||
|
||||
## 11. 🏪 **КАБИНЕТ ПОСТАВЩИКА**
|
||||
|
||||
> **Источник**: [rules-complete.md#10-кабинет-поставщика](./rules-complete.md#10--кабинет-поставщика)
|
||||
|
||||
### 11.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`
|
||||
- **Фильтрация**: В маркете по рынку поставщика
|
||||
|
||||
### 11.2 Основные возможности
|
||||
|
||||
**СОЗДАНИЕ КАРТОЧЕК**:
|
||||
|
||||
- **ТОВАР** - базовые товары поставщика
|
||||
- **РАСХОДНИКИ** - материалы и вспомогательные товары
|
||||
|
||||
### 11.3 Обязательные поля карточки
|
||||
|
||||
**Базовые параметры**:
|
||||
|
||||
- Фото (система загрузки и управления изображениями)
|
||||
- Название
|
||||
- Автоматическая генерация артикула СФ
|
||||
- Описание
|
||||
- Количество предметов в единицах
|
||||
- Количество комплектов (если применимо)
|
||||
- Категория (28 предустановленных + специализированные для расходников)
|
||||
- Бренд, Цвет, Размер/объем, Вес, Габариты, Материал
|
||||
- Цена за единицу и за комплект
|
||||
- Заказано, В пути, Остаток, Продано
|
||||
|
||||
### 11.4 Отображение информации в карточках
|
||||
|
||||
**Каждая карточка содержит**:
|
||||
|
||||
- Основное изображение
|
||||
- Название и артикул СФ
|
||||
- Цена за единицу/комплект
|
||||
- Категория и статус активности
|
||||
- Данные о движении: остаток, заказано, в пути, продано
|
||||
- Индикаторы низких остатков
|
||||
|
||||
### 11.5 Статистика поставщика
|
||||
|
||||
**Блок статистики включает**:
|
||||
|
||||
- **ТОВАРЫ**: Общая статистика товаров поставщика
|
||||
- **РАСХОДНИКИ**: Материалы и вспомогательные товары
|
||||
- Классифицируются при заказе в зависимости от заказчика
|
||||
- Общая статистика по всем расходникам
|
||||
|
||||
---
|
||||
|
||||
## 12. 🏠 **ОБЩИЕ ПРАВИЛА КАБИНЕТОВ**
|
||||
|
||||
> **Источник**: [rules-complete.md#8-общие-правила-кабинетов](./rules-complete.md#8--общие-правила-кабинетов)
|
||||
|
||||
### 12.1 Универсальная структура кабинетов
|
||||
|
||||
**ВСЕ ТИПЫ КАБИНЕТОВ** включают следующие обязательные разделы:
|
||||
|
||||
#### 12.1.1 Страница "Главная"
|
||||
|
||||
**СТАТУС**: Реализовано
|
||||
**ДОСТУП**: Через навигацию в sidebar для всех типов кабинетов
|
||||
**СОДЕРЖАНИЕ**: Универсальная страница с типо-зависимыми компонентами
|
||||
|
||||
**ПРАВИЛА**:
|
||||
|
||||
- **ОБЯЗАТЕЛЬНО**: Каждый тип кабинета должен иметь страницу "Главная"
|
||||
- **НАВИГАЦИЯ**: Доступ через кнопку в sidebar (первая в списке)
|
||||
- **УНИВЕРСАЛЬНОСТЬ**: Одинаковая структура навигации для всех кабинетов
|
||||
- **РОУТ**: `/home` с универсальным компонентом HomePageWrapper
|
||||
- **КОМПОНЕНТЫ**: 4 типо-зависимых компонента: SellerHomePage, FulfillmentHomePage, WholesaleHomePage, LogistHomePage
|
||||
|
||||
#### 12.1.2 Раздел "Экономика"
|
||||
|
||||
**СТАТУС**: Реализовано в системе
|
||||
**РАСПОЛОЖЕНИЕ**: Перед настройками в каждом кабинете
|
||||
**СОДЕРЖАНИЕ**: Пустые разделы-заглушки с пометкой "будет добавлен позже"
|
||||
|
||||
**ПРАВИЛА**:
|
||||
|
||||
- **ОБЯЗАТЕЛЬНО**: Каждый кабинет имеет раздел "Экономика"
|
||||
- **РОУТ**: `/economics` с универсальным компонентом EconomicsPageWrapper
|
||||
- **КОМПОНЕНТЫ**: 4 типо-зависимых компонента экономики: SellerEconomicsPage, FulfillmentEconomicsPage, WholesaleEconomicsPage, LogistEconomicsPage
|
||||
- **КНОПКА**: "Экономика" в sidebar навигации перед настройками
|
||||
- **БЕЗОПАСНОСТЬ**: Проверки доступа и безопасности в экономических компонентах
|
||||
|
||||
#### 12.1.3 Общие разделы для всех кабинетов
|
||||
|
||||
**УНИВЕРСАЛЬНЫЕ РАЗДЕЛЫ** (доступны всем типам):
|
||||
|
||||
- 🏠 **Главная** - основная страница кабинета (реализовано)
|
||||
- 🛒 **Маркет** - просмотр и заказ товаров
|
||||
- 🤝 **Партнеры** - управление контрагентами
|
||||
- 💬 **Мессенджер** - внутренняя связь
|
||||
- 💰 **Экономика** - финансовая аналитика (реализовано)
|
||||
- ⚙️ **Настройки** - профиль и конфигурация
|
||||
|
||||
**СПЕЦИАЛИЗИРОВАННЫЕ РАЗДЕЛЫ** (зависят от типа кабинета):
|
||||
|
||||
- Определяются в соответствующих разделах каждого кабинета
|
||||
|
||||
### 12.2 Правила sidebar навигации
|
||||
|
||||
#### 12.2.1 Структура навигации
|
||||
|
||||
**ОБЩИЙ ПРИНЦИП**:
|
||||
|
||||
- Условное отображение: `{user?.organization?.type === "TYPE" && (...)}`
|
||||
- Адаптивность: сворачиваемый sidebar с `getSidebarMargin()`
|
||||
- Состояния активности: подсветка текущего раздела
|
||||
|
||||
**ПОРЯДОК РАЗДЕЛОВ В SIDEBAR**:
|
||||
|
||||
1. 🏠 **Главная** (реализовано для всех)
|
||||
2. **Специализированные разделы** (зависят от типа кабинета)
|
||||
3. 🛒 **Маркет** (универсальный)
|
||||
4. 🤝 **Партнеры** (универсальный)
|
||||
5. 💬 **Мессенджер** (универсальный)
|
||||
6. 💰 **Экономика** (универсальный, реализовано)
|
||||
7. ⚙️ **Настройки** (универсальный)
|
||||
8. **Выход** (универсальный)
|
||||
|
||||
#### 12.2.2 Типо-зависимая логика
|
||||
|
||||
**АДАПТИВНЫЙ РОУТИНГ**:
|
||||
|
||||
```typescript
|
||||
// Пример: кнопка "Поставки" ведет на разные страницы
|
||||
const handleSuppliesClick = () => {
|
||||
switch (user?.organization?.type) {
|
||||
case 'FULFILLMENT':
|
||||
router.push('/fulfillment-supplies')
|
||||
break
|
||||
case 'SELLER':
|
||||
router.push('/supplies')
|
||||
break
|
||||
case 'WHOLESALE':
|
||||
router.push('/supplies')
|
||||
break
|
||||
case 'LOGIST':
|
||||
router.push('/logistics-orders')
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 📋 **КАТЕГОРИИ ТОВАРОВ И РАСХОДНИКОВ**
|
||||
|
||||
> **Источник**: [rules-complete.md#22-категории-товаров-и-расходников](./rules-complete.md#22--категории-товаров-и-расходников)
|
||||
|
||||
### 13.1 Полный список 28 универсальных категорий товаров
|
||||
|
||||
1. Одежда и обувь
|
||||
2. Косметика и парфюмерия
|
||||
3. Дом и сад
|
||||
4. Детские товары
|
||||
5. Спорт и отдых
|
||||
6. Электроника
|
||||
7. Книги
|
||||
8. Здоровье
|
||||
9. Автотовары
|
||||
10. Строительство и ремонт
|
||||
11. Продукты питания
|
||||
12. Зоотовары
|
||||
13. Дача, сад и огород
|
||||
14. Канцелярские товары
|
||||
15. Хобби и творчество
|
||||
16. Украшения и аксессуары
|
||||
17. Сумки и чемоданы
|
||||
18. Техника для дома
|
||||
19. Музыкальные инструменты
|
||||
20. Игры и игрушки
|
||||
21. Мебель
|
||||
22. Товары для красоты
|
||||
23. Бытовая химия
|
||||
24. Товары для путешествий
|
||||
25. Медицинские товары
|
||||
26. Религиозные товары
|
||||
27. Антиквариат и коллекционирование
|
||||
28. Прочие товары
|
||||
|
||||
### 13.2 12 специализированных категорий расходников
|
||||
|
||||
#### 🎁 **1. УПАКОВКА И ЗАЩИТА**
|
||||
|
||||
- Коробки (различных размеров)
|
||||
- Пакеты (полиэтиленовые, бумажные, фирменные)
|
||||
- Пузырчатая пленка, воздушные подушки
|
||||
- Стрейч-пленка, гофрокартон
|
||||
- Паллетная пленка, защитные уголки
|
||||
|
||||
#### 🏷️ **2. МАРКИРОВКА И ИДЕНТИФИКАЦИЯ**
|
||||
|
||||
- Этикетки (адресные, штрих-код, QR-код)
|
||||
- Бирки (ценники, размерники)
|
||||
- Стикеры и наклейки
|
||||
- Маркеры и ручки
|
||||
- Штампы и печати, термоэтикетки
|
||||
|
||||
#### 🔧 **3. КРЕПЕЖ И СОЕДИНЕНИЕ**
|
||||
|
||||
- Скотч (прозрачный, цветной, армированный)
|
||||
- Клей и клеевые составы
|
||||
- Стяжки пластиковые
|
||||
- Степлер и скобы
|
||||
- Веревки и шнуры, стрейч-лента
|
||||
|
||||
#### 📄 **4. ДОКУМЕНТООБОРОТ И ВКЛАДЫШИ**
|
||||
|
||||
- Накладные и сопроводительные документы
|
||||
- Инструкции по эксплуатации
|
||||
- Гарантийные талоны
|
||||
- Рекламные буклеты, визитки и флаеры
|
||||
- Благодарственные письма, купоны и промокоды
|
||||
|
||||
#### 🧼 **5. ГИГИЕНА И БЕЗОПАСНОСТЬ**
|
||||
|
||||
- Перчатки (латексные, нитриловые)
|
||||
- Маски и респираторы
|
||||
- Антисептики и дезинфекторы
|
||||
- Салфетки и тряпки
|
||||
- Фартуки и халаты, бахилы
|
||||
|
||||
#### 🛠️ **6. ИНСТРУМЕНТЫ И ПРИСПОСОБЛЕНИЯ**
|
||||
|
||||
- Ножи и резаки, ножницы
|
||||
- Линейки и рулетки
|
||||
- Упаковочные машины (ленточные)
|
||||
- Дозаторы скотча
|
||||
- Пистолеты для термоклея
|
||||
- Весы и мерная тара
|
||||
|
||||
#### 🎨 **7. БРЕНДИНГ И ДИЗАЙН**
|
||||
|
||||
- Фирменные пакеты с логотипом
|
||||
- Брендированные коробки
|
||||
- Цветная упаковочная бумага
|
||||
- Ленты и банты
|
||||
- Наклейки с логотипом компании
|
||||
- Подарочная упаковка
|
||||
|
||||
#### ⚡ **8. СПЕЦИАЛИЗИРОВАННЫЕ МАТЕРИАЛЫ**
|
||||
|
||||
- Антистатические пакеты
|
||||
- Влагопоглотители
|
||||
- Температурные индикаторы
|
||||
- Хрупкие наклейки
|
||||
- Пломбы и пломбировочные материалы
|
||||
- Защита от краж (магнитные датчики)
|
||||
|
||||
#### 🏪 **9. ТОРГОВОЕ ОБОРУДОВАНИЕ**
|
||||
|
||||
- Манекены и вешалки
|
||||
- Ценникодержатели
|
||||
- Подставки и стойки
|
||||
- Корзины и тележки
|
||||
- Зеркала примерочные
|
||||
- Освещение витрин
|
||||
|
||||
#### 🚚 **10. ЛОГИСТИКА И СКЛАДИРОВАНИЕ**
|
||||
|
||||
- Паллеты и поддоны
|
||||
- Контейнеры и ящики
|
||||
- Стеллажные системы
|
||||
- Погрузочные ремни
|
||||
- Защитные чехлы
|
||||
- Адресные ярлыки для груза
|
||||
|
||||
#### 💻 **11. ТЕХНИЧЕСКИЕ РАСХОДНИКИ**
|
||||
|
||||
- Картриджи для принтеров
|
||||
- Термоголовки, красящие ленты
|
||||
- Батарейки для сканеров
|
||||
- Чистящие средства для техники
|
||||
- Запчасти для упаковочного оборудования
|
||||
|
||||
#### 🎪 **12. СЕЗОННЫЕ И ПРАЗДНИЧНЫЕ**
|
||||
|
||||
- Новогодняя упаковка
|
||||
- Подарочные мешки
|
||||
- Праздничные ленты
|
||||
- Тематические наклейки
|
||||
- Открытки и поздравления
|
||||
- Сезонная упаковочная бумага
|
||||
|
||||
**ПРИМЕЧАНИЕ**: Данные категории являются рекомендательными и могут быть адаптированы под специфику конкретного поставщика расходников.
|
||||
|
||||
---
|
||||
|
||||
## 📊 **СТАТИСТИКА ПРОЦЕССОВ**
|
||||
|
||||
### По объему (строки):
|
||||
- **UI процессы селлера**: ~942 строки (самый объемный)
|
||||
- **Процесс создания продукта**: ~175 строк (самый детализированный)
|
||||
- **Категории товаров и расходников**: ~141 строка (классификационная система)
|
||||
- **Кабинет поставщика**: ~103 строки (процессы РЫНОК vs МАРКЕТ)
|
||||
- **Общие правила кабинетов**: ~96 строк (универсальные процессы)
|
||||
- **Workflow поставок**: ~45 строк (самый критичный)
|
||||
- **Система партнерства**: ~31 строка
|
||||
- **Workflow логистики**: ~27 строк
|
||||
- **Workflow фулфилмента**: ~20 строк
|
||||
|
||||
### По ролям:
|
||||
- **Селлер**: 6 процессов
|
||||
- **Поставщик**: 5 процессов
|
||||
- **Фулфилмент**: 5 процессов
|
||||
- **Логистика**: 3 процесса
|
||||
- **Универсальные**: 3 процесса
|
||||
|
||||
### По критичности:
|
||||
- **Критические**: Workflow поставок, Создание продукта, РЫНОК vs МАРКЕТ
|
||||
- **Важные**: UI процессы, Категории товаров, Общие правила кабинетов
|
||||
- **Вспомогательные**: Система партнерства, Учет движения, Протоколы разработки
|
||||
|
||||
---
|
||||
|
||||
**Дата создания**: Август 2025
|
||||
**Общий объем**: 1733 строки процессов (фактический подсчет)
|
||||
**Файл содержит**: 1805 строк всего (включая навигацию и статистику)
|
||||
**Источник**: [rules-complete.md](./rules-complete.md)
|
||||
|
||||
✅ **СТАТУС**: ПОЛНОСТЬЮ ЗАПОЛНЕН - все ключевые процессы добавлены
|
||||
|
||||
**Связанные файлы**:
|
||||
- [rules-complete.md](./rules-complete.md) - Основной файл с бизнес-правилами
|
||||
- [interaction-integrity-rules.md](./interaction-integrity-rules.md) - Методология работы
|
||||
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила
|
Reference in New Issue
Block a user