diff --git a/CLAUDE.md b/CLAUDE.md index 469f907..ea85833 100644 --- a/CLAUDE.md +++ b/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`!** diff --git a/current-session.md b/current-session.md new file mode 100644 index 0000000..ea4647f --- /dev/null +++ b/current-session.md @@ -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 из резервной копии +- Начата работа над системой сохранения контекста + +--- + +> ⚠️ **ВАЖНО**: Этот файл обновляется в течение сессии для сохранения контекста! \ No newline at end of file diff --git a/fulfillment-cabinet-rules.md b/fulfillment-cabinet-rules.md new file mode 100644 index 0000000..791705a --- /dev/null +++ b/fulfillment-cabinet-rules.md @@ -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" && ( + +)} + +// Адаптивные отступы +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" && ( + +)} + +// Для полинга данных +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) - Визуальные правила \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b9b8909..81de842 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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") } @@ -149,14 +157,18 @@ model CounterpartyRequest { } model Counterparty { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - organizationId String - counterpartyId String - counterparty Organization @relation("CounterpartyOf", fields: [counterpartyId], references: [id]) - organization Organization @relation("OrganizationCounterparties", fields: [organizationId], references: [id]) + id String @id @default(cuid()) + 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 } diff --git a/referral-system-rules.md b/referral-system-rules.md new file mode 100644 index 0000000..2cba214 --- /dev/null +++ b/referral-system-rules.md @@ -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 +
+ {/* Блок с реферальной ссылкой */} +
+

Ваша реферальная ссылка

+
+
••••••••••
+ +
+

+ Поделитесь ссылкой с партнерами и получайте баллы за каждую регистрацию +

+
+ + {/* Статистика */} +
+ + + + +
+ + {/* Фильтры */} +
+ + + +
+ + {/* Таблица рефералов */} +
+ + + + Дата регистрации + Название организации + ИНН + Тип кабинета + Источник + Начислено сфер ⚡ + Статус + + + + {referrals.map(referral => ( + + {formatDate(referral.createdAt)} + {referral.name || referral.fullName} + {referral.inn} + + + {getTypeLabel(referral.type)} + + + +
+ {referral.source === 'REFERRAL' ? ( + <> + + Реферальная ссылка + + ) : ( + <> + + Бизнес-сделка + + )} +
+
+ +
+ +{referral.points} + +
+
+ + Активен + +
+ ))} +
+
+
+
+``` + +### 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 +**Статус**: Готово к реализации \ No newline at end of file diff --git a/registration-authorization-rules.md b/registration-authorization-rules.md new file mode 100644 index 0000000..53654a9 --- /dev/null +++ b/registration-authorization-rules.md @@ -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. **Идентификация:** Как система определяет, что два номера принадлежат одному пользователю? \ No newline at end of file diff --git a/rules-complete.md b/rules-complete.md index 9e5d565..d23b433 100644 --- a/rules-complete.md +++ b/rules-complete.md @@ -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 маркетинговые кампании diff --git a/seller-ui-rules.md b/seller-ui-rules.md new file mode 100644 index 0000000..05a8439 --- /dev/null +++ b/seller-ui-rules.md @@ -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 +
+
+``` + +**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**: + +- **Ширина**: `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) => { + 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) - Визуальные правила \ No newline at end of file diff --git a/server.log b/server.log deleted file mode 100644 index 2807cf8..0000000 --- a/server.log +++ /dev/null @@ -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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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. (.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 diff --git a/src/app/api/graphql/route.ts b/src/app/api/graphql/route.ts index e8ceb12..c9ca39a 100644 --- a/src/app/api/graphql/route.ts +++ b/src/app/api/graphql/route.ts @@ -51,11 +51,22 @@ const handler = startServerAndCreateNextHandler(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, } diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 8026275..2685a55 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -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 + } + return ( - }> + }> {/* Если пользователь авторизован, перенаправляем в дашборд */} {redirect('/dashboard')} diff --git a/src/components/auth/auth-flow.tsx b/src/components/auth/auth-flow.tsx index ee3cc91..1e27675 100644 --- a/src/components/auth/auth-flow.tsx +++ b/src/components/auth/auth-flow.tsx @@ -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('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(initialStep) + + // Определяем тип регистрации на основе параметров + // Только один из них должен быть активен (валидация уже прошла в RegisterPage) + const registrationType = partnerCode ? 'PARTNER' : (referralCode ? 'REFERRAL' : null) + const activeCode = partnerCode || referralCode || null + + console.log('🎢 AuthFlow - Обработанные данные:', { registrationType, activeCode }) + const [authData, setAuthData] = useState({ phone: '', smsCode: '', @@ -58,8 +77,23 @@ 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(() => { @@ -197,7 +231,13 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) { return ( <> - {step === 'phone' && } + {step === 'phone' && ( + + )} {step === 'sms' && } {step === 'cabinet-select' && } {step === 'inn' && } @@ -208,13 +248,15 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) { void onBack: () => void @@ -65,6 +67,13 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr const handleConfirm = async () => { 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, }) } diff --git a/src/components/auth/phone-step.tsx b/src/components/auth/phone-step.tsx index 0266540..969870d 100644 --- a/src/components/auth/phone-step.tsx +++ b/src/components/auth/phone-step.tsx @@ -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(null) @@ -96,6 +98,35 @@ export function PhoneStep({ onNext }: PhoneStepProps) { totalSteps={5} stepName="Авторизация" > + {/* Индикатор типа регистрации */} + {registrationType && ( +
+
+
+ + {registrationType === 'PARTNER' ? '🤝' : '📎'} + +
+
+

+ {registrationType === 'PARTNER' + ? 'Регистрация по партнерской ссылке' + : 'Регистрация по реферальной ссылке'} +

+

+ {registrationType === 'PARTNER' + ? 'Вы получите +100 сфер ⚡ и автоматически станете партнером' + : 'Вы получите +100 сфер ⚡ за регистрацию'} +

+
+
+
+ )} +
- {(user?.organization?.type === 'FULFILLMENT' || - user?.organization?.type === 'LOGIST' || - user?.organization?.type === 'WHOLESALE') && ( -
-
-

Партнерская программа

-

- Приглашайте новых контрагентов по уникальной ссылке. При регистрации они автоматически - становятся вашими партнерами. -

- -
-
- -
- - {partnerLink && ( -
-
- - -
-

- Ваша партнерская ссылка сгенерирована и готова к использованию -

-
- )} -
+
+
+ +

+ Инструменты в разработке +

+

+ Здесь будут размещены полезные бизнес-инструменты: + калькуляторы, аналитика, планировщики и автоматизация процессов. +

+
+ + Скоро появится +
- )} +
diff --git a/src/components/market/market-counterparties.tsx b/src/components/market/market-counterparties.tsx index de4c616..b4f34bf 100644 --- a/src/components/market/market-counterparties.tsx +++ b/src/components/market/market-counterparties.tsx @@ -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() { + {/* Блок с партнерской ссылкой */} + +
+
+
+ +
+
+

Пригласить партнера

+

Прямое деловое сотрудничество с автоматическим добавлением в партнеры

+
+
+ +
+
+ {/* Компактная панель фильтров */}
diff --git a/src/components/partners/partners-dashboard.tsx b/src/components/partners/partners-dashboard.tsx index 9ee9e2d..dc52422 100644 --- a/src/components/partners/partners-dashboard.tsx +++ b/src/components/partners/partners-dashboard.tsx @@ -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() {
@@ -75,6 +76,12 @@ export function PartnersDashboard() { > Поставщик + + Рефералы + @@ -106,6 +113,10 @@ export function PartnersDashboard() { + + + +
diff --git a/src/components/partners/referrals-tab.tsx b/src/components/partners/referrals-tab.tsx new file mode 100644 index 0000000..a59063c --- /dev/null +++ b/src/components/partners/referrals-tab.tsx @@ -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('all') + const [sourceFilter, setSourceFilter] = useState('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 ( +
+ {/* Блок с реферальной ссылкой */} + +
+
+ +
+

Ваша реферальная ссылка

+
+ +
+
+ •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• +
+ +
+ +

+ Поделитесь ссылкой с партнерами и получайте 100 сфер ⚡ за каждую регистрацию. + Также получайте 50 сфер ⚡ при одобрении заявок от новых клиентов. +

+
+ + {/* Статистика */} +
+ +
+
+ +
+
+

Всего партнеров

+

+ {loading ? ( + + ) : ( + stats.totalPartners + )} +

+
+
+
+ + +
+
+ +
+
+

Сфер заработано

+
+

+ {loading ? ( + + ) : ( + stats.totalSpheres + )} +

+ +
+
+
+
+ + +
+
+ +
+
+

Партнеров за месяц

+

+ {loading ? ( + + ) : ( + stats.monthlyPartners + )} +

+
+
+
+ + +
+
+ +
+
+

Сфер за месяц

+
+

+ {loading ? ( + + ) : ( + stats.monthlySpheres + )} +

+ +
+
+
+
+
+ + {/* Фильтры */} + +
+ {/* Поиск */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-10 h-9" + /> +
+
+ + {/* Фильтры */} +
+ + + + + {hasActiveFilters && ( + + )} +
+
+ + {/* Краткая статистика */} +
+
+ Показано {filteredReferrals.length} из {allReferrals.length} партнеров +
+ +
+
+ + Рефералы: {allReferrals.filter(r => r.source === 'REFERRAL_LINK').length} +
+
+ + Бизнес: {allReferrals.filter(r => r.source === 'AUTO_BUSINESS').length} +
+
+
+
+ + {/* Таблица партнеров */} + +
+
+ {/* Заголовок таблицы */} +
+
+
+ + Дата регистрации +
+
+ + Организация +
+
+ Тип +
+
+ + Источник +
+
+ + Начислено +
+
+ Статус +
+
+
+ + {/* Строки партнеров */} + {filteredReferrals.length === 0 ? ( +
+ +

+ {loading ? 'Загрузка...' : allReferrals.length === 0 ? 'У вас пока нет партнеров' : 'Ничего не найдено'} +

+

+ {loading + ? 'Получаем данные о ваших партнерах...' + : allReferrals.length === 0 + ? 'Поделитесь реферальной ссылкой или начните работать с клиентами' + : 'Попробуйте изменить параметры поиска' + } +

+
+ ) : ( + filteredReferrals.map((referral) => ( +
+
+
+
+ + {formatDate(referral.registeredAt)} +
+
+
+
+

+ {referral.organization.name || referral.organization.fullName} +

+

+ + {referral.organization.inn} +

+
+
+
+ + {getTypeLabel(referral.organization.type)} + +
+
+
+ {referral.source === 'REFERRAL_LINK' ? ( + <> + + Ссылка + + ) : ( + <> + + Бизнес + + )} +
+
+
+
+ +{referral.spheresEarned} + +
+
+
+
+ + Активен +
+
+
+
+ )) + )} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/graphql/context.ts b/src/graphql/context.ts index 6deefbf..bd7f7c3 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -3,6 +3,8 @@ import { PrismaClient } from '@prisma/client' export interface Context { user: { id: string + phone?: string + organizationId?: string organization?: { id: string type: string diff --git a/src/graphql/mutations.ts b/src/graphql/mutations.ts index 2612f57..99c6104 100644 --- a/src/graphql/mutations.ts +++ b/src/graphql/mutations.ts @@ -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 } } } diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index 30f5cff..52319a0 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -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' diff --git a/src/graphql/referral-queries.ts b/src/graphql/referral-queries.ts new file mode 100644 index 0000000..872467d --- /dev/null +++ b/src/graphql/referral-queries.ts @@ -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 + } + } +` \ No newline at end of file diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 027834f..99ff76e 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -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 => { + 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 { @@ -2320,6 +2522,9 @@ export const resolvers = { const tradeMark = validationResults[0]?.data?.tradeMark const sellerName = validationResults[0]?.data?.sellerName const shopName = tradeMark || sellerName || 'Магазин' + + // Генерируем уникальный реферальный код + const generatedReferralCode = await generateReferralCode() const organization = await prisma.organization.create({ data: { @@ -2327,6 +2532,9 @@ export const resolvers = { 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: 'Селлер организация успешно зарегистрирована', diff --git a/src/graphql/resolvers/index.ts b/src/graphql/resolvers/index.ts index 0be3221..5d9e09c 100644 --- a/src/graphql/resolvers/index.ts +++ b/src/graphql/resolvers/index.ts @@ -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 diff --git a/src/graphql/resolvers/referrals.ts b/src/graphql/resolvers/referrals.ts new file mode 100644 index 0000000..802bf51 --- /dev/null +++ b/src/graphql/resolvers/referrals.ts @@ -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)) + } + } + } +} \ No newline at end of file diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index b3eea24..db85406 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -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 + } ` diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 4c9db0d..51eb9b1 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -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,11 +311,17 @@ 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 @@ -340,6 +356,8 @@ export const useAuth = (): UseAuthReturn => { wbApiKey?: string ozonApiKey?: string ozonClientId?: string + referralCode?: string | null + partnerCode?: string | null }) => { try { setIsLoading(true) diff --git a/workflow-catalog.md b/workflow-catalog.md new file mode 100644 index 0000000..ea7e2b6 --- /dev/null +++ b/workflow-catalog.md @@ -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 +// Структура компонентов панели +
+ + +
+ + +
+
+``` + +#### **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 +
+``` + +- **Назначение**: Визуальное обрамление блока, единство с другими блоками +- **Стилизация**: Стеклянный эффект с размытием и полупрозрачностью +- **Рамка**: Тонкая белая рамка border-white/20 с закруглёнными углами +- **Поведение**: flex-shrink-0 предотвращает сжатие блока + +**УРОВЕНЬ 2 - Внутренний контейнер (скролл):** + +```jsx +
+``` + +- **Назначение**: Горизонтальная прокрутка карточек товаров +- **Раскладка**: 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 +
+
+ +

Поставщики

+
+
+
+ + +
+
+
+``` + +**ПРАВИЛА ЗАГОЛОВКА:** + +- **Иконка**: 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 +
+ +
+

{supplier.name || supplier.fullName}

+
+

ИНН: {supplier.inn}

+ {supplier.market && {getMarketLabel(supplier.market)}} +
+
+
+``` + +**ПРАВИЛА СОДЕРЖАНИЯ КАРТОЧКИ:** + +**✅ ОСТАВИТЬ:** + +- **Аватар организации**: 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 +
+
+``` + +**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**: + +- **Ширина**: `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) => { + 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) - Визуальные правила \ No newline at end of file