Реализация реферальной системы и улучшение системы авторизации

- Добавлена полная реферальная система с GraphQL резолверами и UI компонентами
- Улучшена система регистрации с поддержкой ВКонтакте и реферальных ссылок
- Обновлена схема Prisma для поддержки реферальной системы
- Добавлены новые файлы документации правил системы
- Улучшена система партнерства и контрагентов
- Обновлены компоненты авторизации для поддержки новых функций
- Удален устаревший server.log

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-11 09:47:00 +03:00
parent af16402f22
commit 8f7ec70fe6
28 changed files with 5827 additions and 4313 deletions

View File

@ -5,11 +5,14 @@
### Обязательные для чтения: ### Обязательные для чтения:
- **`rules-complete.md`** - основные бизнес-правила (ВСЕГДА читать первым) - **`rules-complete.md`** - основные бизнес-правила (ВСЕГДА читать первым)
- **`workflow-catalog.md`** - каталог всех бизнес-процессов системы
### Специфичные правила по кабинетам: ### Специфичные правила по кабинетам:
- **`wholesale-cabinet-rules.md`** - при работе с кабинетом поставщика - **`wholesale-cabinet-rules.md`** - при работе с кабинетом поставщика
- **`logist-cabinet-rules.md`** - при работе с кабинетом логистики - **`logist-cabinet-rules.md`** - при работе с кабинетом логистики
- **`fulfillment-cabinet-rules.md`** - при работе с кабинетом фулфилмента
- **`seller-ui-rules.md`** - при работе с UI/UX кабинета селлера
- **`visual-design-rules.md`** - при работе с UI/UX - **`visual-design-rules.md`** - при работе с UI/UX
### Правила взаимодействия: ### Правила взаимодействия:
@ -20,6 +23,9 @@
- Упоминание "поставщик", "wholesale", "/warehouse", "/supplier-orders" → читать wholesale-cabinet-rules.md - Упоминание "поставщик", "wholesale", "/warehouse", "/supplier-orders" → читать wholesale-cabinet-rules.md
- Упоминание "логистика", "доставка", "logist", "/logistics-requests", "/routes" → читать logist-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 - Упоминание "дизайн", "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`!** **Этот файл служит для корректной работы system-reminder'ов. Все детальные правила находятся в `rules-complete.md`!**

106
current-session.md Normal file
View File

@ -0,0 +1,106 @@
# ТЕКУЩАЯ СЕССИЯ РАБОТЫ
> 📅 Дата начала: 2025-08-10
> 🎯 Цель: Отслеживание контекста и прогресса текущей работы
---
## 📋 АКТИВНЫЕ ЗАДАЧИ
### Текущая задача:
- **Что делаем**: Создание структуры для сохранения контекста
- **Статус**: В процессе
- **Начато**: 2025-08-10
### Очередь задач:
1. ✅ Восстановить rules-complete.md из backup
2. 🔄 Создать систему сохранения контекста
3. ⏳ [Следующие задачи будут добавлены]
---
## 🔧 ТЕКУЩИЙ КОНТЕКСТ ПРОЕКТА
### О проекте SFERA:
**Тип**: Система управления складами и поставками (B2B маркетплейс)
**Технологии**:
- Frontend: Next.js 15.4.1 (React 19), TypeScript, Tailwind CSS
- Backend: GraphQL (Apollo Server), Prisma ORM
- База данных: PostgreSQL (через Prisma)
- UI: Radix UI, Lucide icons, shadcn/ui компоненты
### Архитектура:
- **4 типа кабинетов**: SELLER (селлер), FULFILLMENT (фулфилмент), WHOLESALE (поставщик), LOGIST (логистика)
- **Типы предметов**: PRODUCT (товар), CONSUMABLE (расходники), DEFECT (брак), FINISHED_PRODUCT (готовый продукт)
- **Workflow поставок**: 8 статусов от PENDING до DELIVERED
- **Система партнерства**: через модель Counterparty
### Ключевые особенности:
- Строгая типизация GraphQL + TypeScript
- Ролевая модель доступа (проверки на уровне резолверов)
- Модульная структура компонентов по кабинетам
- Glass-эффекты и OKLCH цветовая система в UI
### Важные решения:
- Восстановлен файл rules-complete.md из backup-20250809-192625 (3,301 строк)
- Удалена испорченная версия (2,686 строк)
- Создана система сохранения контекста (current-session.md, task-template.md)
### Обнаруженные проблемы:
- Claude часто теряет контекст при длинных сессиях
- Необходима система для сохранения важной информации между сообщениями
### Согласованные подходы:
- Использовать TodoWrite для планирования
- Документировать все важные решения
- Следовать правилам из interaction-integrity-rules.md
- Всегда читать rules-complete.md перед изменениями
---
## 💡 ВАЖНЫЕ ОТКРЫТИЯ И РЕШЕНИЯ
### Структура правил системы:
- `rules-complete.md` - основные бизнес-правила
- `interaction-integrity-rules.md` - методология работы Claude
- `CLAUDE.md` - системные правила и напоминания
- Специфичные правила по кабинетам (wholesale, logist, fulfillment, seller)
---
## 🚀 КОМАНДЫ ДЛЯ ПРОВЕРКИ
```bash
# TypeScript проверка
npm run typecheck
# Линтинг
npm run lint
# Тесты
npm test
# Dev сервер
npm run dev
```
---
## 📝 ЗАМЕТКИ ДЛЯ СЛЕДУЮЩЕЙ СЕССИИ
- При продолжении работы ОБЯЗАТЕЛЬНО прочитать этот файл первым
- Проверить статус задач в TodoWrite
- Продолжить с последней незавершенной задачи
---
## 🔄 ИСТОРИЯ ИЗМЕНЕНИЙ
### 2025-08-10
- Создан файл current-session.md
- Восстановлен rules-complete.md из резервной копии
- Начата работа над системой сохранения контекста
---
> ⚠️ **ВАЖНО**: Этот файл обновляется в течение сессии для сохранения контекста!

View File

@ -0,0 +1,686 @@
# ПРАВИЛА КАБИНЕТА ФУЛФИЛМЕНТА (FULFILLMENT)
> ⚠️ **ВАЖНО**: Это файл с техническими деталями кабинета фулфилмента.
> Общие бизнес-правила находятся в **[rules-complete.md](./rules-complete.md)**
## Когда использовать этот файл:
- Работа с компонентами `/warehouse`, `/fulfillment-supplies`, `/services`, `/employees`
- GraphQL запросы для фулфилмента
- UI/UX специфика кабинета фулфилмента
- Технические детали реализации услуг и логистики
## 1. 🏭 СТРУКТУРА КАБИНЕТА ФУЛФИЛМЕНТА
### 1.1 Основные разделы
**ФУЛФИЛМЕНТ (`FULFILLMENT`)** имеет доступ к следующим разделам:
- **Склад** (`/warehouse`) - управление товарами по модулям
- **Поставки** (`/fulfillment-supplies`) - обработка поставок
- **Услуги** (`/services`) - управление услугами и расходниками
- **Сотрудники** (`/employees`) - управление персоналом
- **Статистика** (`/fulfillment-statistics`) - аналитика фулфилмента
- **Партнеры** (`/partners`) - управление контрагентами
- **Мессенджер** (`/messenger`) - связь с партнерами
- **Настройки** (`/settings`) - профиль и настройки
- **Экономика** (`/economics`) - финансовая аналитика
### 1.2 Навигация и роутинг
#### При входе в систему:
```typescript
switch (user?.organization?.type) {
case 'FULFILLMENT':
router.push('/warehouse') // Направляем на склад
break
}
```
#### Специальная логика роутинга:
- **МАКСИМАЛЬНЫЕ ПРАВА**: Доступ ко ВСЕМ разделам системы
- **АДАПТИВНАЯ НАВИГАЦИЯ**: Sidebar изменяется в зависимости от типа организации
- **СПЕЦИАЛЬНЫЙ РОУТИНГ**: `/fulfillment-supplies` вместо `/supplies`
> 📖 **Бизнес-логика роутинга**: См. [rules-complete.md#4-система-ролей-и-доступов](./rules-complete.md#4--система-ролей-и-доступов)
## 2. 🎨 UI/UX КОМПОНЕНТЫ
### 2.1 Dashboard компоненты
#### Основные компоненты кабинета:
- `FulfillmentWarehouseDashboard` - склад по модулям
- `FulfillmentSuppliesDashboard` - поставки фулфилмента
- `ServicesManagement` - управление услугами
- `FulfillmentStatistics` - статистика и аналитика
- `EmployeeManagement` - управление сотрудниками
#### Wrapper-компоненты:
- `HomePageWrapper` - маршрутизация по типам организаций
- `EconomicsPageWrapper` - адаптивная экономика по кабинетам
#### Специализированные компоненты:
- `WarehouseModuleCard` - карточка модуля склада
- `ServiceCard` - карточка услуги
- `EmployeeCard` - карточка сотрудника
- `FulfillmentStats` - статистика фулфилмента
- `LogisticsRouteCard` - карточка маршрута доставки
### 2.2 Архитектурные особенности
#### Технические правила отображения:
```tsx
// GraphQL проверки
const { data } = useQuery(GET_FULFILLMENT_DATA, {
skip: user?.organization?.type !== 'FULFILLMENT'
})
// Условное отображение
{user?.organization?.type === "FULFILLMENT" && (
<FulfillmentExclusiveComponent />
)}
// Адаптивные отступы
const margin = getSidebarMargin()
// Полинг данных для реального времени
const { data } = useQuery(GET_FULFILLMENT_STATS, {
pollInterval: 60000 // Обновление каждую минуту
})
```
### 2.3 Система учета склада (3-уровневая иерархия)
#### Структура данных:
```
🔵 УРОВЕНЬ 1: МАГАЗИНЫ
🟢 УРОВЕНЬ 2: ТОВАРЫ (зеленый - green-500)
🟠 УРОВЕНЬ 3: ВАРИАНТЫ ТОВАРОВ (оранжевый - orange-500)
```
#### Цветовое кодирование:
- Каждый уровень имеет цветной индикатор увеличивающегося размера
- Цветная левая граница с увеличивающимся отступом и толщиной
- Скроллбары в цвете уровня
- Контрастный цвет текста для читаемости
## 3. 📊 ФУНКЦИОНАЛЬНЫЕ ВОЗМОЖНОСТИ
> 📖 **Бизнес-правила**: См. [rules-complete.md#11-кабинет-фулфилмента](./rules-complete.md#11--кабинет-фулфилмента) для правил workflow и процессов
### 3.1 Структура склада по модулям (ОБЯЗАТЕЛЬНАЯ ПОСЛЕДОВАТЕЛЬНОСТЬ)
1. **📦 ПРОДУКТЫ** - готовые к отправке товары
2. **🛒 ТОВАРЫ** - базовые товары от поставщиков
- **"На складе"** - готовы к обработке
- **"В обработке"** - в процессе создания продукта
3. **❌ БРАК** - товары с дефектами, требуют утилизации
4. **↩️ ВОЗВРАТЫ С ПВЗ** - возвращенные товары, к обработке
5. **🎯 РАСХОДНИКИ СЕЛЛЕРОВ** - материалы для селлеров
6. **⚙️ РАСХОДНИКИ ФУЛФИЛМЕНТА** - операционные материалы (КЛИКАБЕЛЬНЫЙ модуль)
### 3.2 Система учета склада
#### Показатели движения (дополнительные значения):
- **ПРИБЫЛО** - количество поступивших на склад за период
- **УБЫЛО** - количество списанных со склада за период
#### Текущие остатки (основные значения):
- **ФОРМУЛА**: Основные значения = Предыдущие остатки + Прибыло - Убыло
- **ОБНОВЛЕНИЕ**: В реальном времени с изменениями за сутки
- **ИСТОЧНИК**: GraphQL query `GET_FULFILLMENT_WAREHOUSE_STATS`
### 3.3 Поставки фулфилмента (`/fulfillment-supplies`)
#### Структура: 2 основные вкладки
**A) 🛒 ПОСТАВКИ ТОВАРОВ**:
- **Детализированные товары ФФ** - планы и факты поставок с маршрутами
- **Товары ФФ** - общие поставки товаров от селлеров
- **Возвраты с ПВЗ** - обработка возвращенных товаров
**B) 🔧 ПОСТАВКИ РАСХОДНИКОВ**:
- **Заказы расходников** - управление заказами от селлеров
- **Расходники селлеров** - материалы для клиентов
- **Создание поставок** - формирование новых поставок расходников
#### Workflow поставок товаров:
1. **Planned** - поставка запланирована
2. **In-transit** - товар в пути
3. **Delivered** - доставлен на склад
4. **Completed** - обработка завершена
### 3.4 Услуги фулфилмента (`/services`)
#### Архитектура интеграции с системой
**СВЯЗЬ С РЕЦЕПТУРАМИ СЕЛЛЕРОВ:**
```
СЕЛЛЕР (создание поставки)
└── Рецептура
├── Товар (от поставщика)
├── Услуги фулфилмента ← CRUD в разделе Услуги
├── Расходники селлера
└── Расходники фулфилмента ← ТОЛЬКО с установленной ценой
ФУЛФИЛМЕНТ (обработка)
├── Входящие поставки → Поставки расходников (создание)
└── Услуги → Расходники (установка цены за единицу)
```
#### Структура: 3 обязательные вкладки
**A) 🛠️ УСЛУГИ** (`defaultValue="services"`):
- **Полный CRUD**: создание, редактирование, удаление услуг
- **Поля**: `name`, `description`, `price`, `imageUrl`
- **Glass Upload Zone**: элегантная загрузка изображений
- **Назначение**: каталог услуг для рецептур селлеров
**B) 🚚 ЛОГИСТИКА**:
- **Полный CRUD**: маршруты доставки
- **Поля**: откуда → куда, тарификация до/свыше 1м³
- **Группированные локации**:
- Мой фулфилмент (название организации)
- Рынки (предустановленные)
- Склады Wildberries
- Склады Ozon
- **Glass Upload Zone**: для изображений маршрутов
**C) 📦 РАСХОДНИКИ** (**❌ БЕЗ СОЗДАНИЯ**):
- **ТОЛЬКО ПРОСМОТР** расходников с фулфилмент-склада
- **ЕДИНСТВЕННОЕ РЕДАКТИРУЕМОЕ ПОЛЕ**: `pricePerUnit` - цена за единицу для рецептур
- **UI подсветка**: "Цена за 1 {unit}" (например, "Цена за 1 шт")
- **Автоматическая синхронизация** при приеме поставки расходников
- **Glass Upload Zone**: для обновления изображений расходников
#### Workflow расходников:
**ШАГ 1 - СОЗДАНИЕ**: Только через "Входящие поставки → Поставки расходников фулфилмента"
**ШАГ 2 - СИНХРОНИЗАЦИЯ**: При приеме на склад → автоматически в Услуги/Расходники
**ШАГ 3 - ЦЕНООБРАЗОВАНИЕ**: Установка цены за единицу в разделе Услуги
**ШАГ 4 - ИСПОЛЬЗОВАНИЕ**: Доступны в рецептурах селлеров
#### Правила видимости в рецептурах:
**В РЕЦЕПТУРАХ СЕЛЛЕРОВ ПОКАЗЫВАЮТСЯ ТОЛЬКО:**
- `isAvailable = true` (есть на складе)
- `pricePerUnit != null` (цена установлена)
**НЕ ПОКАЗЫВАЮТСЯ:**
- Расходники без цены (`pricePerUnit = null`)
- Удаленные со склада (`isAvailable = false`)
### 3.5 Разделение цен закупки и продажи
**КРИТИЧЕСКОЕ ПРАВИЛО**: Расходники фулфилмента имеют **ДВЕ РАЗНЫЕ ЦЕНЫ**:
1. **ЦЕНА ЗАКУПКИ** (`Supply.price`) - цена покупки у поставщика
2. **ЦЕНА ПРОДАЖИ** (`Supply.pricePerUnit`) - цена продажи селлерам
#### Поля в базе данных:
```prisma
model Supply {
price Decimal @db.Decimal(10, 2) // Цена закупки у поставщика (НЕИЗМЕННАЯ)
pricePerUnit Decimal? @db.Decimal(10, 2) // Цена продажи селлерам (устанавливается фулфилментом)
}
```
#### Правила отображения по разделам:
**РАЗДЕЛ "СКЛАД → РАСХОДНИКИ ФУЛФИЛМЕНТА"**:
- Показывает `Supply.price` (цена закупки)
- Цена ТОЛЬКО ДЛЯ ЧТЕНИЯ, нельзя изменять
- Отражает историческую стоимость приобретения
**РАЗДЕЛ "УСЛУГИ → РАСХОДНИКИ"**:
- Показывает и редактирует `Supply.pricePerUnit` (цена продажи)
- Единственное место где можно изменить цену для селлеров
- Влияет на рецептуры и расчеты для селлеров
#### Бизнес-логика создания:
```typescript
// ПРИ ПОСТУПЛЕНИИ ОТ ПОСТАВЩИКА:
const supply = await prisma.supply.create({
data: {
price: item.price, // Цена поставщика → ЗАФИКСИРОВАНА
pricePerUnit: null, // Цена продажи → ПУСТАЯ
},
})
// УСТАНОВКА ЦЕНЫ ПРОДАЖИ (в разделе "Услуги"):
const updated = await prisma.supply.update({
data: {
pricePerUnit: newPrice, // ТОЛЬКО цена продажи
// price НЕ ТРОГАЕМ - остается цена закупки
},
})
```
### 3.6 Сотрудники фулфилмента (`/employees`)
#### Структура: 2 основные вкладки
**A) 👥 СОТРУДНИКИ** (`defaultValue="combined"`):
**Управление персоналом**:
- **CRUD операции**: создание, редактирование, удаление сотрудников
- **Статусы сотрудников**: `ACTIVE`, `VACATION`, `SICK`, `FIRED`
- **Формы добавления**: Компактная (`showCompactForm`) / Полная форма
- **Поиск и фильтрация** по имени, должности, статусу
**Табель рабочего времени**:
- **Навигация по месяцам**: текущий год/месяц с кнопками ←/→
- **Отметки по дням**: статус дня и количество отработанных часов
**B) 📋 ОТЧЕТЫ** (`value="reports"`):
- **Сводные отчеты** по сотрудникам за период
- **Экспорт данных** табеля
- **Аналитика рабочего времени**
### 3.7 Статистика фулфилмента (`/fulfillment-statistics`)
#### Блоки аналитики (сворачиваемые):
**1. НАКОПЛЕННАЯ СТАТИСТИКА** (`allTime: true`):
- Обработано товаров (общий объем)
- Выявлено брака (всего единиц)
- Поставок получено
- Общий доход (за все время)
- Выполнено заказов (успешных отгрузок)
- Удовлетворенность клиентов (средний рейтинг)
**2. ОТГРУЗКА НА ПЛОЩАДКИ** (`marketplaces: true`):
- Отправлено на Wildberries
- Отправлено на Ozon
- Отправлено на другие площадки
**3. АНАЛИТИКА ПРОИЗВОДИТЕЛЬНОСТИ** (`performance: false`):
- Среднее время обработки (на единицу товара)
- Уровень брака (от общего объема)
- Уровень возвратов (возвраты с площадок)
- Удовлетворенность клиентов
## 4. 🛠️ GRAPHQL API
### 4.1 Основные запросы (Queries)
#### Получение статистики склада:
```graphql
query GetFulfillmentWarehouseStats {
fulfillmentWarehouseStats {
products {
total
received
shipped
}
supplies {
onStock
inProcessing
}
defects {
total
pending
}
returns {
pending
processed
}
}
}
```
#### Получение услуг фулфилмента:
```graphql
query GetMyServices {
myServices {
id
name
description
price
imageUrl
isActive
}
}
```
#### Получение расходников с ценами:
```graphql
query GetMySupplies {
mySupplies {
id
name
price # Цена закупки (read-only)
pricePerUnit # Цена продажи (editable)
unit
isAvailable
warehouseConsumableId
}
}
```
#### Получение логистических маршрутов:
```graphql
query GetMyLogistics {
myLogistics {
id
fromLocation
toLocation
tariffUnder1m3
tariffOver1m3
description
}
}
```
#### Получение сотрудников:
```graphql
query GetEmployees {
employees {
id
name
position
status
schedule {
date
hoursWorked
status
}
}
}
```
### 4.2 Мутации (Mutations)
#### Создание услуги:
```graphql
mutation CreateService($input: ServiceInput!) {
createService(input: $input) {
success
service {
id
name
description
price
organization {
id
name
fullName
}
}
}
}
```
#### Обновление цены расходника:
```graphql
mutation UpdateSupplyPrice($supplyId: ID!, $pricePerUnit: Float!) {
updateSupplyPrice(id: $supplyId, pricePerUnit: $pricePerUnit) {
success
supply {
id
pricePerUnit
isAvailable
}
}
}
```
#### Создание логистического маршрута:
```graphql
mutation CreateLogistics($input: LogisticsInput!) {
createLogistics(input: $input) {
success
logistics {
id
fromLocation
toLocation
tariffUnder1m3
tariffOver1m3
organization {
id
name
fullName
}
}
}
}
```
#### Управление сотрудниками:
```graphql
mutation CreateEmployee($input: EmployeeInput!) {
createEmployee(input: $input) {
success
employee {
id
name
position
status
}
}
}
mutation UpdateEmployeeSchedule($employeeId: ID!, $date: String!, $hoursWorked: Int!, $status: String!) {
updateEmployeeSchedule(employeeId: $employeeId, date: $date, hoursWorked: $hoursWorked, status: $status) {
success
schedule {
date
hoursWorked
status
}
}
}
```
### 4.3 Специальные запросы для рецептур
#### Расходники доступные для рецептур (только с ценой):
```graphql
query GetAvailableSuppliesForRecipe {
availableSuppliesForRecipe {
id
name
pricePerUnit # Только те что != null
unit
imageUrl
}
}
```
#### GraphQL типы:
```graphql
type Supply {
id: ID!
name: String!
price: Float! # Цена закупки (неизменная)
pricePerUnit: Float # Цена продажи (может быть null)
unit: String! # "шт", "кг", "м"
isAvailable: Boolean! # Статус на складе
warehouseConsumableId: ID! # Связь со складом
imageUrl: String
}
type Service {
id: ID!
name: String!
description: String
price: Float!
imageUrl: String
organization: Organization!
}
type Logistics {
id: ID!
fromLocation: String!
toLocation: String!
tariffUnder1m3: Float!
tariffOver1m3: Float!
description: String
organization: Organization!
}
```
## 5. 📁 ТЕХНИЧЕСКИЕ КОМПОНЕНТЫ
### 5.1 Расположение компонентов
```
src/components/
├── fulfillment/ # Компоненты фулфилмента
│ ├── fulfillment-warehouse-dashboard.tsx
│ ├── fulfillment-supplies-dashboard.tsx
│ ├── fulfillment-statistics.tsx
│ └── warehouse-module-card.tsx
├── services/ # Компоненты услуг
│ ├── services-management.tsx
│ ├── service-card.tsx
│ ├── logistics-management.tsx
│ └── consumables-pricing.tsx
├── employees/ # Компоненты сотрудников
│ ├── employee-management.tsx
│ ├── employee-card.tsx
│ ├── employee-schedule.tsx
│ └── employee-reports.tsx
└── economics/ # Экономика
└── fulfillment-economics-page.tsx
```
### 5.2 Страницы (Pages)
```
src/app/
├── warehouse/
│ └── page.tsx # Склад фулфилмента
├── fulfillment-supplies/
│ └── page.tsx # Поставки фулфилмента
├── services/
│ └── page.tsx # Услуги и расходники
├── employees/
│ └── page.tsx # Сотрудники
└── fulfillment-statistics/
└── page.tsx # Статистика
```
## 6. 🚨 ТЕХНИЧЕСКИЕ ПРАВИЛА И ОГРАНИЧЕНИЯ
> 📖 **Workflow процессов**: См. [rules-complete.md#11-кабинет-фулфилмента](./rules-complete.md#11--кабинет-фулфилмента) для бизнес-процессов
### 6.1 Обязательные проверки:
- Проверка типа организации: `organization.type === 'FULFILLMENT'`
- Валидация прав доступа на уровне GraphQL резолверов
- Проверка наличия товаров на складе перед отгрузкой
- Контроль цен расходников перед отображением в рецептурах
### 6.2 Правила безопасности доступа:
#### Контроль на уровне компонентов:
```typescript
{user?.organization?.type === "FULFILLMENT" && (
<FulfillmentWarehouseDashboard />
)}
// Для полинга данных
const { data } = useQuery(GET_FULFILLMENT_STATS, {
skip: user?.organization?.type !== 'FULFILLMENT',
pollInterval: 60000
})
```
#### Проверки в GraphQL резолверах:
```typescript
// Проверка что пользователь - фулфилмент
if (context.user.organization.type !== 'FULFILLMENT') {
throw new Error('Access denied: Fulfillment access required')
}
// Проверка доступа к своим услугам
const service = await prisma.service.findFirst({
where: {
id: serviceId,
organizationId: context.user.organizationId,
},
})
// Обязательное включение organization в ответы
const updated = await prisma.service.update({
where: { id: serviceId },
data: input,
include: { organization: true }, // ОБЯЗАТЕЛЬНО!
})
```
### 6.3 Запрещено:
- Создавать расходники в разделе "Услуги" (только через поставки)
- Изменять цену закупки (`Supply.price`) - она фиксируется при поступлении
- Показывать в рецептурах расходники без установленной цены продажи
- Удалять услуги, используемые в активных рецептурах
### 6.4 Правила создания и ценообразования:
- **Услуги**: Создаются фулфилментом с установленной ценой
- **Расходники**: Создаются только при поступлении от поставщиков
- **Цена продажи**: Устанавливается отдельно в разделе Услуги
- **Логистика**: Тарификация до/свыше 1м³ обязательна
### 6.5 Правила фулфилмента
**ОБЯЗАТЕЛЬНО**:
- Установка цен на расходники перед доступностью селлерам
- Контроль качества товаров при приемке
- Своевременная обработка возвратов
- Ведение учета движения товаров по модулям
- Управление персоналом и рабочим временем
**ЗАПРЕЩЕНО**:
- Отгружать товары без подтверждения наличия
- Создавать расходники минуя систему поставок
- Изменять цены закупки после поступления товара
- Показывать в рецептурах неактивные расходники
- Нарушать последовательность модулей склада
**ИНТЕГРАЦИЯ С ПАРТНЕРАМИ**:
- Фулфилмент видит всех партнеров системы
- Принимает поставки от всех типов организаций
- Предоставляет услуги селлерам через рецептуры
- Все взаимодействия фиксируются в системе уведомлений
> 📖 **Критические запреты**: См. [rules-complete.md#17-критические-запреты](./rules-complete.md#17--критические-запреты)
---
**Последнее обновление**: Август 2025
**Связанные файлы**:
- [rules-complete.md](./rules-complete.md) - Общие бизнес-правила
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила

View File

@ -2,7 +2,6 @@ generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
} }
// Конфигурация для автоматического seeding
generator seed { generator seed {
provider = "prisma-client-js" provider = "prisma-client-js"
output = "./generated/client" output = "./generated/client"
@ -31,7 +30,7 @@ model User {
model Admin { model Admin {
id String @id @default(cuid()) id String @id @default(cuid())
username String @unique username String @unique
password String // Хеш пароля password String
email String? @unique email String? @unique
isActive Boolean @default(true) isActive Boolean @default(true)
lastLogin DateTime? lastLogin DateTime?
@ -89,6 +88,9 @@ model Organization {
revenue BigInt? revenue BigInt?
taxSystem String? taxSystem String?
dadataData Json? dadataData Json?
referralCode String? @unique
referredById String?
referralPoints Int @default(0)
apiKeys ApiKey[] apiKeys ApiKey[]
carts Cart? carts Cart?
counterpartyOf Counterparty[] @relation("CounterpartyOf") counterpartyOf Counterparty[] @relation("CounterpartyOf")
@ -96,25 +98,31 @@ model Organization {
receivedRequests CounterpartyRequest[] @relation("ReceivedRequests") receivedRequests CounterpartyRequest[] @relation("ReceivedRequests")
sentRequests CounterpartyRequest[] @relation("SentRequests") sentRequests CounterpartyRequest[] @relation("SentRequests")
employees Employee[] employees Employee[]
externalAds ExternalAd[] @relation("ExternalAds")
favorites Favorites[] favorites Favorites[]
logistics Logistics[]
receivedMessages Message[] @relation("ReceivedMessages") receivedMessages Message[] @relation("ReceivedMessages")
sentMessages Message[] @relation("SentMessages") sentMessages Message[] @relation("SentMessages")
referredBy Organization? @relation("ReferralRelation", fields: [referredById], references: [id])
referrals Organization[] @relation("ReferralRelation")
products Product[] products Product[]
referralTransactions ReferralTransaction[] @relation("ReferralTransactions")
referrerTransactions ReferralTransaction[] @relation("ReferrerTransactions")
sellerStatsCaches SellerStatsCache[] @relation("SellerStatsCaches")
services Service[] services Service[]
supplies Supply[] supplies Supply[]
users User[] sellerSupplies Supply[] @relation("SellerSupplies")
logistics Logistics[]
supplyOrders SupplyOrder[]
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
fulfillmentSupplyOrders SupplyOrder[] @relation("SupplyOrderFulfillmentCenter") fulfillmentSupplyOrders SupplyOrder[] @relation("SupplyOrderFulfillmentCenter")
logisticsSupplyOrders SupplyOrder[] @relation("SupplyOrderLogistics") logisticsSupplyOrders SupplyOrder[] @relation("SupplyOrderLogistics")
wildberriesSupplies WildberriesSupply[] supplyOrders SupplyOrder[]
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
supplySuppliers SupplySupplier[] @relation("SupplySuppliers") supplySuppliers SupplySupplier[] @relation("SupplySuppliers")
externalAds ExternalAd[] @relation("ExternalAds") users User[]
wbWarehouseCaches WBWarehouseCache[] @relation("WBWarehouseCaches") wbWarehouseCaches WBWarehouseCache[] @relation("WBWarehouseCaches")
sellerStatsCaches SellerStatsCache[] @relation("SellerStatsCaches") wildberriesSupplies WildberriesSupply[]
sellerSupplies Supply[] @relation("SellerSupplies")
@@index([referralCode])
@@index([referredById])
@@map("organizations") @@map("organizations")
} }
@ -149,14 +157,18 @@ model CounterpartyRequest {
} }
model Counterparty { model Counterparty {
id String @id @default(cuid()) id String @id @default(cuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
organizationId String organizationId String
counterpartyId String counterpartyId String
counterparty Organization @relation("CounterpartyOf", fields: [counterpartyId], references: [id]) type CounterpartyType @default(MANUAL)
organization Organization @relation("OrganizationCounterparties", fields: [organizationId], references: [id]) triggeredBy String?
triggerEntityId String?
counterparty Organization @relation("CounterpartyOf", fields: [counterpartyId], references: [id])
organization Organization @relation("OrganizationCounterparties", fields: [organizationId], references: [id])
@@unique([organizationId, counterpartyId]) @@unique([organizationId, counterpartyId])
@@index([type])
@@map("counterparties") @@map("counterparties")
} }
@ -203,26 +215,26 @@ model Supply {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
description String? description String?
price Decimal @db.Decimal(10, 2) // Цена закупки у поставщика (не меняется) price Decimal @db.Decimal(10, 2)
pricePerUnit Decimal? @db.Decimal(10, 2) // Цена продажи селлерам (устанавливается фулфилментом) pricePerUnit Decimal? @db.Decimal(10, 2)
quantity Int @default(0) quantity Int @default(0)
unit String @default("шт") unit String @default("шт")
category String @default("Расходники") category String @default("Расходники")
status String @default("planned") // planned, in-transit, delivered, in-stock status String @default("planned")
date DateTime @default(now()) date DateTime @default(now())
supplier String @default("Не указан") supplier String @default("Не указан")
minStock Int @default(0) minStock Int @default(0)
currentStock Int @default(0) currentStock Int @default(0)
usedStock Int @default(0) // Количество использованных расходников usedStock Int @default(0)
imageUrl String? imageUrl String?
type SupplyType @default(FULFILLMENT_CONSUMABLES) // Тип расходников type SupplyType @default(FULFILLMENT_CONSUMABLES)
sellerOwnerId String? // ID селлера-владельца (для расходников селлеров) sellerOwnerId String?
sellerOwner Organization? @relation("SellerSupplies", fields: [sellerOwnerId], references: [id], onDelete: SetNull) shopLocation String?
shopLocation String? // Местоположение в магазине фулфилмента
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
organizationId String organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
sellerOwner Organization? @relation("SellerSupplies", fields: [sellerOwnerId], references: [id])
@@map("supplies") @@map("supplies")
} }
@ -266,9 +278,9 @@ model Product {
organizationId String organizationId String
cartItems CartItem[] cartItems CartItem[]
favorites Favorites[] favorites Favorites[]
supplyOrderItems SupplyOrderItem[]
category Category? @relation(fields: [categoryId], references: [id]) category Category? @relation(fields: [categoryId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
supplyOrderItems SupplyOrderItem[]
@@unique([organizationId, article]) @@unique([organizationId, article])
@@map("products") @@map("products")
@ -401,6 +413,156 @@ model WildberriesSupplyCard {
@@map("wildberries_supply_cards") @@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 { enum OrganizationType {
FULFILLMENT FULFILLMENT
SELLER SELLER
@ -467,147 +629,20 @@ enum ProductType {
} }
enum SupplyType { enum SupplyType {
FULFILLMENT_CONSUMABLES // Расходники фулфилмента (купленные фулфилментом для себя) FULFILLMENT_CONSUMABLES
SELLER_CONSUMABLES // Расходники селлеров (принятые от селлеров для хранения) SELLER_CONSUMABLES
} }
model Logistics { enum CounterpartyType {
id String @id @default(cuid()) MANUAL
fromLocation String REFERRAL
toLocation String AUTO_BUSINESS
priceUnder1m3 Float AUTO
priceOver1m3 Float
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
@@map("logistics")
} }
model SupplyOrder { enum ReferralTransactionType {
id String @id @default(cuid()) REGISTRATION
partnerId String AUTO_PARTNERSHIP
deliveryDate DateTime FIRST_ORDER
status SupplyOrderStatus @default(PENDING) MONTHLY_BONUS
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")
} }

619
referral-system-rules.md Normal file
View File

@ -0,0 +1,619 @@
# 🎯 ПРАВИЛА РЕФЕРАЛЬНОЙ СИСТЕМЫ SFERA
⚠️ **КРИТИЧЕСКИ ВАЖНОЕ ПРАВИЛО: НЕ ПУТАТЬ "ПАРТНЁРСКИЕ ССЫЛКИ" И "РЕФЕРАЛЬНЫЕ ССЫЛКИ"**
> 📅 **Дата создания**: 2025-01-10
> 🔧 **Статус**: Спецификация для разработки
> 📌 **Версия**: 1.1
## 📋 ОГЛАВЛЕНИЕ
1. [Общие принципы](#1-общие-принципы)
2. [Структура данных](#2-структура-данных)
3. [Генерация реферальных ссылок](#3-генерация-реферальных-ссылок)
4. [UI/UX компоненты](#4-uiux-компоненты)
5. [Система начисления баллов](#5-система-начисления-баллов)
6. [GraphQL API](#6-graphql-api)
7. [Процесс регистрации по реферальной ссылке](#7-процесс-регистрации-по-реферальной-ссылке)
8. [Автоматическое партнерство через бизнес-сделки](#8-автоматическое-партнерство-через-бизнес-сделки)
9. [Безопасность и ограничения](#9-безопасность-и-ограничения)
---
## 1. ОБЩИЕ ПРИНЦИПЫ
### 1.1 Основные положения
- **УНИВЕРСАЛЬНОСТЬ**: Реферальная система доступна для всех типов кабинетов (SELLER, WHOLESALE, FULFILLMENT, LOGIST)
- **АВТОМАТИЗАЦИЯ**: Реферальная ссылка генерируется автоматически при создании организации
- **ПРОЗРАЧНОСТЬ**: Все начисления сфер ⚡ видны в режиме реального времени
- **БЕЗОПАСНОСТЬ**: Невозможно изменить реферальную ссылку после генерации
- **ИНТЕГРАЦИЯ С БИЗНЕСОМ**: Партнерство создается не только через рефералы, но и через коммерческие сделки
### 1.2 Терминология
- **РЕФЕРЕР** - организация, которая приглашает новых участников
- **РЕФЕРАЛ** - организация, зарегистрированная по реферальной ссылке
- **РЕФЕРАЛЬНЫЙ КОД** - уникальный идентификатор в ссылке (10 символов)
- **СФЕРЫ ⚡** - единица вознаграждения за привлечение рефералов и коммерческие сделки
- **АВТОПАРТНЕРСТВО** - автоматическое создание партнерских связей при коммерческих сделках
- **РЕФЕРАЛЬНАЯ ССЫЛКА** - маркетинговый инструмент (`?ref=`), только начисление сфер
- **ПАРТНЕРСКАЯ ССЫЛКА** - бизнес-инструмент (`?partner=`), сферы + автоматическое партнерство
### 1.3 Разделение ссылок
#### Реферальная система (маркетинг):
- **URL формат**: `https://app.sfera.ru/register?ref=SF2X9K4M7P`
- **Местоположение**: Вкладка "Рефералы"
- **Цель**: Привлечение новых пользователей на платформу
- **Результат**: +100 сфер ⚡, автоматического партнерства НЕТ
- **Ссылка**: Уже готова при создании организации, быстрое копирование
#### Партнерская система (бизнес):
- **URL формат**: `https://app.sfera.ru/register?partner=SF2X9K4M7P`
- **Местоположение**: Вкладка "Мои партнеры"
- **Цель**: Прямое деловое сотрудничество
- **Результат**: +100 сфер ⚡ + автоматическое добавление в партнеры
- **Ссылка**: Уже готова при создании организации, быстрое копирование
---
## 2. СТРУКТУРА ДАННЫХ
### 2.1 Расширение модели Organization (Prisma)
```prisma
model Organization {
// Существующие поля...
// Реферальная система
referralCode String? @unique @default(cuid()) // Уникальный код для реферальной ссылки
referredById String? // ID организации-реферера
referredBy Organization? @relation("ReferralRelation", fields: [referredById], references: [id])
referrals Organization[] @relation("ReferralRelation")
referralPoints Int @default(0) // Общее количество баллов
@@index([referralCode])
@@index([referredById])
}
```
### 2.2 Новая модель ReferralTransaction
```prisma
model ReferralTransaction {
id String @id @default(cuid())
referrerId String // Кто получил баллы
referralId String // За кого получил баллы
points Int // Количество баллов
type ReferralTransactionType // Тип транзакции
description String? // Описание транзакции
createdAt DateTime @default(now())
referrer Organization @relation("ReferrerTransactions", fields: [referrerId], references: [id])
referral Organization @relation("ReferralTransactions", fields: [referralId], references: [id])
@@index([referrerId, createdAt])
@@index([referralId])
}
enum ReferralTransactionType {
REGISTRATION // За регистрацию по реферальной ссылке
AUTO_PARTNERSHIP // За автоматическое партнерство через бизнес-сделку
FIRST_ORDER // За первый заказ (будущее расширение)
MONTHLY_BONUS // Ежемесячный бонус (будущее расширение)
}
```
---
## 3. ГЕНЕРАЦИЯ РЕФЕРАЛЬНЫХ ССЫЛОК
### 3.1 Формат реферальной ссылки
```
https://app.sfera.ru/register?ref={referralCode}
```
### 3.2 Алгоритм генерации кода
- **Длина**: 10 символов
- **Символы**: A-Z, 0-9 (исключая похожие: O/0, I/1)
- **Пример**: `SF2X9K4M7P`
### 3.3 Правила генерации
- Генерируется автоматически при создании организации
- Проверка уникальности перед сохранением
- Невозможно изменить после создания
- Хранится в поле `referralCode` модели Organization
---
## 4. UI/UX КОМПОНЕНТЫ
### 4.1 Вкладка "Рефералы" в разделе Партнеры
#### Расположение
- Раздел: `/partners`
- Новая вкладка: 6-я позиция после "Поставщик"
- Название: "Рефералы"
- Иконка: `Gift` или `Users2` из lucide-react
#### Структура страницы
```tsx
<div className="referrals-page">
{/* Блок с реферальной ссылкой */}
<div className="referral-link-section glass-card p-6 mb-6">
<h3>Ваша реферальная ссылка</h3>
<div className="flex items-center gap-4">
<div className="hidden-link">••••••••••</div>
<Button onClick={copyLink}>
<Copy className="h-4 w-4 mr-2" />
Копировать ссылку
</Button>
</div>
<p className="text-sm text-white/60 mt-2">
Поделитесь ссылкой с партнерами и получайте баллы за каждую регистрацию
</p>
</div>
{/* Статистика */}
<div className="stats-grid grid grid-cols-4 gap-4 mb-6">
<StatsCard title="Всего партнеров" value={totalReferrals} icon="Users" />
<StatsCard title="Сфер заработано" value={totalPoints} icon="Zap" suffix="⚡" />
<StatsCard title="Партнеров за месяц" value={monthlyReferrals} icon="TrendingUp" />
<StatsCard title="Сфер за месяц" value={monthlyPoints} icon="Zap" suffix="⚡" />
</div>
{/* Фильтры */}
<div className="filters glass-card p-4 mb-6">
<DateRangePicker />
<TypeFilter types={['SELLER', 'WHOLESALE', 'FULFILLMENT', 'LOGIST']} />
<SearchInput placeholder="Поиск по названию или ИНН" />
</div>
{/* Таблица рефералов */}
<div className="referrals-table glass-card">
<Table>
<TableHeader>
<TableRow>
<TableHead>Дата регистрации</TableHead>
<TableHead>Название организации</TableHead>
<TableHead>ИНН</TableHead>
<TableHead>Тип кабинета</TableHead>
<TableHead>Источник</TableHead>
<TableHead>Начислено сфер </TableHead>
<TableHead>Статус</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{referrals.map(referral => (
<TableRow key={referral.id}>
<TableCell>{formatDate(referral.createdAt)}</TableCell>
<TableCell>{referral.name || referral.fullName}</TableCell>
<TableCell>{referral.inn}</TableCell>
<TableCell>
<Badge className={getTypeBadgeStyles(referral.type)}>
{getTypeLabel(referral.type)}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
{referral.source === 'REFERRAL' ? (
<>
<UserPlus className="h-4 w-4 text-blue-400" />
<span className="text-blue-300">Реферальная ссылка</span>
</>
) : (
<>
<ShoppingCart className="h-4 w-4 text-orange-400" />
<span className="text-orange-300">Бизнес-сделка</span>
</>
)}
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<span className="text-green-400">+{referral.points}</span>
<Zap className="h-4 w-4 text-yellow-400" />
</div>
</TableCell>
<TableCell>
<Badge variant="success">Активен</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
```
### 4.2 Визуальные элементы
#### Скрытие реферальной ссылки
- Ссылка НЕ отображается в явном виде
- Показываются точки или звездочки: `••••••••••`
- Только кнопка "Копировать ссылку"
#### Уведомления
- При копировании: "Реферальная ссылка скопирована"
- При новом реферале: push-уведомление
- При начислении баллов: анимация изменения баланса
---
## 5. СИСТЕМА НАЧИСЛЕНИЯ БАЛЛОВ
### 5.1 Базовые ставки
| Тип регистрации | Количество баллов |
|-----------------|-------------------|
| SELLER | 100 сфер ⚡ |
| WHOLESALE | 100 сфер ⚡ |
| FULFILLMENT | 100 сфер ⚡ |
| LOGIST | 100 сфер ⚡ |
> 💡 **ИКОНКА СФЕРЫ**: ⚡ (молния) - символизирует энергию, скорость и силу партнерства
### 5.2 Правила начисления
- **МОМЕНТ НАЧИСЛЕНИЯ**: Сразу после успешной регистрации реферала
- **УСЛОВИЕ**: Реферал должен пройти полную регистрацию (подтвердить ИНН)
- **ОГРАНИЧЕНИЯ**: Один ИНН = одно начисление (защита от дублей)
- **ВИДИМОСТЬ**: Баллы сразу отображаются в таблице и общем счетчике
### 5.3 Будущие расширения
- Бонусы за первый заказ реферала
- Ежемесячные начисления за активных рефералов
- Многоуровневая система (рефералы рефералов)
---
## 6. GRAPHQL API
### 6.1 Новые Queries
```graphql
type Query {
# Получить мою реферальную ссылку
myReferralLink: String!
# Список моих рефералов
myReferrals(
dateFrom: DateTime
dateTo: DateTime
type: OrganizationType
search: String
limit: Int
offset: Int
): ReferralsResponse!
# Статистика по рефералам
myReferralStats: ReferralStats!
# История транзакций баллов
myReferralTransactions(
limit: Int
offset: Int
): ReferralTransactionsResponse!
}
```
### 6.2 Новые Types
```graphql
type ReferralsResponse {
referrals: [Referral!]!
totalCount: Int!
totalPages: Int!
}
type Referral {
id: ID!
organization: Organization!
registeredAt: DateTime!
pointsEarned: Int!
status: ReferralStatus!
transactions: [ReferralTransaction!]!
}
type ReferralStats {
totalReferrals: Int!
totalPoints: Int!
monthlyReferrals: Int!
monthlyPoints: Int!
referralsByType: [ReferralTypeStats!]!
}
type ReferralTypeStats {
type: OrganizationType!
count: Int!
points: Int!
}
type ReferralTransaction {
id: ID!
points: Int!
type: ReferralTransactionType!
description: String
createdAt: DateTime!
referral: Organization!
}
enum ReferralStatus {
ACTIVE
INACTIVE
BLOCKED
}
```
### 6.3 Расширение существующих типов
```graphql
extend type Organization {
referralCode: String
referredBy: Organization
referrals: [Organization!]!
referralPoints: Int!
isMyReferral: Boolean! # Computed field
}
```
---
## 7. ПРОЦЕСС РЕГИСТРАЦИИ ПО ССЫЛКАМ
### 7.1 Реферальная ссылка (маркетинг)
#### Пошаговый процесс:
1. **Переход по ссылке**
- Пользователь переходит по ссылке: `https://app.sfera.ru/register?ref=SF2X9K4M7P`
- Система сохраняет реферальный код в sessionStorage
2. **Регистрация**
- Стандартный процесс регистрации
- Реферальный код передается в mutation `registerOrganization`
3. **Валидация**
- Проверка существования реферального кода
- Проверка, что организация не регистрировалась ранее
4. **Создание связи**
- Установка `referredById` для новой организации
- Создание записи в `ReferralTransaction`
- **ВАЖНО**: Автоматическое партнерство НЕ создается
5. **Начисление баллов**
- Автоматическое начисление 100 сфер ⚡ рефереру
- Отправка уведомления рефереру
### 7.2 Партнерская ссылка (бизнес)
#### Пошаговый процесс:
1. **Переход по ссылке**
- Пользователь переходит по ссылке: `https://app.sfera.ru/register?partner=SF2X9K4M7P`
- Система сохраняет партнерский код в sessionStorage
2. **Регистрация**
- Стандартный процесс регистрации
- Партнерский код передается в mutation `registerOrganization`
3. **Валидация**
- Проверка существования партнерского кода
- Проверка, что организация не регистрировалась ранее
4. **Создание связи**
- Установка `referredById` для новой организации
- Создание записи в `ReferralTransaction`
- **ВАЖНО**: Автоматическое создание партнерства (`Counterparty`)
5. **Начисление баллов**
- Автоматическое начисление 100 сфер ⚡ партнеру
- Отправка уведомления партнеру
- Добавление в список "Мои партнеры"
### 7.3 Логика различения
```javascript
// В процессе регистрации
if (query.ref) {
// Реферальная ссылка - только сферы
await addReferralTransaction(referrerId, newUserId, 100, 'REFERRAL_LINK')
} else if (query.partner) {
// Партнерская ссылка - сферы + автоматическое партнерство
await addReferralTransaction(partnerId, newUserId, 100, 'REFERRAL_LINK')
await createPartnership(partnerId, newUserId, 'REFERRAL')
}
```
### 7.4 Обработка ошибок
- **Невалидный код**: Регистрация продолжается без реферала/партнерства
- **Самореферал**: Блокировка (нельзя регистрироваться по своей ссылке)
- **Повторная регистрация**: Игнорирование кода
---
## 8. АВТОМАТИЧЕСКОЕ ПАРТНЕРСТВО ЧЕРЕЗ БИЗНЕС-СДЕЛКИ
### 8.1 Принцип работы
Кроме реферальных ссылок, партнерские отношения создаются автоматически через коммерческие взаимодействия в системе.
### 8.2 Триггер автопартнерства
**МОМЕНТ СОЗДАНИЯ ПАРТНЕРСТВА**: Когда поставщик одобряет заявку на поставку (`supplierApproveOrder` mutation)
**УСЛОВИЕ**: Если между организациями еще нет партнерских отношений (`Counterparty` связи)
**ДЕЙСТВИЕ**: Автоматическое создание взаимного партнерства
### 8.3 Алгоритм автопартнерства
```typescript
// В GraphQL резолвере supplierApproveOrder
async supplierApproveOrder(supplyOrderId: string) {
// 1. Одобрить заявку
const supplyOrder = await updateSupplyOrderStatus(supplyOrderId, 'SUPPLIER_APPROVED')
// 2. Проверить существование партнерства
const existingCounterparty = await checkCounterpartyExists(
supplyOrder.organizationId, // Покупатель
supplyOrder.partnerId // Поставщик
)
// 3. Создать автопартнерство если его нет
if (!existingCounterparty) {
await createAutoCounterparty({
organizationId: supplyOrder.organizationId,
counterpartyId: supplyOrder.partnerId,
type: 'AUTO_BUSINESS',
triggeredBy: 'SUPPLY_ORDER_APPROVAL',
supplyOrderId: supplyOrderId
})
}
return supplyOrder
}
```
### 8.4 Расширение модели данных
```prisma
model Counterparty {
// Существующие поля...
// Новые поля для автопартнерства
type CounterpartyType @default(MANUAL)
triggeredBy String? // 'SUPPLY_ORDER_APPROVAL', 'REFERRAL_LINK'
triggerEntityId String? // ID заявки, реферала и т.д.
@@index([type])
}
enum CounterpartyType {
MANUAL // Создано вручную через UI
REFERRAL // Создано через реферальную ссылку
AUTO_BUSINESS // Создано автоматически через бизнес-сделку
}
```
### 8.5 Начисление сфер за автопартнерство
**ДЛЯ ПОСТАВЩИКА** (кто одобрил заявку):
- +100 сфер ⚡ за каждого нового партнера через бизнес-сделку
- Равно рефералу, так как это также ценное партнерство
**ДЛЯ ПОКУПАТЕЛЯ**:
- Партнерство создается, но сферы не начисляются
- Выгода в том, что появляется прямая связь с поставщиком
### 8.6 Отображение в UI
В таблице рефералов добавить колонку "Источник":
| Источник | Описание | Иконка |
|----------|----------|---------|
| Реферальная ссылка | Зарегистрировались по вашей ссылке | `UserPlus` |
| Бизнес-сделка | Стали партнерами через заказ | `ShoppingCart` |
### 8.7 Логирование автопартнерства
```typescript
// Лог события создания автопартнерства
{
event: 'auto_counterparty_created',
supplierId: '...',
buyerId: '...',
supplyOrderId: '...',
spheresEarned: 50,
timestamp: '2025-01-10T15:30:00Z'
}
```
---
## 9. БЕЗОПАСНОСТЬ И ОГРАНИЧЕНИЯ
### 9.1 Защита от злоупотреблений
- **Один ИНН - одна регистрация**: Проверка уникальности ИНН
- **Блокировка самореферала**: Проверка IP и device fingerprint
- **Лимиты**: Максимум 100 регистраций в день с одного реферального кода
- **Валидация ИНН**: Обязательная проверка через DaData API
### 9.2 Права доступа
- **Просмотр своих рефералов**: Только владелец реферального кода
- **Изменение кода**: Запрещено
- **Просмотр чужих рефералов**: Запрещено
- **Администратор**: Полный доступ для модерации
### 9.3 Логирование
Все действия с реферальной системой логируются:
- Генерация ссылок
- Переходы по ссылкам
- Регистрации
- Начисления баллов
- Попытки злоупотреблений
---
## 🎨 ВИЗУАЛЬНЫЙ ДИЗАЙН
### Цветовая схема для сфер ⚡
- **Положительные начисления**: `text-green-400`
- **Иконка молнии**: `text-yellow-400` (золотистый)
- **Фон для сфер**: `bg-yellow-500/20`
- **Анимация при изменении**: `animate-pulse` на 2 секунды
### Иконки источников партнерства
- **Реферальная ссылка**: `UserPlus` в `text-blue-400`
- **Бизнес-сделка**: `ShoppingCart` в `text-orange-400`
- **Сферы**: `Zap` в `text-yellow-400`
### Стили для типов кабинетов (соответствуют существующим)
- **SELLER**: `bg-green-500/20 text-green-300`
- **WHOLESALE**: `bg-purple-500/20 text-purple-300`
- **FULFILLMENT**: `bg-blue-500/20 text-blue-300`
- **LOGIST**: `bg-orange-500/20 text-orange-300`
---
## 📊 МЕТРИКИ УСПЕХА
- **Конверсия**: % регистраций по реферальным ссылкам
- **Активность**: Среднее количество рефералов на одного партнера
- **Retention**: % активных рефералов через 30 дней
- **Виральность**: Количество рефералов, которые сами привели рефералов
---
## 🚀 ПЛАН ВНЕДРЕНИЯ
### Фаза 1 (MVP)
- Базовая генерация ссылок
- Регистрация по реферальным ссылкам
- Начисление баллов за регистрацию
- UI для просмотра рефералов
### Фаза 2
- Бонусы за первый заказ
- Email уведомления о новых рефералах
- Экспорт данных о рефералах
### Фаза 3
- Многоуровневая система
- Использование баллов для оплаты услуг
- Gamification элементы (достижения, уровни)
---
**Дата создания**: 2025-01-10
**Автор**: Claude AI Assistant
**Статус**: Готово к реализации

View File

@ -0,0 +1,178 @@
# ПРАВИЛА РЕГИСТРАЦИИ И АВТОРИЗАЦИИ
## 🏗️ АРХИТЕКТУРА СИСТЕМЫ
### Основные сущности:
- **ПОЛЬЗОВАТЕЛЬ** - верхний уровень, может иметь много номеров телефонов
- **НОМЕР ТЕЛЕФОНА** - привязан к пользователю, может иметь много организаций
- **ОРГАНИЗАЦИЯ/КАБИНЕТ** - бизнес-сущность, привязана к номеру телефона
- **ПАРТНЕРСТВО** - связь между организациями (не пользователями или номерами)
### Связи:
```
ПОЛЬЗОВАТЕЛЬ (1) ←→ (N) НОМЕРА ТЕЛЕФОНОВ (1) ←→ (N) ОРГАНИЗАЦИИ
ОРГАНИЗАЦИЯ (N) ←→ (N) ОРГАНИЗАЦИИ (партнерство)
```
### Пример структуры:
```
ПОЛЬЗОВАТЕЛЬ "Иван Петров"
├── +7111111111 (номер 1)
│ ├── СБЕРБАНК (организация 1)
│ └── АВИТО (организация 2)
└── +7222222222 (номер 2)
└── ЯНДЕКС (организация 3)
```
## 📋 СПОСОБЫ СОЗДАНИЯ ОРГАНИЗАЦИЙ
### 1. СТАНДАРТНАЯ РЕГИСТРАЦИЯ
**URL:** `/register` (без параметров)
**Флоу для НОВОГО номера телефона:**
1. Ввод телефона → SMS код → авторизация
2. Выбор типа кабинета
3. Ввод данных организации (ИНН или API ключи)
4. Создание организации
5. Привязка организации к номеру телефона
**Флоу для АВТОРИЗОВАННОГО номера:**
1. Редирект в дашборд (стандартное поведение)
### 2. РЕГИСТРАЦИЯ ЧЕРЕЗ САЙДБАР (ПОТОМ)
**Местоположение:** Блок организации в сайдбаре
**Флоу:**
1. Модалка создания новой организации
2. Выбор типа кабинета
3. Ввод данных организации
4. Создание организации без партнерства
### 3. ПАРТНЕРСКАЯ ССЫЛКА
**URL:** `/register?partner=CODE`
**Флоу для НОВОГО номера телефона:**
1. Ввод телефона → SMS код → авторизация
2. Выбор типа кабинета
3. Ввод данных организации
4. Создание организации С ПАРТНЕРСТВОМ
5. Автоматическое создание партнерских связей между организациями
6. Начисление +100 сфер организации-рефереру
**Флоу для АВТОРИЗОВАННОГО номера телефона:**
1. Пропуск авторизации (уже авторизован на этом номере)
2. Переход сразу к выбору типа кабинета
3. Ввод данных новой организации
4. Создание организации С ПАРТНЕРСТВОМ
5. Автоматическое создание партнерских связей
6. Начисление +100 сфер организации-рефереру
### 4. РЕФЕРАЛЬНАЯ ССЫЛКА
**URL:** `/register?ref=CODE`
**Флоу для НОВОГО номера телефона:**
1. Ввод телефона → SMS код → авторизация
2. Выбор типа кабинета
3. Ввод данных организации
4. Создание организации С РЕФЕРАЛЬНОЙ СВЯЗЬЮ
5. Начисление +100 сфер организации-рефереру
6. БЕЗ автоматического партнерства
**Флоу для АВТОРИЗОВАННОГО номера телефона:**
1. Пропуск авторизации (уже авторизован на этом номере)
2. Переход сразу к выбору типа кабинета
3. Ввод данных новой организации
4. Создание организации С РЕФЕРАЛЬНОЙ СВЯЗЬЮ
5. Начисление +100 сфер организации-рефереру
6. БЕЗ автоматического партнерства
## 📊 ДАШБОРД РЕФЕРАЛОВ (РЕАЛИЗОВАНО)
### Местоположение: `/partners` → вкладка "Рефералы"
### Функции:
1. **Мои ссылки:**
- Реферальная ссылка: `register?ref=CODE`
- Партнерская ссылка: `register?partner=CODE`
- Кнопка копирования
2. **Статистика:**
- Всего партнеров и начисленных сфер
- Статистика за текущий месяц
3. **Список рефералов:**
- Привлеченные организации
- Фильтрация и поиск
- Источник регистрации
## 🔧 ТЕХНИЧЕСКИЕ ПРАВИЛА
### Валидация URL параметров:
- Нельзя использовать `partner` и `ref` одновременно
- Коды должны быть длиной 10 символов
- Коды должны существовать в системе
- Коды должны соответствовать формату `[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{10}`
### Поведение AuthGuard:
- **Без кодов:** если авторизован → дашборд, иначе → форма авторизации
- **С кодами:** всегда показывать AuthFlow (даже для авторизованных)
### Поведение AuthFlow:
- **Новый номер телефона:** полный флоу (телефон → SMS → создание организации)
- **Авторизованный номер:** пропустить телефон/SMS, сразу к созданию организации
### Создание партнерства:
- Создаются записи в таблице `Counterparty` в ОБЕ стороны
- Создается `ReferralTransaction` с начислением сфер
- Устанавливается `referredById` у новой организации
### Создание реферальной связи:
- Создается `ReferralTransaction` с начислением сфер
- Устанавливается `referredById` у новой организации
- БЕЗ создания `Counterparty` записей
## ⚠️ ВАЖНЫЕ ОГРАНИЧЕНИЯ
### Архитектурные ограничения:
- **ОДИН ПОЛЬЗОВАТЕЛЬ** может иметь **МНОГО номеров телефонов**
- **ОДИН НОМЕР ТЕЛЕФОНА** привязан к **ОДНОМУ ПОЛЬЗОВАТЕЛЮ**
- **ОДИН НОМЕР ТЕЛЕФОНА** может иметь **МНОГО ОРГАНИЗАЦИЙ**
- Нельзя зарегистрировать один номер телефона дважды
### Уникальность организаций:
- Одна организация (по ИНН) может быть зарегистрирована только один раз
- API ключи селлеров должны быть уникальными
### Сохранение кодов:
- Партнерские/реферальные коды должны передаваться через все компоненты
- Коды не должны теряться при авторизации номера телефона
- Коды применяются при создании ОРГАНИЗАЦИИ, не пользователя или номера
## 🧪 ТЕСТОВЫЕ СЦЕНАРИИ
### Сценарий 1: Новый номер + партнерская ссылка
1. Переход по `register?partner=A2LA72BZZX`
2. Ввод нового телефона → SMS → авторизация
3. Создание новой организации
4. Проверка: создались партнерские связи, начислились сферы
### Сценарий 2: Авторизованный номер + партнерская ссылка
1. Уже авторизован на номере в системе
2. Переход по `register?partner=A2LA72BZZX`
3. Пропуск авторизации → сразу создание организации
4. Проверка: создались партнерские связи, начислились сферы
### Сценарий 3: Реферальная ссылка
1. Переход по `register?ref=A2LA72BZZX`
2. Авторизация (если нужно)
3. Создание организации
4. Проверка: начислились сферы, НЕТ партнерских связей
## ❓ ВОПРОСЫ ДЛЯ СОГЛАСОВАНИЯ
1. **Архитектура БД:** Нужна ли новая таблица для "мастер-пользователей" или изменить текущую схему?
2. **Связь пользователь-телефон:** Как будет происходить связывание номеров телефонов с одним пользователем?
3. **UI для авторизованных:** Показывать ли информацию "Вы создаете организацию для номера +7XXX"?
4. **Переключение организаций:** Как пользователь выбирает активную организацию из разных номеров?
5. **Лимиты:** Есть ли ограничения на количество номеров у пользователя или организаций у номера?
6. **Идентификация:** Как система определяет, что два номера принадлежат одному пользователю?

View File

@ -1,3 +1,15 @@
# 🔒 РЕЗЕРВНАЯ КОПИЯ - НЕ РЕДАКТИРОВАТЬ БЕЗ РАЗРЕШЕНИЯ!
> 🚨 **КРИТИЧЕСКИ ВАЖНО**: Это РЕЗЕРВНАЯ КОПИЯ файла rules-complete.md
>
> ❌ **ЗАПРЕЩЕНО РЕДАКТИРОВАТЬ БЕЗ ЯВНОГО РАЗРЕШЕНИЯ ПОЛЬЗОВАТЕЛЯ!**
>
> 📅 **Дата создания резерва**: 2025-08-08
> 🔐 **Статус**: ЗАЩИЩЕН ОТ ИЗМЕНЕНИЙ
> 📄 **Оригинальный файл**: rules-complete.md
---
# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v10.0 # ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v10.0
> ⚠️ **АБСОЛЮТНО ПОЛНЫЙ ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ**: Данный файл объединяет АБСОЛЮТНО ВСЕ правила системы: протоколы работы Claude Code, детальные протоколы по сложности, систему предотвращения нарушений, расширенную самопроверку, специальный UI/UX протокол и бизнес-правила. Визуальные правила вынесены в отдельный файл visual-design-rules.md с автоматической интеграцией. > ⚠️ **АБСОЛЮТНО ПОЛНЫЙ ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ**: Данный файл объединяет АБСОЛЮТНО ВСЕ правила системы: протоколы работы Claude Code, детальные протоколы по сложности, систему предотвращения нарушений, расширенную самопроверку, специальный UI/UX протокол и бизнес-правила. Визуальные правила вынесены в отдельный файл visual-design-rules.md с автоматической интеграцией.
@ -113,7 +125,7 @@
| ----------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- | | ----------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- |
| **Типы предметов** | [2](#2--типизация-предметов) | PRODUCT, CONSUMABLE, DEFECT, FINISHED_PRODUCT | | **Типы предметов** | [2](#2--типизация-предметов) | PRODUCT, CONSUMABLE, DEFECT, FINISHED_PRODUCT |
| **Кабинет фулфилмента** | [11](#11--кабинет-фулфилмента-полная-документация) | Склад, Услуги, Сотрудники, 6 модулей | | **Кабинет фулфилмента** | [11](#11--кабинет-фулфилмента-полная-документация) | Склад, Услуги, Сотрудники, 6 модулей |
| **Workflow поставок** | [5](#5--workflow-поставок) | 8 статусов, уведомления, роль логистики | | **Workflow поставок** | [5](#5--workflow-поставок) | 8 статусов, уведомления, логистика |
| **GraphQL запросы** | [18](#18--graphql-и-typescript-правила), [24](#24--технические-приложения) | Резолверы, мутации, типизация | | **GraphQL запросы** | [18](#18--graphql-и-typescript-правила), [24](#24--технические-приложения) | Резолверы, мутации, типизация |
| **Система партнерства** | [13](#13--система-партнерства-и-контрагентов) | Counterparty, WHOLESALE, заявки | | **Система партнерства** | [13](#13--система-партнерства-и-контрагентов) | Counterparty, WHOLESALE, заявки |
| **Рынки и маркет** | [10.1](#101-разделение-понятий-рынок-vs-маркет), [18.7](#187-правила-рынков-и-маркета) | РЫНОК ≠ МАРКЕТ, Organization.market | | **Рынки и маркет** | [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) - общая база знаний системы > - rules-unified.md (v3.0) - общая база знаний системы
@ -508,45 +522,7 @@ if (currentUser.organization.type !== 'FULFILLMENT') {
> 📌 **ВИЗУАЛЬНЫЕ ПРАВИЛА**: См. [visual-design-rules.md - Статусы поставок](#142-статусы-поставок---расширенная-цветовая-система) > 📌 **ВИЗУАЛЬНЫЕ ПРАВИЛА**: См. [visual-design-rules.md - Статусы поставок](#142-статусы-поставок---расширенная-цветовая-система)
### 5.1 Детализированная система статусов См. детальное описание в [workflow-catalog.md#1-workflow-поставок](./workflow-catalog.md#1-workflow-поставок)
**Статусы 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 Система уведомлений
**Обязательные уведомления:**
- Поставщику: о новом заказе
- Фулфилменту: о подтвержденной поставке
- Логистике: о назначении на заявку
- Селлеру: об изменении каждого статуса
--- ---
@ -1974,351 +1950,114 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
## 11. 🏭 КАБИНЕТ ФУЛФИЛМЕНТА ## 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. 🚚 КАБИНЕТ ЛОГИСТИКИ ## 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 - [ ] Проверить данные в базе через Prisma Studio
- [ ] Использовать `fetchPolicy: 'network-only'` для обхода кеша - [ ] Использовать `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. 🌐 ИНТЕГРАЦИИ С СИСТЕМОЙ ## 14. 🌐 ИНТЕГРАЦИИ С СИСТЕМОЙ
@ -3464,7 +3312,7 @@ const handleSuppliesClick = () => {
_Эта база знаний создана путем объединения rules-unified.md (v3.0) и fulfillment-cabinet-rules.md (v1.0) с устранением всех несоответствий и добавлением критически важных улучшений: быстрый справочник, глоссарий терминов, детальные алгоритмы процессов, edge cases._ _Эта база знаний создана путем объединения rules-unified.md (v3.0) и fulfillment-cabinet-rules.md (v1.0) с устранением всех несоответствий и добавлением критически важных улучшений: быстрый справочник, глоссарий терминов, детальные алгоритмы процессов, edge cases._
_Версия: 10.1_ _Версия: 10.2_
ата создания: 2025_ ата создания: 2025_
_Статус: ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ - ГОТОВ К РАЗРАБОТКЕ_ _Статус: ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ - ГОТОВ К РАЗРАБОТКЕ_
@ -3560,3 +3408,12 @@ _Статус: ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ - ГОТОВ К РАЗ
- **СИСТЕМА МАРКИРОВКИ**: 📖 ФАКТ, 🧠 ИНТЕРПРЕТАЦИЯ, ПРЕДПОЛОЖЕНИЕ, НЕ НАЙДЕНО - **СИСТЕМА МАРКИРОВКИ**: 📖 ФАКТ, 🧠 ИНТЕРПРЕТАЦИЯ, ПРЕДПОЛОЖЕНИЕ, НЕ НАЙДЕНО
- **СТОП-СЛОВА**: Список категоричных утверждений для избегания без доказательств - **СТОП-СЛОВА**: Список категоричных утверждений для избегания без доказательств
- **ОБЯЗАТЕЛЬНЫЕ ПРАВИЛА 11-13**: Указание источников и осторожные формулировки - **ОБЯЗАТЕЛЬНЫЕ ПРАВИЛА 11-13**: Указание источников и осторожные формулировки
### 🔗 ПАРТНЕРСКАЯ И РЕФЕРАЛЬНАЯ СИСТЕМА v10.2:
- **ДОБАВЛЕН РАЗДЕЛ 13.6**: Критическое различие партнерских и реферальных ссылок
- **ЧЕТКИЕ ОПРЕДЕЛЕНИЯ**: Партнерские (?partner=) vs Реферальные (?ref=) ссылки
- **ТЕХНИЧЕСКИЕ ПРАВИЛА**: Различия в обработке кодов в резолверах
- **UI СПЕЦИФИКАЦИИ**: Разные интерфейсы для партнерства и маркетинга
- **ЗАПРЕТЫ ПУТАНИЦЫ**: Строгие правила именования и терминологии
- **ПРИМЕРЫ СЦЕНАРИЕВ**: Деловое партнерство vs маркетинговые кампании

500
seller-ui-rules.md Normal file
View File

@ -0,0 +1,500 @@
# ПРАВИЛА UI/UX КАБИНЕТА СЕЛЛЕРА (SELLER)
> ⚠️ **ВАЖНО**: Это файл с UI/UX деталями кабинета селлера.
> Общие бизнес-правила находятся в **[rules-complete.md](./rules-complete.md)**
## Когда использовать этот файл:
- Работа с компонентами `/supplies`, `/my-supplies`
- UI/UX специфика кабинета селлера
- Интерфейсы создания поставок
- Визуальные правила компонентов селлера
## 1. 🛍️ СТРУКТУРА КАБИНЕТА СЕЛЛЕРА
### 1.1 Основные разделы
**СЕЛЛЕР (`SELLER`)** имеет доступ к следующим разделам:
- **Мои поставки** (`/my-supplies`) - управление поставками
- **Маркет** (`/market`) - просмотр глобального каталога
- **Партнеры** (`/partners`) - управление контрагентами
- **Мессенджер** (`/messenger`) - связь с партнерами
- **Настройки** (`/settings`) - профиль и настройки
- **Экономика** (`/economics`) - финансовая аналитика
### 1.2 Навигация и роутинг
#### При входе в систему:
```typescript
switch (user?.organization?.type) {
case 'SELLER':
router.push('/my-supplies') // Направляем на страницу поставок
break
}
```
#### Специальная логика роутинга:
> 📖 **Бизнес-логика роутинга**: См. [rules-complete.md#4-система-ролей-и-доступов](./rules-complete.md#4--система-ролей-и-доступов)
## 2. 🎨 UI/UX КОМПОНЕНТЫ
### 2.1 Dashboard компоненты
#### Основные компоненты кабинета:
- `SellerHomePage` - главный компонент селлера
- `SellerEconomicsPage` - экономическая аналитика
- `MySuppliesDashboard` - управление поставками
#### Wrapper-компоненты:
- `HomePageWrapper` - маршрутизация по типам организаций
- `EconomicsPageWrapper` - адаптивная экономика по кабинетам
### 2.2 Детальные правила горизонтального скролла поставщиков
**СТРУКТУРА И ОТОБРАЖЕНИЕ:**
- **Источник данных**: Партнеры типа `WHOLESALE` из раздела "Партнеры"
- **Контейнер**: Фиксированная высота 176px (h-44) с горизонтальным скроллом
- **Блок поставщиков**: Общая высота 180px, включает заголовок + контейнер скролла
- **Направление**: Слева направо (LTR)
- **Поведение**: Плавный скролл с автоскрытием полосы прокрутки
**РАЗМЕРЫ И АДАПТИВНОСТЬ:**
- **Десктоп**: Карточка 216×92px, отступы 12px между карточками, 16px от краев
- **Планшет**: Карточка 200×92px, отступы 12px между карточками
- **Мобильный**: Карточка 184×92px, отступы 12px между карточками
- **Высота блока**: 180px фиксированная для всего блока поставщиков
**ВЗАИМОДЕЙСТВИЕ:**
- **Навигация**: Колесо мыши (Shift+скролл), стрелки клавиатуры, свайп на тач
- **Выбор**: Клик по карточке → активная рамка + загрузка товаров в блок 2
- **Состояния**: Default, Hover (box-shadow), Active (цветная рамка), Loading (скелетон)
**ГРАНИЧНЫЕ СЛУЧАИ:**
- **1-4 карточки**: Выравнивание по левому краю, скролл неактивен
- **5+ карточек**: Полный горизонтальный скролл
- **Нет партнеров**: Заглушка с ссылкой на раздел "Партнеры"
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
**Критическая Flex-архитектура:**
```css
.parent-container {
display: flex;
gap: 16px;
min-height: 0;
}
.left-block {
flex: 1;
min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для overflow */
display: flex;
flex-direction: column;
}
.suppliers-container {
height: 180px; /* Общая высота блока */
flex-shrink: 0;
min-width: 0; /* Предотвращает растяжение */
}
.right-block {
width: 384px; /* w-96 */
flex-shrink: 0; /* Защита от сжатия */
}
```
**Контейнер скролла:**
```css
.suppliers-block {
display: flex;
overflow-x: auto;
scroll-behavior: smooth;
gap: 12px;
padding: 0 16px 8px 16px; /* px-4 pb-2 */
height: 176px; /* h-44 */
scrollbar-width: thin;
scrollbar-color: #64748b33 transparent;
}
.suppliers-block:hover {
scrollbar-color: #cbd5e0 #64748b22;
}
.supplier-card {
flex-shrink: 0;
width: 216px; /* Десктоп */
height: 92px; /* Фиксированная высота */
padding: 8px; /* p-2 */
transition: all 0.2s ease;
}
```
**СОДЕРЖАНИЕ КАРТОЧКИ ПОСТАВЩИКА:**
**Структура (3 строки в 92px высоты):**
- **Строка 1**: Название + рейтинг (справа, если есть)
- **Строка 2**: ИНН (формат "ИНН: 1234567890")
- **Строка 3**: Бейдж рынка (отдельная строка)
**Элементы:**
- **Аватар**: Размер xs, слева с gap-2
- **Текст**: text-xs для компактности
- **Отступы**: mb-1 между строками 1-2, mb-0.5 между строками 2-3
- **Padding карточки**: 8px (p-2)
**ЦВЕТОВАЯ СХЕМА РЫНКОВ:**
- **"Садовод"** (sadovod): Зеленый `bg-green-500/20 text-green-300 border-green-500/30`
- **"ТЯК Москва"** (tyak-moscow): Синий `bg-blue-500/20 text-blue-300 border-blue-500/30`
- **Другие/не указан**: Серый `bg-gray-500/20 text-gray-300 border-gray-500/30`
**ДОСТУПНОСТЬ:**
- `role="tablist"` для контейнера
- `role="tab"` для карточек
- `aria-selected="true/false"` для выбранной карточки
- `tabindex="0"` для активной, `-1` для неактивных
### 2.3 Правила блока "Карточки товаров" (Блок 2)
**НАЗНАЧЕНИЕ И ЛОГИКА:**
- **Источник данных**: Товары выбранного поставщика из Блока 1
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
- **Поведение**: Горизонтальный скролл при множестве товаров
**АРХИТЕКТУРА И РАЗМЕРЫ:**
- **Внешний контейнер**: bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0
- **Внутренний контейнер скролла**: flex gap-3 overflow-x-auto p-4
- **Стилизация скролла**: scrollbarWidth: 'thin' для тонкой полосы прокрутки
- **Отступы**: padding: 16px (p-4) внутри, gap: 12px (gap-3) между карточками
- **Адаптивная высота**: по содержимому карточек (БЕЗ фиксированной высоты)
- **Визуальное единство**: стеклянный эффект как у других блоков системы
- **БЕЗ заголовков/иконок**: только чистые карточки товаров в контейнере
**РАЗМЕРЫ КАРТОЧЕК ТОВАРОВ:**
- **Компактная карточка**: 80×112px (w-20 h-28), соотношение 5:7
- **Адаптивность**: фиксированный размер для всех устройств
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
- **Рамка**: border-white/10, при выборе border-white/30
- **Фон**: bg-white/5 полупрозрачный
**ДЕЙСТВИЕ:**
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
### 2.4 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
#### **2.4.1 Размеры и позиционирование**
```tsx
<div className="w-72 flex-shrink-0">
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
```
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
- **Флекс**: `flex-shrink-0` - корзина не сжимается
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
#### **2.4.2 Автодобавление товаров**
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
```tsx
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
if (newQuantity > 0) {
// Автоматически добавляем товар в корзину
updateProductQuantity(product.id, newQuantity)
} else {
// Удаляем товар из корзины при количестве 0
removeFromCart(product.id)
}
}
```
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
#### **2.4.3 Структура корзины**
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
2. **Список товаров**:
- Название товара (БЕЗ суффикса "(с рецептурой)")
- Цена за единицу × количество
- Кнопка удаления (X справа)
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
4. **Итого**: Общая сумма с выделением зелёным цветом
5. **Кнопка действия**: "Создать поставку" с градиентом
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
#### **2.4.4 Единая функция расчета стоимости**
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
```tsx
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
const product = products.find((p) => p.id === productId)
if (!product) return 0
// Базовая цена товара
let total = (product.pricePerUnit || 0) * quantity
// Добавляем услуги
if (product.services && product.services.length > 0) {
const servicesTotal = product.services.reduce((sum, service) => {
return sum + (service.pricePerUnit || 0) * quantity
}, 0)
total += servicesTotal
}
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
if (product.ffConsumables && product.ffConsumables.length > 0) {
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
return sum + (consumable.price || 0) * quantity // ВАЖНО: .price!
}, 0)
total += ffConsumablesTotal
}
// Добавляем расходники продавца
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
return sum + (consumable.pricePerUnit || 0) * quantity
}, 0)
total += sellerConsumablesTotal
}
return total
}
```
#### **2.4.5 Синхронизация данных между блоками**
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
1. **Дата поставки**: Из Блока 3 (дата пикер)
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
```
Дата поставки: 08.08.2025
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
Логистическая компания: [Выпадающий список]
```
#### **2.4.6 Критические требования**
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
- Использовать optional chaining для всех вложенных объектов
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
- Мемоизация расчетов: `useMemo` для дорогих вычислений
- Debounce для инпутов количества
🚨 **UX КОНСИСТЕНТНОСТЬ**:
- Единые стили для всех корзин в системе
- Одинаковое поведение auto-add во всех формах
- Синхронная валидация данных
### 2.5 Трёхблочная архитектура страницы "Мои поставки"
**ПРИНЦИП**: Страница состоит из трёх визуально разделённых блоков
```
┌─────────────────────────────────────────┐
│ 1. БЛОК ТАБОВ (навигация) │
│ - Фиксированная высота │
│ - Glass-эффект │
│ - Иерархическая структура │
├─────────────────────────────────────────┤
│ 2. БЛОК СТАТИСТИКИ (метрики) │
│ - Контекстные данные │
│ - 4 карточки в ряд (desktop) │
│ - Динамическое обновление │
├─────────────────────────────────────────┤
│ 3. ОСНОВНОЙ БЛОК (контент) │
│ - Сохраняет весь функционал │
│ - Таблицы, фильтры, действия │
│ - Высота до низа sidebar │
└─────────────────────────────────────────┘
```
**ПРАВИЛО**: Статистика меняется в зависимости от выбранных табов
**Для путей "Фулфилмент → Товар → Карточки/Поставщики":**
- Всего поставок
- Активных поставок
- Сумма активных поставок
- В пути
**ФОРМУЛА РАСЧЕТА ВЫСОТЫ**:
```css
height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
```
**ПРАВИЛО ВЫРАВНИВАНИЯ**:
- Нижняя граница основного блока должна быть на одном уровне с нижней границей sidebar
- При изменении размера окна высота пересчитывается
- Внутренний скролл: `overflow-y-auto`
### 2.6 Четырёхблочная архитектура создания поставки расходников
#### **Структура страницы**:
**БЛОК 1: ПОСТАВЩИКИ** _(обязательный, 180px)_:
- Карточки поставщиков из раздела "Партнеры"
- Горизонтальный скролл при превышении ширины
- Выбор только одного поставщика одновременно
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(адаптивная высота - НОВЫЙ БЛОК)_:
- ТОЛЬКО минималистичные карточки товаров 80×112px
- ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
- Горизонтальный скролл при множестве товаров
- Клик добавляет товар в блок 3
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(flex-1, детальный каталог)_:
- Детальные карточки выбранных товаров
- Управление количеством и параметрами поставки
**БЛОК 4: КОРЗИНА И НАСТРОЙКИ** _(правая панель, 384px)_:
- Корзина поставки с выбранными товарами
- Настройки поставки (фулфилмент-центр, дата, логистика)
- Сортировка: цена, название, категория
- Фильтры: категория, ценовой диапазон
- Карточка с полем ввода количества и кнопками +/-
**БЛОК 3: КОРЗИНА** _(правая часть)_:
- **РАСПОЛОЖЕНИЕ**: Правая часть экрана
- **СОДЕРЖАНИЕ**:
- Счетчик видов расходников
- Детализация по каждому расходнику (название, количество, цена, сумма)
- Общая сумма всех расходников
- **УПРАВЛЕНИЕ**:
- Изменение количества (с валидацией остатков)
- Удаление позиций
- **ОБЯЗАТЕЛЬНЫЕ ПОЛЯ**:
- Выбор фулфилмент-центра (из партнеров)
- Дата поставки (не прошедшая, по умолчанию - текущая)
### 2.7 Многоуровневая таблица поставок
#### **ПЕРВЫЙ УРОВЕНЬ** _(основной список)_:
- **СОРТИРОВКА**: Номер поставки от большего к меньшему
- **ОБЯЗАТЕЛЬНЫЕ КОЛОНКИ**:
- Порядковый номер поставки
- Количество видов расходников
- Стоимость всей поставки
- Количество категорий
- Статус поставки
#### **ВТОРОЙ УРОВЕНЬ** _(детализация по клику)_:
- **АКТИВАЦИЯ**: По клику на строку первого уровня
- **СОДЕРЖАНИЕ**:
- Название расходника
- Количество
- Цена
- Категория
- Поставщик
- **ОГРАНИЧЕНИЯ**: Только просмотр, редактирование запрещено
## 3. 🛠️ ГРАФИЧЕСКИЙ ИНТЕРФЕЙС И КОМПОНЕНТЫ
### 3.1 React компоненты селлера
**Основные компоненты:**
- `SellerHomePage` - главная страница селлера (4 типо-зависимых компонента)
- `SellerEconomicsPage` - экономическая аналитика селлера
### 3.2 Роутинг для селлера
```typescript
switch (user?.organization?.type) {
case 'SELLER':
router.push('/supplies')
break
}
```
### 3.3 Структура разделов кабинета
**🛍️ СЕЛЛЕР (`SELLER`):**
- Мои поставки (`/supplies`) - управление заказами товаров
- WB Интеграция (`/wb-integration`) - связь с Wildberries
## 4. 🛠️ GRAPHQL API
### 4.1 Основные запросы (Queries)
#### Получение карточек селлера:
```graphql
query GetSellerCards {
myMarketplaceCards {
id
title
marketplace
article
linkedProductId # null если свободна
linkedProduct {
# для отображения занятости
id
name
}
}
}
```
---
**Последнее обновление**: Август 2025
**Связанные файлы**:
- [rules-complete.md](./rules-complete.md) - Общие бизнес-правила
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила

3644
server.log
View File

@ -1,3644 +0,0 @@
> sferav@0.1.0 dev
> next dev --turbopack
▲ Next.js 15.4.1 (Turbopack)
- Local: http://localhost:3000
- Network: http://192.168.0.104:3000
- Environments: .env
✓ Starting...
✓ Ready in 823ms
○ Compiling /api/graphql ...
✓ Compiled /api/graphql in 1234ms
🚀 Проверка инициализации базы данных...
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 582ms
✨ Инициализация базы данных завершена
POST /api/graphql 200 in 3932ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 4435ms
POST /api/graphql 200 in 4521ms
POST /api/graphql 200 in 1014ms
POST /api/graphql 200 in 4627ms
POST /api/graphql 200 in 1194ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 776ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 466ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 288ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 716ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 1026ms
POST /api/graphql 200 in 605ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
POST /api/graphql 200 in 369ms
POST /api/graphql 200 in 782ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 302ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 381ms
POST /api/graphql 200 in 389ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 275ms
POST /api/graphql 200 in 326ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 738ms
POST /api/graphql 200 in 1119ms
POST /api/graphql 200 in 1131ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 1185ms
POST /api/graphql 200 in 1052ms
POST /api/graphql 200 in 1198ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 482ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 286ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 710ms
POST /api/graphql 200 in 622ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 601ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
POST /api/graphql 200 in 458ms
POST /api/graphql 200 in 1121ms
○ Compiling / ...
✓ Compiled / in 1642ms
GET / 200 in 1852ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 397ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 343ms
POST /api/graphql 200 in 341ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 265ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 279ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 1281ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 683ms
POST /api/graphql 200 in 589ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 602ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 1193ms
POST /api/graphql 200 in 602ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 379ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 267ms
○ Compiling /supplies/create-suppliers ...
✓ Compiled /supplies/create-suppliers in 580ms
GET /supplies/create-suppliers 200 in 797ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
✓ Compiled /favicon.ico in 130ms
GET /favicon.ico?favicon.45db1c09.ico 200 in 393ms
POST /api/graphql 200 in 413ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
POST /api/graphql 200 in 325ms
POST /api/graphql 200 in 499ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 827ms
POST /api/graphql 200 in 833ms
POST /api/graphql 200 in 629ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 615ms
POST /api/graphql 200 in 619ms
GET /supplies/create-suppliers 200 in 118ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GET /favicon.ico?favicon.45db1c09.ico 200 in 245ms
POST /api/graphql 200 in 371ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
POST /api/graphql 200 in 321ms
POST /api/graphql 200 in 335ms
POST /api/graphql 200 in 724ms
POST /api/graphql 200 in 950ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
🏢 ORGANIZATION_PRODUCTS RESOLVER - ВЫЗВАН: {
userId: 'cmdxbhacs0004y52vqs3cu86k',
organizationId: 'cmdxbievj000ay52vliq4h23r',
search: '',
category: '',
type: 'ТОВАР',
timestamp: '2025-08-04T17:38:34.516Z'
}
POST /api/graphql 200 in 58ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 387ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 285ms
POST /api/graphql 200 in 358ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 285ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 282ms
POST /api/graphql 200 in 705ms
POST /api/graphql 200 in 818ms
POST /api/graphql 200 in 819ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 638ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 602ms
POST /api/graphql 200 in 601ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 408ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 385ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 794ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 606ms
POST /api/graphql 200 in 692ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmhhY3MwMDA0eTUydnFzM2N1ODZrIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDMyNDQ0NywiZXhwIjoxNzU2OTE2NDQ3fQ.G3fwU6DYC7Ue649ibdA7G3D9Xy3DBHQukcXNVRaFQ6E
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbhacs0004y52vqs3cu86k', phone: '78888888888' }
POST /api/graphql 200 in 291ms
POST /api/graphql 200 in 822ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 273ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 273ms
POST /api/graphql 200 in 359ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 295ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
POST /api/graphql 200 in 699ms
POST /api/graphql 200 in 695ms
POST /api/graphql 200 in 810ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 293ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Yml2NjQwMDBjeTUydnhoNTdkMnJ5IiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDMyNDUyMCwiZXhwIjoxNzU2OTE2NTIwfQ.JFgG4M95MTlnkRxJET8ZkcGEwVtaOaH4ofbosZbk9yQ
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbiv64000cy52vxh57d2ry', phone: '76666666666' }
POST /api/graphql 200 in 1482ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4YmkxcHYwMDA5eTUydnZrenkzOWdyIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDMyNDQ4MiwiZXhwIjoxNzU2OTE2NDgyfQ.uudvqWO9a8S0RAiFO1J-rTHHUO13x3QZ5m1d7VQ8YOU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbi1pv0009y52vvkzy39gr', phone: '77777777777' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR4Ymc2cmswMDAxeTUydjNnbm9ncGcyIiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDMyNDM5NSwiZXhwIjoxNzU2OTE2Mzk1fQ.kQHVo5zIbifiqwkxnXKGiKh0fZJYEvC8LSG1k-C929s
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdxbg6rk0001y52v3gnogpg2', phone: '79999999999' }
POST /api/graphql 200 in 932ms
POST /api/graphql 200 in 932ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
○ Compiling /_error ...
✓ Compiled /_error in 847ms
POST /api/graphql 500 in 1332ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 92ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 70ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 132ms
POST /api/graphql 500 in 129ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 118ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 85ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 114ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 138ms
POST /api/graphql 500 in 138ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 77ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 143ms
POST /api/graphql 500 in 149ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 51ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 95ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 61ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 114ms
POST /api/graphql 500 in 119ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 92ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 80ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 105ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 121ms
POST /api/graphql 500 in 124ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 93ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 85ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 94ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 141ms
POST /api/graphql 500 in 145ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 83ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 91ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 97ms
POST /api/graphql 500 in 106ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 113ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 95ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 188ms
POST /api/graphql 500 in 188ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 90ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 92ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 77ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 283ms
POST /api/graphql 500 in 295ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 222ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 84ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 152ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 101ms
POST /api/graphql 500 in 109ms
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/supplies/create-suppliers/page/app-build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/favicon.ico/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/app/api/graphql/[__metadata_id__]/route/app-paths-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
POST /api/graphql 500 in 205ms
POST /api/graphql 500 in 60ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
[Error: ENOENT: no such file or directory, open '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/veronikasmirnova/Desktop/Projects/sfera/.next/server/pages/_app/build-manifest.json'
}
POST /api/graphql 500 in 336ms
POST /api/graphql 500 in 42ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 108ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 115ms
POST /api/graphql 500 in 118ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 75ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 65ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 103ms
POST /api/graphql 500 in 101ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 86ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 56ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 93ms
POST /api/graphql 500 in 95ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 53ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 140ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 98ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 84ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 108ms
POST /api/graphql 500 in 110ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 119ms
POST /api/graphql 500 in 116ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 94ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 83ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 94ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 113ms
POST /api/graphql 500 in 116ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 70ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 160ms
POST /api/graphql 500 in 171ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 110ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 119ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 66ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 122ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 115ms
POST /api/graphql 500 in 124ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 115ms
POST /api/graphql 500 in 117ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 90ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 82ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 79ms
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
GraphQLError: Syntax Error: Unexpected character: U+0422.
at parseDocument (../src/index.ts:96:25)
at Module.gql (../src/index.ts:134:9)
at [project]/src/graphql/typedefs.ts [app-route] (ecmascript) (src/graphql/typedefs.ts:3:28)
at [project]/src/app/api/graphql/route.ts [app-route] (ecmascript) (src/app/api/graphql/route.ts:5:0)
at Object.<anonymous> (.next/server/app/api/graphql/route.js:15:9)
1 | import { gql } from "graphql-tag";
2 |
> 3 | export const typeDefs = gql`
| ^
4 | scalar DateTime
5 |
6 | type Query { {
path: undefined,
locations: [Array],
extensions: [Object: null prototype] {},
page: '/api/graphql'
}
POST /api/graphql 500 in 106ms
POST /api/graphql 500 in 110ms
[?25h

View File

@ -51,11 +51,22 @@ const handler = startServerAndCreateNextHandler<NextRequest, Context>(server, {
prisma, prisma,
} }
} else if (decoded.userId && decoded.phone) { } else if (decoded.userId && decoded.phone) {
// Получаем пользователя с организацией из базы
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
include: {
organization: {
select: { id: true, type: true }
}
}
})
return { return {
user: { user: user ? {
id: decoded.userId, id: user.id,
phone: decoded.phone, phone: decoded.phone,
}, organizationId: user.organization?.id
} : null,
admin: null, admin: null,
prisma, prisma,
} }

View File

@ -9,9 +9,50 @@ import { AuthGuard } from '@/components/auth-guard'
function RegisterContent() { function RegisterContent() {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const partnerCode = searchParams.get('partner') const partnerCode = searchParams.get('partner')
const referralCode = searchParams.get('ref')
console.log('🔍 RegisterContent - URL параметры:', {
partnerCode,
referralCode,
searchParams: Object.fromEntries(searchParams.entries())
})
// Валидация: нельзя использовать оба параметра одновременно
if (partnerCode && referralCode) {
console.error('Попытка использовать и ref и partner одновременно')
redirect('/register') // Редирект на чистую регистрацию
return null
}
// Валидация формата кода (10 символов, только разрешенные)
const isValidCode = (code: string | null): boolean => {
if (!code) return true // null/undefined разрешены
return /^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{10}$/.test(code)
}
if (referralCode && !isValidCode(referralCode)) {
console.error(`Недействительный реферальный код: ${referralCode}`)
redirect('/register')
return null
}
if (partnerCode && !isValidCode(partnerCode)) {
console.error(`Недействительный партнерский код: ${partnerCode}`)
redirect('/register')
return null
}
console.log('🚀 RegisterContent - Передача в AuthFlow:', { partnerCode, referralCode })
// Если есть реферальный или партнерский код, всегда показываем AuthFlow
// даже для авторизованных пользователей (для создания дополнительных организаций)
if (partnerCode || referralCode) {
console.log('🎯 RegisterContent - Принудительный показ AuthFlow из-за наличия кода')
return <AuthFlow partnerCode={partnerCode} referralCode={referralCode} />
}
return ( return (
<AuthGuard fallback={<AuthFlow partnerCode={partnerCode} />}> <AuthGuard fallback={<AuthFlow partnerCode={partnerCode} referralCode={referralCode} />}>
{/* Если пользователь авторизован, перенаправляем в дашборд */} {/* Если пользователь авторизован, перенаправляем в дашборд */}
{redirect('/dashboard')} {redirect('/dashboard')}
</AuthGuard> </AuthGuard>

View File

@ -3,6 +3,8 @@
import { CheckCircle } from 'lucide-react' import { CheckCircle } from 'lucide-react'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '@/hooks/useAuth'
import { CabinetSelectStep } from './cabinet-select-step' import { CabinetSelectStep } from './cabinet-select-step'
import { ConfirmationStep } from './confirmation-step' import { ConfirmationStep } from './confirmation-step'
import { InnStep } from './inn-step' import { InnStep } from './inn-step'
@ -39,14 +41,31 @@ interface AuthData {
ozonApiValidation: ApiKeyValidation | null ozonApiValidation: ApiKeyValidation | null
isAuthenticated: boolean isAuthenticated: boolean
partnerCode?: string | null partnerCode?: string | null
referralCode?: string | null
} }
interface AuthFlowProps { interface AuthFlowProps {
partnerCode?: string | null partnerCode?: string | null
referralCode?: string | null
} }
export function AuthFlow({ partnerCode }: AuthFlowProps = {}) { export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
const [step, setStep] = useState<AuthStep>('phone') const { isAuthenticated, user } = useAuth()
console.log('🎢 AuthFlow - Полученные props:', { partnerCode, referralCode })
console.log('🎢 AuthFlow - Статус авторизации:', { isAuthenticated, hasUser: !!user })
// Определяем начальный шаг в зависимости от авторизации
const initialStep = isAuthenticated ? 'cabinet-select' : 'phone'
const [step, setStep] = useState<AuthStep>(initialStep)
// Определяем тип регистрации на основе параметров
// Только один из них должен быть активен (валидация уже прошла в RegisterPage)
const registrationType = partnerCode ? 'PARTNER' : (referralCode ? 'REFERRAL' : null)
const activeCode = partnerCode || referralCode || null
console.log('🎢 AuthFlow - Обработанные данные:', { registrationType, activeCode })
const [authData, setAuthData] = useState<AuthData>({ const [authData, setAuthData] = useState<AuthData>({
phone: '', phone: '',
smsCode: '', smsCode: '',
@ -58,8 +77,23 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
ozonApiKey: '', ozonApiKey: '',
ozonApiValidation: null, ozonApiValidation: null,
isAuthenticated: false, 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(() => { useEffect(() => {
@ -197,7 +231,13 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
return ( return (
<> <>
{step === 'phone' && <PhoneStep onNext={handlePhoneNext} />} {step === 'phone' && (
<PhoneStep
onNext={handlePhoneNext}
registrationType={registrationType}
referrerCode={activeCode}
/>
)}
{step === 'sms' && <SmsStep phone={authData.phone} onNext={handleSmsNext} onBack={handleSmsBack} />} {step === 'sms' && <SmsStep phone={authData.phone} onNext={handleSmsNext} onBack={handleSmsBack} />}
{step === 'cabinet-select' && <CabinetSelectStep onNext={handleCabinetNext} onBack={handleCabinetBack} />} {step === 'cabinet-select' && <CabinetSelectStep onNext={handleCabinetNext} onBack={handleCabinetBack} />}
{step === 'inn' && <InnStep onNext={handleInnNext} onBack={handleInnBack} />} {step === 'inn' && <InnStep onNext={handleInnNext} onBack={handleInnBack} />}
@ -208,13 +248,15 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
<ConfirmationStep <ConfirmationStep
data={{ data={{
phone: authData.phone, phone: authData.phone,
cabinetType: authData.cabinetType!, cabinetType: authData.cabinetType as 'fulfillment' | 'seller' | 'logist' | 'wholesale',
inn: authData.inn || undefined, inn: authData.inn || undefined,
organizationData: authData.organizationData || undefined, organizationData: authData.organizationData || undefined,
wbApiKey: authData.wbApiKey || undefined, wbApiKey: authData.wbApiKey || undefined,
wbApiValidation: authData.wbApiValidation || undefined, wbApiValidation: authData.wbApiValidation || undefined,
ozonApiKey: authData.ozonApiKey || undefined, ozonApiKey: authData.ozonApiKey || undefined,
ozonApiValidation: authData.ozonApiValidation || undefined, ozonApiValidation: authData.ozonApiValidation || undefined,
referralCode: authData.referralCode,
partnerCode: authData.partnerCode,
}} }}
onConfirm={handleConfirmation} onConfirm={handleConfirmation}
onBack={handleConfirmationBack} onBack={handleConfirmationBack}

View File

@ -33,6 +33,8 @@ interface ConfirmationStepProps {
wbApiValidation?: ApiKeyValidation wbApiValidation?: ApiKeyValidation
ozonApiKey?: string ozonApiKey?: string
ozonApiValidation?: ApiKeyValidation ozonApiValidation?: ApiKeyValidation
referralCode?: string | null
partnerCode?: string | null
} }
onConfirm: () => void onConfirm: () => void
onBack: () => void onBack: () => void
@ -65,6 +67,13 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
const handleConfirm = async () => { const handleConfirm = async () => {
setIsLoading(true) setIsLoading(true)
setError(null) setError(null)
console.log('📝 ConfirmationStep - Данные для регистрации:', {
cabinetType: data.cabinetType,
inn: data.inn,
referralCode: data.referralCode,
partnerCode: data.partnerCode
})
try { try {
let result let result
@ -73,16 +82,25 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
(data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') && (data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') &&
data.inn data.inn
) { ) {
console.log('📝 ConfirmationStep - Вызов registerFulfillmentOrganization с кодами:', {
referralCode: data.referralCode,
partnerCode: data.partnerCode
})
result = await registerFulfillmentOrganization( result = await registerFulfillmentOrganization(
data.phone.replace(/\D/g, ''), data.phone.replace(/\D/g, ''),
data.inn, data.inn,
getOrganizationType(data.cabinetType), getOrganizationType(data.cabinetType),
data.referralCode,
data.partnerCode,
) )
} else if (data.cabinetType === 'seller') { } else if (data.cabinetType === 'seller') {
result = await registerSellerOrganization({ result = await registerSellerOrganization({
phone: data.phone.replace(/\D/g, ''), phone: data.phone.replace(/\D/g, ''),
wbApiKey: data.wbApiKey, wbApiKey: data.wbApiKey,
ozonApiKey: data.ozonApiKey, ozonApiKey: data.ozonApiKey,
referralCode: data.referralCode,
partnerCode: data.partnerCode,
}) })
} }

View File

@ -13,9 +13,11 @@ import { AuthLayout } from './auth-layout'
interface PhoneStepProps { interface PhoneStepProps {
onNext: (phone: string) => void 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 [phone, setPhone] = useState('')
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@ -96,6 +98,35 @@ export function PhoneStep({ onNext }: PhoneStepProps) {
totalSteps={5} totalSteps={5}
stepName="Авторизация" stepName="Авторизация"
> >
{/* Индикатор типа регистрации */}
{registrationType && (
<div className="mb-6 p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/20">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${
registrationType === 'PARTNER'
? 'bg-purple-500/20 border border-purple-500/30'
: 'bg-blue-500/20 border border-blue-500/30'
}`}>
<span className="text-xl">
{registrationType === 'PARTNER' ? '🤝' : '📎'}
</span>
</div>
<div className="flex-1">
<p className="text-sm font-medium text-white">
{registrationType === 'PARTNER'
? 'Регистрация по партнерской ссылке'
: 'Регистрация по реферальной ссылке'}
</p>
<p className="text-xs text-white/60 mt-1">
{registrationType === 'PARTNER'
? 'Вы получите +100 сфер ⚡ и автоматически станете партнером'
: 'Вы получите +100 сфер ⚡ за регистрацию'}
</p>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="phone" className="text-white text-sm font-medium flex items-center gap-2"> <Label htmlFor="phone" className="text-white text-sm font-medium flex items-center gap-2">

View File

@ -10,8 +10,6 @@ import {
CreditCard, CreditCard,
Key, Key,
Edit3, Edit3,
ExternalLink,
Copy,
CheckCircle, CheckCircle,
AlertTriangle, AlertTriangle,
MessageCircle, MessageCircle,
@ -53,8 +51,6 @@ export function UserSettings() {
type: 'success' | 'error' type: 'success' | 'error'
text: string text: string
} | null>(null) } | null>(null)
const [partnerLink, setPartnerLink] = useState('')
const [isGenerating, setIsGenerating] = useState(false)
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false) const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
const [localAvatarUrl, setLocalAvatarUrl] = useState<string | null>(null) const [localAvatarUrl, setLocalAvatarUrl] = useState<string | null>(null)
const phoneInputRef = useRef<HTMLInputElement | null>(null) const phoneInputRef = useRef<HTMLInputElement | null>(null)
@ -278,56 +274,6 @@ export function UserSettings() {
const profileStatus = checkProfileCompleteness() const profileStatus = checkProfileCompleteness()
const isIncomplete = profileStatus.percentage < 100 const isIncomplete = profileStatus.percentage < 100
const generatePartnerLink = async () => {
if (!user?.id) return
setIsGenerating(true)
setSaveMessage(null)
try {
// Генерируем уникальный код партнера
const partnerCode = btoa(user.id + Date.now())
.replace(/[^a-zA-Z0-9]/g, '')
.substring(0, 12)
const link = `${window.location.origin}/register?partner=${partnerCode}`
setPartnerLink(link)
setSaveMessage({
type: 'success',
text: 'Партнерская ссылка сгенерирована!',
})
// TODO: Сохранить партнерский код в базе данных
} catch (error) {
console.error('Error generating partner link:', error)
setSaveMessage({ type: 'error', text: 'Ошибка при генерации ссылки' })
} finally {
setIsGenerating(false)
}
}
const handleCopyLink = async () => {
if (!partnerLink) {
await generatePartnerLink()
return
}
try {
await navigator.clipboard.writeText(partnerLink)
setSaveMessage({ type: 'success', text: 'Ссылка скопирована!' })
} catch (error) {
console.error('Error copying to clipboard:', error)
setSaveMessage({ type: 'error', text: 'Ошибка при копировании' })
}
}
const handleOpenLink = async () => {
if (!partnerLink) {
await generatePartnerLink()
return
}
window.open(partnerLink, '_blank')
}
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0] const file = event.target.files?.[0]
@ -1589,61 +1535,23 @@ export function UserSettings() {
</div> </div>
</div> </div>
{(user?.organization?.type === 'FULFILLMENT' || <div className="space-y-6">
user?.organization?.type === 'LOGIST' || <div className="text-center py-12">
user?.organization?.type === 'WHOLESALE') && ( <Settings className="h-16 w-16 text-white/20 mx-auto mb-4" />
<div className="space-y-6"> <h3 className="text-lg font-medium text-white mb-2">
<div> Инструменты в разработке
<h4 className="text-white font-medium mb-2">Партнерская программа</h4> </h3>
<p className="text-white/70 text-sm mb-4"> <p className="text-white/60 text-sm max-w-md mx-auto">
Приглашайте новых контрагентов по уникальной ссылке. При регистрации они автоматически Здесь будут размещены полезные бизнес-инструменты:
становятся вашими партнерами. калькуляторы, аналитика, планировщики и автоматизация процессов.
</p> </p>
<div className="mt-6">
<div className="space-y-3"> <Badge variant="outline" className="bg-blue-500/20 text-blue-300 border-blue-500/30">
<div className="flex gap-2"> Скоро появится
<Button </Badge>
size="sm"
variant="outline"
className="glass-secondary text-white hover:text-white cursor-pointer text-xs px-3 py-2"
onClick={generatePartnerLink}
disabled={isGenerating}
>
<RefreshCw className={`h-3 w-3 mr-1 ${isGenerating ? 'animate-spin' : ''}`} />
{isGenerating ? 'Генерируем...' : 'Сгенерировать ссылку'}
</Button>
</div>
{partnerLink && (
<div className="space-y-2">
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
className="glass-secondary text-white hover:text-white cursor-pointer text-xs px-3 py-2"
onClick={handleOpenLink}
>
<ExternalLink className="h-3 w-3 mr-1" />
Открыть ссылку
</Button>
<Button
size="sm"
variant="outline"
className="glass-secondary text-white hover:text-white cursor-pointer px-2"
onClick={handleCopyLink}
>
<Copy className="h-3 w-3" />
</Button>
</div>
<p className="text-white/60 text-xs">
Ваша партнерская ссылка сгенерирована и готова к использованию
</p>
</div>
)}
</div>
</div> </div>
</div> </div>
)} </div>
</Card> </Card>
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@ -15,11 +15,15 @@ import {
Mail, Mail,
MapPin, MapPin,
X, X,
Copy,
Gift,
} from 'lucide-react' } from 'lucide-react'
import React, { useState, useMemo } from 'react' import React, { useState, useMemo } from 'react'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { GlassInput } from '@/components/ui/input' import { GlassInput } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
@ -29,6 +33,7 @@ import {
GET_INCOMING_REQUESTS, GET_INCOMING_REQUESTS,
GET_OUTGOING_REQUESTS, GET_OUTGOING_REQUESTS,
SEARCH_ORGANIZATIONS, SEARCH_ORGANIZATIONS,
GET_MY_PARTNER_LINK,
} from '@/graphql/queries' } from '@/graphql/queries'
import { OrganizationAvatar } from './organization-avatar' import { OrganizationAvatar } from './organization-avatar'
@ -68,6 +73,7 @@ export function MarketCounterparties() {
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES) const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS) const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS)
const { data: outgoingData, loading: outgoingLoading } = useQuery(GET_OUTGOING_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, { const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, {
refetchQueries: [ refetchQueries: [
@ -103,6 +109,23 @@ export function MarketCounterparties() {
awaitRefetchQueries: true, 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 filteredAndSortedCounterparties = useMemo(() => {
const filtered = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => { const filtered = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => {
@ -298,6 +321,28 @@ export function MarketCounterparties() {
</TabsList> </TabsList>
<TabsContent value="counterparties" className="flex-1 overflow-hidden mt-3 flex flex-col"> <TabsContent value="counterparties" className="flex-1 overflow-hidden mt-3 flex flex-col">
{/* Блок с партнерской ссылкой */}
<Card className="glass-card p-4 mb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-purple-500/20 border border-purple-500/30">
<Gift className="h-5 w-5 text-purple-400" />
</div>
<div>
<h3 className="text-white font-medium">Пригласить партнера</h3>
<p className="text-white/60 text-sm">Прямое деловое сотрудничество с автоматическим добавлением в партнеры</p>
</div>
</div>
<Button
onClick={copyPartnerLink}
className="glass-button hover:bg-white/20 transition-all duration-200"
>
<Copy className="h-4 w-4 mr-2" />
Копировать ссылку
</Button>
</div>
</Card>
{/* Компактная панель фильтров */} {/* Компактная панель фильтров */}
<div className="glass-card p-3 mb-3 space-y-3"> <div className="glass-card p-3 mb-3 space-y-3">
<div className="flex flex-col xl:flex-row gap-3"> <div className="flex flex-col xl:flex-row gap-3">

View File

@ -13,6 +13,7 @@ import { MarketFulfillment } from '../market/market-fulfillment'
import { MarketLogistics } from '../market/market-logistics' import { MarketLogistics } from '../market/market-logistics'
import { MarketSellers } from '../market/market-sellers' import { MarketSellers } from '../market/market-sellers'
import { MarketSuppliers } from '../market/market-suppliers' import { MarketSuppliers } from '../market/market-suppliers'
import { ReferralsTab } from './referrals-tab'
export function PartnersDashboard() { export function PartnersDashboard() {
const { getSidebarMargin } = useSidebar() const { getSidebarMargin } = useSidebar()
@ -36,7 +37,7 @@ export function PartnersDashboard() {
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<Tabs defaultValue="counterparties" className="h-full flex flex-col"> <Tabs defaultValue="counterparties" className="h-full flex flex-col">
<TabsList <TabsList
className={`grid w-full grid-cols-5 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 ${ className={`grid w-full grid-cols-6 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 ${
hasIncomingRequests ? 'ring-2 ring-blue-400/50' : '' hasIncomingRequests ? 'ring-2 ring-blue-400/50' : ''
}`} }`}
> >
@ -75,6 +76,12 @@ export function PartnersDashboard() {
> >
Поставщик Поставщик
</TabsTrigger> </TabsTrigger>
<TabsTrigger
value="referrals"
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70"
>
Рефералы
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="counterparties" className="flex-1 overflow-hidden mt-6"> <TabsContent value="counterparties" className="flex-1 overflow-hidden mt-6">
@ -106,6 +113,10 @@ export function PartnersDashboard() {
<MarketSuppliers /> <MarketSuppliers />
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="referrals" className="flex-1 overflow-hidden mt-6">
<ReferralsTab />
</TabsContent>
</Tabs> </Tabs>
</div> </div>
</div> </div>

View File

@ -0,0 +1,420 @@
'use client'
import { useQuery } from '@apollo/client'
import {
Copy,
Gift,
Users,
TrendingUp,
Zap,
UserPlus,
ShoppingCart,
Search,
Filter,
Calendar,
Building,
CheckCircle,
X,
} from 'lucide-react'
import React, { useState, useMemo } from 'react'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { GlassInput } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { GET_REFERRAL_DASHBOARD_DATA } from '@/graphql/referral-queries'
export function ReferralsTab() {
console.log('🚀 ReferralsTab COMPONENT RENDERED!')
const [searchQuery, setSearchQuery] = useState('')
const [typeFilter, setTypeFilter] = useState<string>('all')
const [sourceFilter, setSourceFilter] = useState<string>('all')
// GraphQL запрос для получения данных
const { data, loading, error } = useQuery(GET_REFERRAL_DASHBOARD_DATA, {
fetchPolicy: 'cache-and-network',
errorPolicy: 'all',
})
console.log('🔥 ReferralsTab - useQuery result:', {
loading,
hasData: !!data,
error: error?.message,
data
})
// Извлекаем данные из GraphQL ответа или используем fallback для разработки
const referralLink = data?.myReferralLink || 'http://localhost:3000/register?ref=LOADING'
const stats = data?.myReferralStats || {
totalPartners: 0,
totalSpheres: 0,
monthlyPartners: 0,
monthlySpheres: 0,
}
const allReferrals = useMemo(() => data?.myReferrals?.referrals || [], [data])
const copyReferralLink = async () => {
try {
await navigator.clipboard.writeText(referralLink)
toast.success('Реферальная ссылка скопирована!', {
description: 'Теперь вы можете поделиться ей с партнерами',
})
} catch {
toast.error('Не удалось скопировать ссылку')
}
}
// Фильтрация и поиск
const filteredReferrals = useMemo(() => {
return allReferrals.filter((referral) => {
const matchesSearch =
!searchQuery ||
referral.organization.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
referral.organization.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
referral.organization.inn.includes(searchQuery)
const matchesType = typeFilter === 'all' || referral.organization.type === typeFilter
const matchesSource = sourceFilter === 'all' || referral.source === sourceFilter
return matchesSearch && matchesType && matchesSource
})
}, [allReferrals, searchQuery, typeFilter, sourceFilter])
const getTypeLabel = (type: string) => {
switch (type) {
case 'SELLER': return 'Селлер'
case 'WHOLESALE': return 'Поставщик'
case 'FULFILLMENT': return 'Фулфилмент'
case 'LOGIST': return 'Логистика'
default: return type
}
}
const getTypeBadgeStyles = (type: string) => {
switch (type) {
case 'SELLER': return 'bg-green-500/20 text-green-300 border-green-500/30'
case 'WHOLESALE': return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
case 'FULFILLMENT': return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
case 'LOGIST': return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
default: return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
}
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
const clearFilters = () => {
setSearchQuery('')
setTypeFilter('all')
setSourceFilter('all')
}
const hasActiveFilters = searchQuery || typeFilter !== 'all' || sourceFilter !== 'all'
return (
<div className="h-full flex flex-col space-y-6">
{/* Блок с реферальной ссылкой */}
<Card className="glass-card p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
<Gift className="h-5 w-5 text-yellow-400" />
</div>
<h3 className="text-lg font-semibold text-white">Ваша реферальная ссылка</h3>
</div>
<div className="flex items-center gap-4 mb-4">
<div className="flex-1 px-4 py-3 glass-input rounded-lg text-white/60 font-mono text-sm">
</div>
<Button
onClick={copyReferralLink}
className="glass-button hover:bg-white/20 transition-all duration-200"
>
<Copy className="h-4 w-4 mr-2" />
Копировать ссылку
</Button>
</div>
<p className="text-sm text-white/60">
Поделитесь ссылкой с партнерами и получайте <span className="text-yellow-400 font-medium">100 сфер </span> за каждую регистрацию.
Также получайте <span className="text-yellow-400 font-medium">50 сфер </span> при одобрении заявок от новых клиентов.
</p>
</Card>
{/* Статистика */}
<div className="grid grid-cols-4 gap-4">
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 rounded-lg bg-blue-500/20 border border-blue-500/30">
<Users className="h-4 w-4 text-blue-400" />
</div>
<div>
<p className="text-xs text-white/60 uppercase tracking-wide">Всего партнеров</p>
<p className="text-2xl font-bold text-white">
{loading ? (
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
) : (
stats.totalPartners
)}
</p>
</div>
</div>
</Card>
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
<Zap className="h-4 w-4 text-yellow-400" />
</div>
<div>
<p className="text-xs text-white/60 uppercase tracking-wide">Сфер заработано</p>
<div className="flex items-center gap-1">
<p className="text-2xl font-bold text-white">
{loading ? (
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
) : (
stats.totalSpheres
)}
</p>
<Zap className="h-5 w-5 text-yellow-400" />
</div>
</div>
</div>
</Card>
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 rounded-lg bg-green-500/20 border border-green-500/30">
<TrendingUp className="h-4 w-4 text-green-400" />
</div>
<div>
<p className="text-xs text-white/60 uppercase tracking-wide">Партнеров за месяц</p>
<p className="text-2xl font-bold text-white">
{loading ? (
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
) : (
stats.monthlyPartners
)}
</p>
</div>
</div>
</Card>
<Card className="glass-card p-4 hover:bg-white/5 transition-all duration-200">
<div className="flex items-center gap-3 mb-3">
<div className="p-2 rounded-lg bg-yellow-500/20 border border-yellow-500/30">
<Zap className="h-4 w-4 text-yellow-400" />
</div>
<div>
<p className="text-xs text-white/60 uppercase tracking-wide">Сфер за месяц</p>
<div className="flex items-center gap-1">
<p className="text-2xl font-bold text-white">
{loading ? (
<span className="inline-block h-8 w-12 bg-white/10 rounded animate-pulse" />
) : (
stats.monthlySpheres
)}
</p>
<Zap className="h-5 w-5 text-yellow-400" />
</div>
</div>
</div>
</Card>
</div>
{/* Фильтры */}
<Card className="glass-card p-4">
<div className="flex flex-col xl:flex-row gap-4">
{/* Поиск */}
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
<GlassInput
placeholder="Поиск по названию или ИНН..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 h-9"
/>
</div>
</div>
{/* Фильтры */}
<div className="flex gap-2">
<Select value={typeFilter} onValueChange={setTypeFilter}>
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[130px]">
<Filter className="h-3 w-3 mr-1" />
<SelectValue placeholder="Тип" />
</SelectTrigger>
<SelectContent className="glass-card border-white/20">
<SelectItem value="all">Все типы</SelectItem>
<SelectItem value="SELLER">Селлер</SelectItem>
<SelectItem value="WHOLESALE">Поставщик</SelectItem>
<SelectItem value="FULFILLMENT">Фулфилмент</SelectItem>
<SelectItem value="LOGIST">Логистика</SelectItem>
</SelectContent>
</Select>
<Select value={sourceFilter} onValueChange={setSourceFilter}>
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[140px]">
<Filter className="h-3 w-3 mr-1" />
<SelectValue placeholder="Источник" />
</SelectTrigger>
<SelectContent className="glass-card border-white/20">
<SelectItem value="all">Все источники</SelectItem>
<SelectItem value="REFERRAL_LINK">Реферальная ссылка</SelectItem>
<SelectItem value="AUTO_BUSINESS">Бизнес-сделка</SelectItem>
</SelectContent>
</Select>
{hasActiveFilters && (
<Button
variant="ghost"
size="sm"
onClick={clearFilters}
className="text-white/60 hover:text-white hover:bg-white/10 h-9 w-9 p-0"
>
<X className="h-3 w-3" />
</Button>
)}
</div>
</div>
{/* Краткая статистика */}
<div className="flex items-center justify-between mt-3 text-xs">
<div className="text-white/60">
Показано {filteredReferrals.length} из {allReferrals.length} партнеров
</div>
<div className="flex gap-2">
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-blue-500/10 text-blue-300">
<UserPlus className="h-3 w-3" />
<span>Рефералы: {allReferrals.filter(r => r.source === 'REFERRAL_LINK').length}</span>
</div>
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-orange-500/10 text-orange-300">
<ShoppingCart className="h-3 w-3" />
<span>Бизнес: {allReferrals.filter(r => r.source === 'AUTO_BUSINESS').length}</span>
</div>
</div>
</div>
</Card>
{/* Таблица партнеров */}
<Card className="glass-card flex-1 overflow-hidden">
<div className="h-full overflow-auto">
<div className="p-6 space-y-3">
{/* Заголовок таблицы */}
<div className="p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/10">
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-white/80">
<div className="col-span-2 flex items-center gap-2">
<Calendar className="h-4 w-4 text-blue-400" />
<span>Дата регистрации</span>
</div>
<div className="col-span-3 flex items-center gap-2">
<Building className="h-4 w-4 text-green-400" />
<span>Организация</span>
</div>
<div className="col-span-1 text-center flex items-center justify-center">
<span>Тип</span>
</div>
<div className="col-span-2 text-center flex items-center justify-center gap-1">
<UserPlus className="h-4 w-4 text-purple-400" />
<span>Источник</span>
</div>
<div className="col-span-2 text-center flex items-center justify-center gap-1">
<Zap className="h-4 w-4 text-yellow-400" />
<span>Начислено</span>
</div>
<div className="col-span-2 text-center flex items-center justify-center">
<span>Статус</span>
</div>
</div>
</div>
{/* Строки партнеров */}
{filteredReferrals.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64">
<Gift className="h-12 w-12 text-white/20 mb-2" />
<p className="text-white/60">
{loading ? 'Загрузка...' : allReferrals.length === 0 ? 'У вас пока нет партнеров' : 'Ничего не найдено'}
</p>
<p className="text-white/40 text-sm mt-1">
{loading
? 'Получаем данные о ваших партнерах...'
: allReferrals.length === 0
? 'Поделитесь реферальной ссылкой или начните работать с клиентами'
: 'Попробуйте изменить параметры поиска'
}
</p>
</div>
) : (
filteredReferrals.map((referral) => (
<div key={referral.id} className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10">
<div className="grid grid-cols-12 gap-4 items-center">
<div className="col-span-2 text-white/80">
<div className="flex items-center gap-2">
<Calendar className="h-3 w-3 text-white/40" />
<span className="text-sm">{formatDate(referral.registeredAt)}</span>
</div>
</div>
<div className="col-span-3">
<div>
<p className="text-white font-medium text-sm">
{referral.organization.name || referral.organization.fullName}
</p>
<p className="text-white/60 text-xs flex items-center gap-1">
<Building className="h-3 w-3" />
{referral.organization.inn}
</p>
</div>
</div>
<div className="col-span-1 text-center">
<Badge className={getTypeBadgeStyles(referral.organization.type) + ' text-xs'}>
{getTypeLabel(referral.organization.type)}
</Badge>
</div>
<div className="col-span-2 text-center">
<div className="flex items-center justify-center gap-2">
{referral.source === 'REFERRAL_LINK' ? (
<>
<UserPlus className="h-4 w-4 text-blue-400" />
<span className="text-blue-300 text-sm">Ссылка</span>
</>
) : (
<>
<ShoppingCart className="h-4 w-4 text-orange-400" />
<span className="text-orange-300 text-sm">Бизнес</span>
</>
)}
</div>
</div>
<div className="col-span-2 text-center">
<div className="flex items-center justify-center gap-2">
<span className="text-green-400 font-semibold">+{referral.spheresEarned}</span>
<Zap className="h-4 w-4 text-yellow-400" />
</div>
</div>
<div className="col-span-2 text-center">
<div className="flex items-center justify-center gap-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span className="text-green-300 text-sm">Активен</span>
</div>
</div>
</div>
</div>
))
)}
</div>
</div>
</Card>
</div>
)
}

View File

@ -3,6 +3,8 @@ import { PrismaClient } from '@prisma/client'
export interface Context { export interface Context {
user: { user: {
id: string id: string
phone?: string
organizationId?: string
organization?: { organization?: {
id: string id: string
type: string type: string

View File

@ -115,6 +115,7 @@ export const REGISTER_FULFILLMENT_ORGANIZATION = gql`
marketplace marketplace
isActive isActive
} }
referralPoints
} }
} }
} }
@ -163,6 +164,7 @@ export const REGISTER_SELLER_ORGANIZATION = gql`
marketplace marketplace
isActive isActive
} }
referralPoints
} }
} }
} }

View File

@ -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'

View File

@ -0,0 +1,135 @@
import { gql } from '@apollo/client'
// Получение реферальной ссылки
export const GET_MY_REFERRAL_LINK = gql`
query GetMyReferralLink {
myReferralLink
}
`
// Получение статистики по рефералам
export const GET_MY_REFERRAL_STATS = gql`
query GetMyReferralStats {
myReferralStats {
totalPartners
totalSpheres
monthlyPartners
monthlySpheres
referralsByType {
type
count
spheres
}
referralsBySource {
source
count
spheres
}
}
}
`
// Получение списка рефералов
export const GET_MY_REFERRALS = gql`
query GetMyReferrals(
$dateFrom: DateTime
$dateTo: DateTime
$type: OrganizationType
$source: ReferralSource
$search: String
$limit: Int
$offset: Int
) {
myReferrals(
dateFrom: $dateFrom
dateTo: $dateTo
type: $type
source: $source
search: $search
limit: $limit
offset: $offset
) {
referrals {
id
organization {
id
name
fullName
inn
type
createdAt
}
source
spheresEarned
registeredAt
status
}
totalCount
totalPages
}
}
`
// Получение истории транзакций
export const GET_MY_REFERRAL_TRANSACTIONS = gql`
query GetMyReferralTransactions($limit: Int, $offset: Int) {
myReferralTransactions(limit: $limit, offset: $offset) {
transactions {
id
spheres
type
description
createdAt
referral {
id
name
fullName
inn
type
}
}
totalCount
}
}
`
// Получение данных для дашборда рефералов (комбинированный запрос)
export const GET_REFERRAL_DASHBOARD_DATA = gql`
query GetReferralDashboardData {
myReferralLink
myReferralStats {
totalPartners
totalSpheres
monthlyPartners
monthlySpheres
referralsByType {
type
count
spheres
}
referralsBySource {
source
count
spheres
}
}
myReferrals(limit: 50) {
referrals {
id
organization {
id
name
fullName
inn
type
createdAt
}
source
spheresEarned
registeredAt
status
}
totalCount
}
}
`

View File

@ -9,13 +9,41 @@ import { MarketplaceService } from '@/services/marketplace-service'
import { SmsService } from '@/services/sms-service' import { SmsService } from '@/services/sms-service'
import { WildberriesService } from '@/services/wildberries-service' import { WildberriesService } from '@/services/wildberries-service'
import '@/lib/seed-init'; // Автоматическая инициализация БД import '@/lib/seed-init' // Автоматическая инициализация БД
// Сервисы // Сервисы
const smsService = new SmsService() const smsService = new SmsService()
const dadataService = new DaDataService() const dadataService = new DaDataService()
const marketplaceService = new MarketplaceService() const marketplaceService = new MarketplaceService()
// Функция генерации уникального реферального кода
const generateReferralCode = async (): Promise<string> => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
let attempts = 0
const maxAttempts = 10
while (attempts < maxAttempts) {
let code = ''
for (let i = 0; i < 10; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
// Проверяем уникальность
const existing = await prisma.organization.findUnique({
where: { referralCode: code }
})
if (!existing) {
return code
}
attempts++
}
// Если не удалось сгенерировать уникальный код, используем cuid как fallback
return `REF${Date.now()}${Math.random().toString(36).substr(2, 5).toUpperCase()}`
}
// Интерфейсы для типизации // Интерфейсы для типизации
interface Context { interface Context {
user?: { user?: {
@ -2023,6 +2051,53 @@ export const resolvers = {
return scheduleRecords 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: { Mutation: {
@ -2139,17 +2214,27 @@ export const resolvers = {
phone: string phone: string
inn: string inn: string
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE' type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE'
referralCode?: string
partnerCode?: string
} }
}, },
context: Context, 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) { if (!context.user) {
throw new GraphQLError('Требуется авторизация', { throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }, extensions: { code: 'UNAUTHENTICATED' },
}) })
} }
const { inn, type } = args.input const { inn, type, referralCode, partnerCode } = args.input
// Валидируем ИНН // Валидируем ИНН
if (!dadataService.validateInn(inn)) { if (!dadataService.validateInn(inn)) {
@ -2181,6 +2266,9 @@ export const resolvers = {
} }
} }
// Генерируем уникальный реферальный код
const generatedReferralCode = await generateReferralCode()
// Создаем организацию со всеми данными из DaData // Создаем организацию со всеми данными из DaData
const organization = await prisma.organization.create({ const organization = await prisma.organization.create({
data: { data: {
@ -2225,6 +2313,9 @@ export const resolvers = {
type: type, type: type,
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)), 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 { return {
success: true, success: true,
message: 'Организация успешно зарегистрирована', message: 'Организация успешно зарегистрирована',
@ -2263,17 +2454,28 @@ export const resolvers = {
wbApiKey?: string wbApiKey?: string
ozonApiKey?: string ozonApiKey?: string
ozonClientId?: string ozonClientId?: string
referralCode?: string
partnerCode?: string
} }
}, },
context: Context, 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) { if (!context.user) {
throw new GraphQLError('Требуется авторизация', { throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }, extensions: { code: 'UNAUTHENTICATED' },
}) })
} }
const { wbApiKey, ozonApiKey, ozonClientId } = args.input const { wbApiKey, ozonApiKey, ozonClientId, referralCode, partnerCode } = args.input
if (!wbApiKey && !ozonApiKey) { if (!wbApiKey && !ozonApiKey) {
return { return {
@ -2320,6 +2522,9 @@ export const resolvers = {
const tradeMark = validationResults[0]?.data?.tradeMark const tradeMark = validationResults[0]?.data?.tradeMark
const sellerName = validationResults[0]?.data?.sellerName const sellerName = validationResults[0]?.data?.sellerName
const shopName = tradeMark || sellerName || 'Магазин' const shopName = tradeMark || sellerName || 'Магазин'
// Генерируем уникальный реферальный код
const generatedReferralCode = await generateReferralCode()
const organization = await prisma.organization.create({ const organization = await prisma.organization.create({
data: { data: {
@ -2327,6 +2532,9 @@ export const resolvers = {
name: shopName, // Используем tradeMark как основное название name: shopName, // Используем tradeMark как основное название
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`, fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
type: 'SELLER', 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 { return {
success: true, success: true,
message: 'Селлер организация успешно зарегистрирована', message: 'Селлер организация успешно зарегистрирована',

View File

@ -5,6 +5,7 @@ import { authResolvers } from './auth'
import { employeeResolvers } from './employees' import { employeeResolvers } from './employees'
import { logisticsResolvers } from './logistics' import { logisticsResolvers } from './logistics'
import { suppliesResolvers } from './supplies' import { suppliesResolvers } from './supplies'
import { referralResolvers } from './referrals'
// Типы для резолверов // Типы для резолверов
interface ResolverObject { interface ResolverObject {
@ -22,6 +23,7 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
for (const resolver of resolvers) { for (const resolver of resolvers) {
if (resolver?.Query) { if (resolver?.Query) {
console.log('🔀 MERGING QUERY RESOLVERS:', Object.keys(resolver.Query))
Object.assign(result.Query, resolver.Query) Object.assign(result.Query, resolver.Query)
} }
if (resolver?.Mutation) { if (resolver?.Mutation) {
@ -40,6 +42,7 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
} }
} }
console.log('✅ FINAL MERGED Query RESOLVERS:', Object.keys(result.Query || {}))
return result return result
} }
@ -47,35 +50,26 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
// TODO: Постепенно убрать это после полного рефакторинга // TODO: Постепенно убрать это после полного рефакторинга
// Объединяем новые модульные резолверы с остальными старыми // Объединяем новые модульные резолверы с остальными старыми
export const resolvers = mergeResolvers( const mergedResolvers = mergeResolvers(
// Скалярные типы // Скалярные типы
{ {
JSON: JSONScalar, JSON: JSONScalar,
DateTime: DateTimeScalar, DateTime: DateTimeScalar,
}, },
// Новые модульные резолверы // Временно добавляем старые резолверы ПЕРВЫМИ, чтобы новые их перезаписали
authResolvers,
employeeResolvers,
logisticsResolvers,
suppliesResolvers,
// Временно добавляем старые резолверы, исключая уже вынесенные
{ {
Query: { Query: (() => {
...oldResolvers.Query, const { myEmployees, logisticsPartners, pendingSuppliesCount, myReferralLink, myPartnerLink, myReferralStats, myReferrals, ...filteredQuery } = oldResolvers.Query || {}
// Исключаем уже вынесенные Query return filteredQuery
myEmployees: undefined, })(),
logisticsPartners: undefined,
pendingSuppliesCount: undefined,
},
Mutation: { Mutation: {
...oldResolvers.Mutation, ...oldResolvers.Mutation,
// Исключаем уже вынесенные Mutation // Исключаем уже вынесенные Mutation
sendSmsCode: undefined, sendSmsCode: undefined,
verifySmsCode: undefined, // verifySmsCode: undefined, // НЕ исключаем - пока в старых резолверах
verifyInn: undefined, verifyInn: undefined,
registerFulfillmentOrganization: undefined, // registerFulfillmentOrganization: undefined, // НЕ исключаем - резолвер нужен!
createEmployee: undefined, createEmployee: undefined,
updateEmployee: undefined, updateEmployee: undefined,
deleteEmployee: undefined, deleteEmployee: undefined,
@ -91,4 +85,18 @@ export const resolvers = mergeResolvers(
// Employee берем из нового модуля // Employee берем из нового модуля
Employee: undefined, 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

View File

@ -0,0 +1,203 @@
import { prisma } from '@/lib/prisma'
import { GraphQLError } from 'graphql'
interface Context {
user: {
id: string
phone?: string
organizationId?: string
organization?: {
id: string
type: string
}
} | null
}
export const referralResolvers = {
Query: {
// Тестовый резолвер для проверки подключения
testReferral: () => {
console.log('🔥 TEST REFERRAL RESOLVER WORKS!')
return 'TEST OK'
},
// Простой тест резолвер для отладки
debugTest: () => {
console.log('🔥 DEBUG TEST RESOLVER CALLED!')
return 'DEBUG OK'
},
// Получить реферальную ссылку текущего пользователя
myReferralLink: async (_: unknown, __: unknown, context: Context) => {
console.log('🔥 REFERRAL RESOLVER CALLED!')
console.log('🔥 Process env APP_URL:', process.env.NEXT_PUBLIC_APP_URL)
console.log('🔗 myReferralLink DEBUG - context.user:', context.user)
if (!context.user?.organizationId) {
console.log('❌ myReferralLink DEBUG - NO organizationId! Returning placeholder')
return 'http://localhost:3000/register?ref=PLEASE_LOGIN'
}
console.log('🔍 myReferralLink DEBUG - Looking for organization:', context.user.organizationId)
const organization = await prisma.organization.findUnique({
where: { id: context.user.organizationId },
select: { referralCode: true }
})
console.log('🏢 myReferralLink DEBUG - Found organization:', organization)
if (!organization?.referralCode) {
console.log('❌ myReferralLink DEBUG - NO referralCode!')
throw new GraphQLError('Реферальный код не найден')
}
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?ref=${organization.referralCode}`
console.log('✅ myReferralLink DEBUG - Generated link:', link)
// Гарантированно возвращаем строку, не null
return link || 'http://localhost:3000/register?ref=ERROR'
},
// Получить партнерскую ссылку текущего пользователя
myPartnerLink: async (_: unknown, __: unknown, context: Context) => {
console.log('🔗 myPartnerLink DEBUG - context.user:', context.user)
if (!context.user?.organizationId) {
console.log('❌ myPartnerLink DEBUG - NO organizationId!')
throw new GraphQLError('Требуется авторизация и организация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
console.log('🔍 myPartnerLink DEBUG - Looking for organization:', context.user.organizationId)
const organization = await prisma.organization.findUnique({
where: { id: context.user.organizationId },
select: { referralCode: true }
})
console.log('🏢 myPartnerLink DEBUG - Found organization:', organization)
if (!organization?.referralCode) {
console.log('❌ myPartnerLink DEBUG - NO referralCode!')
throw new GraphQLError('Реферальный код не найден')
}
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?partner=${organization.referralCode}`
console.log('✅ myPartnerLink DEBUG - Generated link:', link)
return link
},
// Получить статистику по рефералам
myReferralStats: async (_: unknown, __: unknown, context: Context) => {
console.log('🔥🔥🔥 NEW myReferralStats RESOLVER CALLED!')
console.log('🔗 myReferralStats DEBUG - context.user:', context.user)
try {
// Если пользователь не авторизован, возвращаем дефолтные значения
if (!context.user?.organizationId) {
console.log('❌ myReferralStats DEBUG - NO USER OR organizationId!')
const defaultResult = {
totalPartners: 0,
totalSpheres: 0,
monthlyPartners: 0,
monthlySpheres: 0,
referralsByType: [
{ type: 'SELLER', count: 0, spheres: 0 },
{ type: 'WHOLESALE', count: 0, spheres: 0 },
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
{ type: 'LOGIST', count: 0, spheres: 0 }
],
referralsBySource: [
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
]
}
console.log('✅ myReferralStats DEBUG - returning default result for unauth user:', defaultResult)
return defaultResult
}
// TODO: Реальная логика подсчета статистики
const result = {
totalPartners: 0,
totalSpheres: 0,
monthlyPartners: 0,
monthlySpheres: 0,
referralsByType: [
{ type: 'SELLER', count: 0, spheres: 0 },
{ type: 'WHOLESALE', count: 0, spheres: 0 },
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
{ type: 'LOGIST', count: 0, spheres: 0 }
],
referralsBySource: [
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
]
}
console.log('✅ myReferralStats DEBUG - returning result:', result)
return result
} catch (error) {
console.error('❌ myReferralStats ERROR:', error)
// В случае ошибки всегда возвращаем валидную структуру
const fallbackResult = {
totalPartners: 0,
totalSpheres: 0,
monthlyPartners: 0,
monthlySpheres: 0,
referralsByType: [
{ type: 'SELLER', count: 0, spheres: 0 },
{ type: 'WHOLESALE', count: 0, spheres: 0 },
{ type: 'FULFILLMENT', count: 0, spheres: 0 },
{ type: 'LOGIST', count: 0, spheres: 0 }
],
referralsBySource: [
{ source: 'REFERRAL_LINK', count: 0, spheres: 0 },
{ source: 'AUTO_BUSINESS', count: 0, spheres: 0 }
]
}
console.log('✅ myReferralStats DEBUG - returning fallback result after error:', fallbackResult)
return fallbackResult
}
},
// Получить список рефералов
myReferrals: async (_: unknown, args: any, context: Context) => {
if (!context.user?.organizationId) {
throw new GraphQLError('Требуется авторизация и организация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const referrals = await prisma.organization.findMany({
where: { referredById: context.user.organizationId },
include: {
referralTransactions: {
where: { referrerId: context.user.organizationId }
}
},
take: args.limit || 50,
skip: args.offset || 0
})
const totalCount = await prisma.organization.count({
where: { referredById: context.user.organizationId }
})
return {
referrals: referrals.map(org => ({
id: org.id,
organization: org,
source: org.referralTransactions[0]?.type === 'AUTO_PARTNERSHIP' ? 'AUTO_BUSINESS' : 'REFERRAL_LINK',
spheresEarned: org.referralTransactions.reduce((sum, t) => sum + t.points, 0),
registeredAt: org.createdAt,
status: 'ACTIVE'
})),
totalCount,
totalPages: Math.ceil(totalCount / (args.limit || 50))
}
}
}
}

View File

@ -119,6 +119,24 @@ export const typeDefs = gql`
# Типы для кеша склада WB # Типы для кеша склада WB
getWBWarehouseData: WBWarehouseCacheResponse! 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 { type Mutation {
@ -306,6 +324,12 @@ export const typeDefs = gql`
isCurrentUser: Boolean isCurrentUser: Boolean
hasOutgoingRequest: Boolean hasOutgoingRequest: Boolean
hasIncomingRequest: Boolean hasIncomingRequest: Boolean
# Реферальная система
referralCode: String
referredBy: Organization
referrals: [Organization!]!
referralPoints: Int!
isMyReferral: Boolean!
createdAt: DateTime! createdAt: DateTime!
updatedAt: DateTime! updatedAt: DateTime!
} }
@ -346,6 +370,8 @@ export const typeDefs = gql`
phone: String! phone: String!
inn: String! inn: String!
type: OrganizationType! type: OrganizationType!
referralCode: String
partnerCode: String
} }
input SellerRegistrationInput { input SellerRegistrationInput {
@ -353,6 +379,8 @@ export const typeDefs = gql`
wbApiKey: String wbApiKey: String
ozonApiKey: String ozonApiKey: String
ozonClientId: String ozonClientId: String
referralCode: String
partnerCode: String
} }
input MarketplaceApiKeyInput { input MarketplaceApiKeyInput {
@ -1430,4 +1458,81 @@ export const typeDefs = gql`
extend type Query { extend type Query {
fulfillmentWarehouseStats: FulfillmentWarehouseStats! 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
}
` `

View File

@ -73,6 +73,8 @@ interface UseAuthReturn {
phone: string, phone: string,
inn: string, inn: string,
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE', type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
referralCode?: string | null,
partnerCode?: string | null,
) => Promise<{ ) => Promise<{
success: boolean success: boolean
message: string message: string
@ -83,6 +85,8 @@ interface UseAuthReturn {
wbApiKey?: string wbApiKey?: string
ozonApiKey?: string ozonApiKey?: string
ozonClientId?: string ozonClientId?: string
referralCode?: string | null
partnerCode?: string | null
}) => Promise<{ }) => Promise<{
success: boolean success: boolean
message: string message: string
@ -290,7 +294,13 @@ export const useAuth = (): UseAuthReturn => {
phone: string, phone: string,
inn: string, inn: string,
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE', type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
referralCode?: string | null,
partnerCode?: string | null,
) => { ) => {
console.log('🎬 useAuth - registerFulfillmentOrganization вызван с параметрами:', {
phone, inn, type, referralCode, partnerCode
})
try { try {
setIsLoading(true) setIsLoading(true)
@ -301,11 +311,17 @@ export const useAuth = (): UseAuthReturn => {
currentToken ? `${currentToken.substring(0, 20)}...` : 'No token', currentToken ? `${currentToken.substring(0, 20)}...` : 'No token',
) )
console.log('🎬 useAuth - Отправка GraphQL мутации с input:', {
phone, inn, type, referralCode, partnerCode
})
const { data } = await registerFulfillmentMutation({ const { data } = await registerFulfillmentMutation({
variables: { variables: {
input: { phone, inn, type }, input: { phone, inn, type, referralCode, partnerCode },
}, },
}) })
console.log('🎬 useAuth - Ответ GraphQL мутации:', data)
const result = data.registerFulfillmentOrganization const result = data.registerFulfillmentOrganization
@ -340,6 +356,8 @@ export const useAuth = (): UseAuthReturn => {
wbApiKey?: string wbApiKey?: string
ozonApiKey?: string ozonApiKey?: string
ozonClientId?: string ozonClientId?: string
referralCode?: string | null
partnerCode?: string | null
}) => { }) => {
try { try {
setIsLoading(true) setIsLoading(true)

1806
workflow-catalog.md Normal file
View File

@ -0,0 +1,1806 @@
# КАТАЛОГ ВСЕХ БИЗНЕС-ПРОЦЕССОВ СИСТЕМЫ
> ⚠️ **НАЗНАЧЕНИЕ**: Полный каталог всех бизнес-процессов и workflow системы управления складами.
> **Источник**: Все процессы из [rules-complete.md](./rules-complete.md) собраны в одном месте для удобного поиска.
---
## 📋 **ОГЛАВЛЕНИЕ**
### Основные процессы:
- [1. Workflow поставок](#1-workflow-поставок) - 8 статусов, 6 этапов
- [2. Процесс создания продукта](#2-процесс-создания-продукта) - 5 шагов с SLA
- [3. UI процессы селлера](#3-ui-процессы-селлера) - 4-блочная система
- [4. Workflow фулфилмента](#4-workflow-фулфилмента) - 3 этапа обработки
- [5. Workflow логистики](#5-workflow-логистики) - 4 этапа доставки
- [6. Система партнерства](#6-система-партнерства) - 2 способа, 4 статуса
- [7. Интеграция с услугами](#7-интеграция-с-услугами) - 5-шаговый workflow
- [8. Критические ситуации](#8-критические-ситуации) - отмены и чрезвычайные
### Вспомогательные процессы:
- [9. Протоколы разработки](#9-протоколы-разработки) - последовательность работы
- [10. Система учета движения товаров](#10-система-учета-движения-товаров)
### Индекс по ролям:
- **Селлер**: процессы 1, 2, 3, 6
- **Поставщик**: процессы 1, 6
- **Фулфилмент**: процессы 1, 2, 4, 7
- **Логистика**: процессы 1, 5
---
## 1. 🚚 **WORKFLOW ПОСТАВОК**
> **Источник**: rules-complete.md, раздел 5, строки 519-563
### 1.1 Детализированная система статусов
**Статусы SupplyOrder (Заказ поставки):**
1. **PENDING** - Ожидает подтверждения поставщиком
2. **SUPPLIER_APPROVED** - Одобрено поставщиком
3. **CONFIRMED** - Подтвержден (готов к обработке)
4. **LOGISTICS_CONFIRMED** - Подтверждено логистикой
5. **SHIPPED** - Отгружено поставщиком
6. **IN_TRANSIT** - В пути (логистика доставляет)
7. **DELIVERED** - Доставлен на фулфилмент
8. **CANCELLED** - Отменен
### 1.2 Пошаговый процесс поставки
**ЭТАП 1: Создание заказа**
1. Селлер заказывает товар/расходники у поставщика
2. Система создает SupplyOrder со статусом `PENDING`
3. Автоматическое уведомление поставщику
**ЭТАП 2: Обработка поставщиком**
4. Поставщик получает оповещение
5. Поставщик нажимает "Одобрить"
6. Статус меняется на `SUPPLIER_APPROVED`
**ЭТАП 3: Передача в фулфилмент**
7. Поставка отображается в кабинете фулфилмента
8. Фулфилмент выбирает ответственного и логистику
9. Статус меняется на `CONFIRMED`
**ЭТАП 4: Логистическое подтверждение**
10. Логистика подтверждает доставку
11. Статус меняется на `LOGISTICS_CONFIRMED`
**ЭТАП 5: Отгрузка**
12. Поставщик отгружает товар
13. Статус меняется на `SHIPPED`, затем `IN_TRANSIT`
**ЭТАП 6: Доставка и приемка**
14. Логистика доставляет на фулфилмент
15. Фулфилмент принимает товар
16. Статус меняется на `DELIVERED`
### 1.3 Система уведомлений
**Обязательные уведомления:**
- Поставщику: о новом заказе
- Фулфилменту: о подтвержденной поставке
- Логистике: о назначении на заявку
- Селлеру: об изменении каждого статуса
---
## 2. 🔄 **ПРОЦЕСС СОЗДАНИЯ ПРОДУКТА**
> **Источник**: rules-complete.md, раздел 6, строки 565-740
### 2.1 5-шаговый алгоритм создания
**ПРЕДВАРИТЕЛЬНОЕ УСЛОВИЕ**: Рецептура задана селлером
**ШАГ 1: Поступление на склад (автоматически)**
- Товар поступает на склад фулфилмента
- Система фиксирует поступление
- Товар получает статус "доступен для обработки"
**ШАГ 2: Планирование работы (менеджер фулфилмента)**
- Менеджер фулфилмента видит товар в интерфейсе
- Планирует обработку согласно рецептуре
- Назначает исполнителя
**ШАГ 3: Обработка товара (исполнитель)**
- Исполнитель берет товар в работу
- Применяет услуги согласно рецептуре
- Использует расходники селлера и фулфилмента
- Товар превращается в продукт
**ШАГ 4: Контроль качества (менеджер/отдел качества)**
- Проверка соответствия рецептуре
- Контроль качества обработки
- Подтверждение или возврат на доработку
**ШАГ 5: Завершение (система + менеджер)**
- Система создает запись о готовом продукте
- Продукт получает статус FINISHED_PRODUCT
- Готов к отправке селлеру
### 2.2 Временные рамки и SLA
| Этап | Время выполнения | Ответственный | KPI |
|------|------------------|---------------|-----|
| Поступление | Мгновенно | Система | 100% автоматизация |
| Планирование | До 2 часов | Менеджер ФФ | 95% в срок |
| Обработка | 1-3 дня | Исполнитель | Согласно сложности |
| Контроль | До 4 часов | ОТК | 99% точность |
### 2.3 Детальная рецептура продукта
**РЕЦЕПТУРА ПРОДУКТА** (задается селлером при создании поставки):
- **БАЗОВЫЙ ТОВАР**: Исходный материал (обязательно)
- Артикул товара
- Количество единиц
- Размерная сетка (если применимо)
- **УСЛУГА ФУЛФИЛМЕНТА**: Из каталога услуг фулфилмента
- Тип услуги (глажка, упаковка, маркировка и т.д.)
- Количество применений
- Специальные требования
- **РАСХОДНИК СЕЛЛЕРА**: Материалы селлера (опционально)
- Фирменная упаковка
- Этикетки, бирки
- Дополнительные аксессуары
- **РАСХОДНИК ФУЛФИЛМЕНТА**: Материалы фулфилмента (опционально)
- Стандартная упаковка
- Защитные материалы
- Маркировочные элементы
- **СВЯЗЬ С МАРКЕТПЛЕЙСОМ**: Привязка к карточке (опционально)
- ID карточки на маркетплейсе
- Артикул маркетплейса
- Особые требования МП
**ФОРМУЛА**: ПРОДУКТ = Товар + Услуга(и) + Расходники селлера + Расходники ФФ
### 2.4 Пошаговый алгоритм создания продукта
#### **ПРЕДВАРИТЕЛЬНОЕ УСЛОВИЕ: РЕЦЕПТУРА ЗАДАНА** (селлер)
```
Время: при создании заявки на поставку
Действие: селлер указывает рецептуру продукта
Обязательные компоненты:
✓ Базовый товар (от поставщика)
✓ Услуги фулфилмента (упаковка, маркировка и т.д.)
✓ Расходники (материалы для производства)
Результат: рецептура сохраняется в заявке и передается фулфилменту
```
#### **ШАГ 1: ПОСТУПЛЕНИЕ НА СКЛАД** (автоматически)
```
Время: при смене статуса поставки DELIVERED
Действие: товар переходит в статус "на складе"
Ответственный: система
Результат: +Прибыло в статистике товаров
```
#### **ШАГ 2: ПЛАНИРОВАНИЕ РАБОТЫ** (менеджер фулфилмента)
```
Время: в течение 2 рабочих дней после поступления
Действие: назначение параметров обработки
Ответственный: менеджер фулфилмента
Обязательные поля:
✓ Дедлайн выполнения (не более 5 рабочих дней)
✓ Ответственный исполнитель (из списка сотрудников)
✓ Рецептура (товар + услуги + расходники, указанная селлером в заявке на поставку)
Опциональные поля:
- Место хранения готовых продуктов (зона склада, стеллаж, ячейка)
- Комментарии к работе
Результат: поставка переходит во вкладку "В работе"
```
#### **ШАГ 3: ОБРАБОТКА ТОВАРА** (исполнитель)
```
Время: согласно дедлайну (обычно 1-3 дня)
Действие: физическая обработка товара
Ответственный: назначенный сотрудник
Обязательные действия:
1. Проверка качества товара
2. Фиксация фактического количества
3. Выявление и учет брака
4. Применение рецептуры (услуги + расходники)
5. Создание готового продукта
Точки контроля:
- Соответствие плану/факту
- Качество выполнения услуг
- Расход материалов по норме
УЧЕТ ПЛАН/ФАКТ:
- ПЛАН: Количество товаров из поставки селлера (указано в заказе)
- ФАКТ: Реальное количество после обработки = Брак + Хороший товар
- ДЕТАЛИЗАЦИЯ: Учет ведется по каждому размеру/объему/варианту
- КОРРЕКТИРОВКА: Статистика автоматически обновляется на фактические данные
```
#### **ШАГ 4: КОНТРОЛЬ КАЧЕСТВА** (менеджер/отдел качества)
```
Время: сразу после завершения ШАГ 3
Действие: приемка готовой продукции
Ответственный: менеджер или контролер качества
Критерии приемки:
✓ Соответствие рецептуре селлера
✓ Качество выполненных услуг
✓ Правильность упаковки/маркировки
✓ Полнота комплектации
Результат: продукт готов к отправке или отправлен на доработку
```
#### **ШАГ 5: ЗАВЕРШЕНИЕ** (система + менеджер)
```
Время: после успешного прохождения контроля качества
Действие: финализация процесса
Автоматические действия:
- Создание записи FINISHED_PRODUCT в БД
- Обновление статистики: товар "на складе" → продукт "готов"
- Списание использованных расходников
- Уведомление селлера о готовности
Ручные действия менеджера:
- Подтверждение перехода во вкладку "Выполнено"
- Указание фактических расходов материалов
- Добавление комментариев о выполненной работе
```
### 2.5 Временные рамки и SLA
| Этап | Стандартное время | Максимальное время | Ответственный |
| ----------------- | ----------------- | ------------------ | -------------- |
| Планирование | 1 рабочий день | 2 рабочих дня | Менеджер ФФ |
| Обработка | 2-3 рабочих дня | 5 рабочих дней | Исполнитель |
| Контроль качества | 4 часа | 1 рабочий день | Отдел качества |
| Завершение | 2 часа | 4 часа | Менеджер ФФ |
### 2.6 Детальная рецептура продукта
**РЕЦЕПТУРА ПРОДУКТА** (задается селлером при создании поставки):
- **БАЗОВЫЙ ТОВАР**: Исходный материал (обязательно)
- Артикул товара
- Количество единиц
- Размерная сетка (если применимо)
- **УСЛУГА ФУЛФИЛМЕНТА**: Из каталога услуг фулфилмента
- Тип услуги (глажка, упаковка, маркировка и т.д.)
- Количество применений
- Специальные требования
- **РАСХОДНИК СЕЛЛЕРА**: Материалы селлера (опционально)
- Фирменная упаковка
- Этикетки, бирки
- Дополнительные аксессуары
- **РАСХОДНИК ФУЛФИЛМЕНТА**: Материалы фулфилмента (опционально)
- Стандартная упаковка
- Защитные материалы
- Маркировочные элементы
- **СВЯЗЬ С МАРКЕТПЛЕЙСОМ**: Привязка к карточке (опционально)
- ID карточки на маркетплейсе
- Артикул маркетплейса
- Особые требования МП
**ФОРМУЛА**: ПРОДУКТ = Товар + Услуга(и) + Расходники селлера + Расходники ФФ
### 2.7 Учет план/факт в процессе работы
**ПЛАН**: Количество товара из поставки селлера
**ФАКТ**: Реальное количество после пересчета (работник фулфилмента производит сортировку при пересчете)
**ФИКСАЦИЯ ПОТЕРЬ:**
- **КОГДА**: В процессе работы (вкладка "В работе")
- **ЧТО**: Недостача, повреждения (без создания записей брака)
- **КАК**: Корректировка количества в статистике
**WORKFLOW СОЗДАНИЯ ПРОДУКТА:**
1. Товар поступает на склад фулфилмента (статус "на складе")
2. Товар берется в работу (переход в статус "в обработке")
3. Исполнитель производит пересчет и сортировку
4. Создается готовый продукт (тип FINISHED_PRODUCT)
5. Продукт готов к отправке на маркетплейсы
**ВЛИЯНИЕ НА СТАТИСТИКУ:**
- При принятии поставки: +План в статистику
- При выявлении факта: корректировка на реальные данные
- **ФОРМУЛА**: Факт = Потери + Хороший товар
_Где потери - это недостача/повреждения, выявленные при пересчете и сортировке_
- **ЛОГИКА**: Фактическое количество = сумма всех пересчитанных предметов
- **ПЛАН/ФАКТ**: Корректировка статистики при выявлении расхождений
---
## 3. 🎨 **UI ПРОЦЕССЫ СЕЛЛЕРА**
> **Источник**: rules-complete.md, раздел 9, строки 871-1885 (1015 строк)
### 3.1 Структура раздела "Мои поставки"
#### **🏢 ПОСТАВКИ НА ФУЛФИЛМЕНТ**:
- **Товар** - поставка товаров для создания продуктов
- **Карточки** - поставка через WB API с рецептурой (результат: WildberriesSupply)
- **Поставщики** - заказ товаров у поставщиков с рецептурой (результат: SupplyOrder)
- **Расходники селлера** - поставка материалов для товаров селлера
#### **🛒 ПОСТАВКИ НА МАРКЕТПЛЕЙСЫ** _(планируется)_:
- **Wildberries** - прямые поставки на WB
- **Ozon** - прямые поставки на Ozon
### 3.2 UI структура создания поставки расходников селлера
#### **📄 Структура страницы создания поставки:**
**ОБНОВЛЕННАЯ СТРУКТУРА СИСТЕМЫ (4 БЛОКА):**
**БЛОК 1: ПОСТАВЩИКИ** _(адаптивная сетка)_
- **Заголовок**: Минималистичный "🏢 Поставщики" без лишних элементов
- **Поиск**: Компактное поле справа "Поиск поставщиков..." (w-64)
- **Отображение**: Карточки поставщиков из раздела "Партнеры" в адаптивной сетке
- **Выбор**: Клик выделяет карточку поставщика
- **Результат**: Загружаются карточки товаров выбранного поставщика в блок 2
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(горизонтальный скролл - НОВЫЙ)_
- **Отображение**: ТОЛЬКО минималистичные карточки товаров 80×112px
- **Содержание**: ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
- **Навигация**: Горизонтальный скролл при множестве товаров
- **Выбор**: Клик добавляет товар в детальный каталог
- **Результат**: Товар добавляется в блок 3 для управления поставкой
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(детальный каталог)_
- **Отображение**: Детальные карточки выбранных товаров
- **Управление**: Количество, параметры, настройки поставки
- **Результат**: Формирование окончательной поставки
**БЛОК 4: КОРЗИНА И НАСТРОЙКИ** _(правая панель)_
- **Отображение**: Корзина поставки + настройки
- **Управление**: Фулфилмент-центр, дата, логистика
#### **3.2.1 Детальные правила горизонтального скролла поставщиков**
**СТРУКТУРА И ОТОБРАЖЕНИЕ:**
- **Источник данных**: Партнеры типа `WHOLESALE` из раздела "Партнеры"
- **Контейнер**: Фиксированная высота 176px (h-44) с горизонтальным скроллом
- **Блок поставщиков**: Общая высота 180px, включает заголовок + контейнер скролла
- **Направление**: Слева направо (LTR)
- **Поведение**: Плавный скролл с автоскрытием полосы прокрутки
**РАЗМЕРЫ И АДАПТИВНОСТЬ:**
- **Десктоп**: Карточка 216×92px, отступы 12px между карточками, 16px от краев
- **Планшет**: Карточка 200×92px, отступы 12px между карточками
- **Мобильный**: Карточка 184×92px, отступы 12px между карточками
- **Высота блока**: 180px фиксированная для всего блока поставщиков
**ВЗАИМОДЕЙСТВИЕ:**
- **Навигация**: Колесо мыши (Shift+скролл), стрелки клавиатуры, свайп на тач
- **Выбор**: Клик по карточке → активная рамка + загрузка товаров в блок 2
- **Состояния**: Default, Hover (box-shadow), Active (цветная рамка), Loading (скелетон)
**ГРАНИЧНЫЕ СЛУЧАИ:**
- **1-4 карточки**: Выравнивание по левому краю, скролл неактивен
- **5+ карточек**: Полный горизонтальный скролл
- **Нет партнеров**: Заглушка с ссылкой на раздел "Партнеры"
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
**Критическая Flex-архитектура:**
```css
.parent-container {
display: flex;
gap: 16px;
min-height: 0;
}
.left-block {
flex: 1;
min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для overflow */
display: flex;
flex-direction: column;
}
.suppliers-container {
height: 180px; /* Общая высота блока */
flex-shrink: 0;
min-width: 0; /* Предотвращает растяжение */
}
.right-block {
width: 384px; /* w-96 */
flex-shrink: 0; /* Защита от сжатия */
}
```
**Контейнер скролла:**
```css
.suppliers-block {
display: flex;
overflow-x: auto;
scroll-behavior: smooth;
gap: 12px;
padding: 0 16px 8px 16px; /* px-4 pb-2 */
height: 176px; /* h-44 */
scrollbar-width: thin;
scrollbar-color: #64748b33 transparent;
}
.suppliers-block:hover {
scrollbar-color: #cbd5e0 #64748b22;
}
.supplier-card {
flex-shrink: 0;
width: 216px; /* Десктоп */
height: 92px; /* Фиксированная высота */
padding: 8px; /* p-2 */
transition: all 0.2s ease;
}
```
**СОДЕРЖАНИЕ КАРТОЧКИ ПОСТАВЩИКА:**
**Структура (3 строки в 92px высоты):**
- **Строка 1**: Название + рейтинг (справа, если есть)
- **Строка 2**: ИНН (формат "ИНН: 1234567890")
- **Строка 3**: Бейдж рынка (отдельная строка)
**Элементы:**
- **Аватар**: Размер xs, слева с gap-2
- **Текст**: text-xs для компактности
- **Отступы**: mb-1 между строками 1-2, mb-0.5 между строками 2-3
- **Padding карточки**: 8px (p-2)
**ЦВЕТОВАЯ СХЕМА РЫНКОВ:**
- **"Садовод"** (sadovod): Зеленый `bg-green-500/20 text-green-300 border-green-500/30`
- **"ТЯК Москва"** (tyak-moscow): Синий `bg-blue-500/20 text-blue-300 border-blue-500/30`
- **Другие/не указан**: Серый `bg-gray-500/20 text-gray-300 border-gray-500/30`
**ДОСТУПНОСТЬ:**
- `role="tablist"` для контейнера
- `role="tab"` для карточек
- `aria-selected="true/false"` для выбранной карточки
- `tabindex="0"` для активной, `-1` для неактивных
#### **3.2.2 Правила блока "Карточки товаров" (Блок 2)**
**НАЗНАЧЕНИЕ И ЛОГИКА:**
- **Источник данных**: Товары выбранного поставщика из Блока 1
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
- **Поведение**: Горизонтальный скролл при множестве товаров
**АРХИТЕКТУРА И РАЗМЕРЫ:**
- **Внешний контейнер**: bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0
- **Внутренний контейнер скролла**: flex gap-3 overflow-x-auto p-4
- **Стилизация скролла**: scrollbarWidth: 'thin' для тонкой полосы прокрутки
- **Отступы**: padding: 16px (p-4) внутри, gap: 12px (gap-3) между карточками
- **Адаптивная высота**: по содержимому карточек (БЕЗ фиксированной высоты)
- **Визуальное единство**: стеклянный эффект как у других блоков системы
- **БЕЗ заголовков/иконок**: только чистые карточки товаров в контейнере
**РАЗМЕРЫ КАРТОЧЕК ТОВАРОВ:**
- **Компактная карточка**: 80×112px (w-20 h-28), соотношение 5:7
- **Адаптивность**: фиксированный размер для всех устройств
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
- **Рамка**: border-white/10, при выборе border-white/30
- **Фон**: bg-white/5 полупрозрачный
**ДЕЙСТВИЕ:**
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
#### **3.2.3 Правила Блока 3 "Детальный каталог товаров"**
**НАЗНАЧЕНИЕ И СТРУКТУРА:**
- **Контент**: Детальные карточки выбранных товаров с полным управлением
- **Верхняя панель**: Выбор даты + Выбор Fulfillment + Поиск
- **Основная область**: Сетка карточек товаров с детальной информацией
#### **3.2.3.1 Структура верхней панели Блока 3**
**МИНИМАЛИСТИЧНАЯ ПАНЕЛЬ УПРАВЛЕНИЯ:**
- **Выбор даты поставки**: DatePicker для планирования поставки
- **Выбор Fulfillment-центра**: Select dropdown со списком доступных фулфилментов
- **Поиск по товарам**: Input с иконкой поиска и placeholder
- **Компоновка**: Горизонтальная строка с равномерным распределением
**ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ:**
```tsx
// Структура компонентов панели
<div className="flex items-center gap-4 p-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl mb-4">
<DatePicker placeholder="Дата поставки" />
<Select placeholder="Выберите фулфилмент">
<SelectContent>
{fulfillmentCenters.map((center) => (
<SelectItem value={center.id}>{center.name}</SelectItem>
))}
</SelectContent>
</Select>
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
<Input placeholder="Поиск товаров..." className="pl-10 glass-input" />
</div>
</div>
```
#### **3.2.3.2 Структура основной области карточек**
**СЕТКА ТОВАРОВ:**
- **Адаптивная сетка**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`
- **Детальные карточки**: Полная информация + количество + управление
- **Состояния**: Default, Selected, Editing
- **Интерактивность**: Изменение количества, удаление, настройки рецептуры
**ФУНКЦИОНАЛЬНОСТЬ ПАНЕЛИ:**
- **Выбор даты**: Планирование времени поставки (обязательное поле)
- **Выбор фулфилмента**: Определение исполнителя поставки (обязательное поле)
- **Поиск**: Фильтрация товаров в каталоге по названию/артикулу
- **Валидация**: Блокировка создания поставки без заполнения даты и фулфилмента
**ГРАНИЧНЫЕ СЛУЧАИ:**
- **Пустой каталог**: Заглушка "Добавьте товары"
- **Нет фулфилментов**: Сообщение "Настройте партнерство с фулфилмент-центрами"
- **Поиск без результатов**: "По запросу ничего не найдено"
#### **3.2.2.1 Структура контейнера Блока 2**
**ДВУХУРОВНЕВАЯ АРХИТЕКТУРА:**
**УРОВЕНЬ 1 - Внешний контейнер (блок):**
```jsx
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0">
```
- **Назначение**: Визуальное обрамление блока, единство с другими блоками
- **Стилизация**: Стеклянный эффект с размытием и полупрозрачностью
- **Рамка**: Тонкая белая рамка border-white/20 с закруглёнными углами
- **Поведение**: flex-shrink-0 предотвращает сжатие блока
**УРОВЕНЬ 2 - Внутренний контейнер (скролл):**
```jsx
<div className="flex gap-3 overflow-x-auto p-4" style={{ scrollbarWidth: 'thin' }}>
```
- **Назначение**: Горизонтальная прокрутка карточек товаров
- **Раскладка**: Flex с промежутками gap-3 (12px) между карточками
- **Отступы**: padding p-4 (16px) со всех сторон
- **Скролл**: overflow-x-auto с тонкой полосой прокрутки
- **Поведение**: Автоматическое появление скролла при превышении ширины
**ПРАВИЛА КОНТЕЙНЕРОВ:**
- Внешний контейнер НЕ содержит заголовков, иконок, описаний
- Внутренний контейнер содержит ТОЛЬКО карточки товаров
- Высота адаптируется под размер карточек (80×112px + отступы)
- Визуальное единство со всеми блоками формы поставки
**ТЕХНИЧЕСКИЕ ПРАВИЛА:**
- **Условие отображения**: selectedSupplier && products.length > 0
- **Источник данных**: products массив из GraphQL запроса organizationProducts
- **Реактивность**: Автоматическое обновление при смене поставщика
- **Производительность**: React.memo для карточек при большом количестве товаров
- **Доступность**: Клавиатурная навигация (Tab, Enter для выбора)
**UX ПРАВИЛА ВЗАИМОДЕЙСТВИЯ:**
- **Скролл**: Автоматическое появление при превышении ширины контейнера
- **Индикация загрузки**: Скелетоны карточек во время загрузки товаров
- **Пустое состояние**: Скрытие блока при отсутствии поставщика или товаров
- **Фокус**: Первая карточка получает фокус при загрузке товаров
- **Навигация**: Стрелки ←→ для перемещения между карточками
**СОСТОЯНИЯ БЛОКА:**
- **Скрыт**: При отсутствии выбранного поставщика
- **Скрыт**: При отсутствии товаров у поставщика
- **Активен**: При наличии поставщика и товаров
- **Загрузка**: Показ скелетонов карточек во время запроса
**ПРАВИЛА ПРОИЗВОДИТЕЛЬНОСТИ:**
- **Виртуализация**: При количестве товаров > 100
- **Ленивая загрузка изображений**: loading="lazy" для всех изображений
- **Мемоизация**: React.memo для компонентов карточек
- **Дебаунс**: 300мс для поисковых запросов (если будет добавлен поиск)
**ПРАВИЛА АДАПТИВНОСТИ:**
- **Мобильные устройства**: Свайп для горизонтальной прокрутки
- **Планшеты**: Сохранение размеров карточек 80×112px
- **Десктоп**: Полная функциональность с клавиатурной навигацией
- **Высокие разрешения**: Сохранение пропорций и читаемости
**ПРАВИЛА БЕЗОПАСНОСТИ И ВАЛИДАЦИИ:**
- **Валидация данных**: Проверка существования product.id перед добавлением
- **Дубликаты**: Предотвращение добавления одного товара дважды в детальный каталог
- **Санитизация**: Безопасное отображение названий товаров (XSS защита)
- **Обработка ошибок**: Graceful degradation при ошибках загрузки изображений
- **Защита от спама**: Дебаунс кликов 200мс для предотвращения множественных добавлений
**ПРАВИЛА ИНТЕГРАЦИИ С ДРУГИМИ БЛОКАМИ:**
- **Блок 1 (Поставщики)**: Слушает изменения selectedSupplier для обновления товаров
- **Блок 3 (Детальный каталог)**: Передаёт выбранные товары через setAllSelectedProducts
- **Блок 4 (Корзина)**: Товары добавляются в корзину из Блока 3, не напрямую из Блока 2
- **Синхронизация состояний**: Реактивное обновление при изменении данных в любом блоке
**ПРАВИЛА АНАЛИТИКИ И МЕТРИК:**
- **Отслеживание кликов**: Логирование добавления товаров в детальный каталог
- **Метрики производительности**: Время загрузки товаров поставщика
- **Пользовательское поведение**: Количество просмотренных товаров на поставщика
- **A/B тестирование**: Готовность к тестированию различных размеров карточек
**ПРАВИЛА ЛОКАЛИЗАЦИИ:**
- **Alt-текст изображений**: На языке интерфейса пользователя
- **Направление скролла**: RTL поддержка для арабского/иврита
- **Размеры карточек**: Неизменны для всех локалей (80×112px)
- **Сообщения об ошибках**: Локализованные уведомления при проблемах загрузки
#### **3.2.1.1 Заголовок и поиск Блока 1**
**МИНИМАЛИСТИЧНЫЙ ДИЗАЙН:**
```jsx
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2">
<Building2 className="h-5 w-5 text-blue-400" />
<h2 className="text-lg font-semibold text-white">Поставщики</h2>
</div>
<div className="w-64">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-4 w-4" />
<Input
placeholder="Поиск поставщиков..."
className="bg-white/5 border-white/10 text-white placeholder:text-white/50 pl-10 h-9"
/>
</div>
</div>
</div>
```
**ПРАВИЛА ЗАГОЛОВКА:**
- **Иконка**: Building2 h-5 w-5 text-blue-400 (без фонового контейнера)
- **Текст**: "Поставщики" (убран избыточный "товаров")
- **Размер**: text-lg font-semibold (увеличен для лучшей читаемости)
- **БЕЗ бэджа**: Убран избыточный бэдж "Создание поставки"
- **Выравнивание**: flex items-center gap-2 (компактное)
**ПРАВИЛА ПОИСКА:**
- **Позиция**: Справа от заголовка (justify-between)
- **Ширина**: w-64 (256px) фиксированная ширина
- **Плейсхолдер**: "Поиск поставщиков..." (конкретное описание)
- **Иконка**: Search h-4 w-4 слева в поле
- **Стили**: Стандартные glass-эффекты, focus:border-white/20
**ПРАВИЛА КНОПКИ "НАЙТИ В МАРКЕТЕ":**
- **Условие**: Показывается только при allCounterparties.length === 0
- **Позиция**: Отдельный блок под заголовком (mt-4)
- **НЕ интегрирована**: В поле поиска (отдельно)
- **Стили**: glass-secondary outline button размера sm
#### **3.2.1.2 Структура карточки поставщика в Блоке 1**
**МИНИМАЛИСТИЧНАЯ КАРТОЧКА ПОСТАВЩИКА:**
**СТРУКТУРА ИНФОРМАЦИИ:**
```jsx
<div className="flex items-start gap-2">
<OrganizationAvatar organization={supplier} size="sm" />
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm truncate">{supplier.name || supplier.fullName}</h4>
<div className="flex items-center gap-2 mt-1">
<p className="text-white/60 text-xs font-mono">ИНН: {supplier.inn}</p>
{supplier.market && <Badge className="market-badge">{getMarketLabel(supplier.market)}</Badge>}
</div>
</div>
</div>
```
**ПРАВИЛА СОДЕРЖАНИЯ КАРТОЧКИ:**
**✅ ОСТАВИТЬ:**
- **Аватар организации**: OrganizationAvatar size="sm" слева
- **Название поставщика**: supplier.name || supplier.fullName (приоритет name)
- **ИНН**: font-mono, text-white/60, с префиксом "ИНН: "
**🔸 ДОБАВИТЬ:**
- **Принадлежность к рынку**: Badge с названием рынка из supplier.market
- **Рынки**: "Садовод", "ТЯК Москва" и другие из Organization.market поля
**❌ УБРАТЬ:**
- **Рейтинг**: Звездочка и цифра rating (избыточно)
- **Тип бэдж**: "Поставщик" badge (и так понятно из контекста)
- **Адрес**: supplier.address (занимает место, не критично)
**СТИЛИ РЫНОЧНЫХ БЭДЖЕЙ:**
- **Садовод**: bg-green-500/20 text-green-300 border-green-500/30
- **ТЯК Москва**: bg-blue-500/20 text-blue-300 border-blue-500/30
- **По умолчанию**: bg-gray-500/20 text-gray-300 border-gray-500/30
**ПРАВИЛА АДАПТИВНОСТИ:**
- **Мобильные**: Сохранение структуры, truncate для длинных названий
- **Планшеты/десктоп**: Полное отображение в сетке
- **Малые экраны**: line-clamp-1 для названия организации
**СОСТОЯНИЯ КАРТОЧКИ:**
- **Default**: bg-white/5 border-white/10
- **Hover**: hover:border-white/20 hover:bg-white/10
- **Selected**: bg-white/15 border-white/40 shadow-lg
- **Disabled**: opacity-50 cursor-not-allowed (при недоступности)
**ПРАВИЛА ИНТЕГРАЦИИ С РЫНКАМИ:**
**ИСТОЧНИК ДАННЫХ:**
- **Поле БД**: Organization.market (String?) - поле принадлежности к рынку
- **Настройка**: Указывается в настройках кабинета поставщика
- **Опциональность**: Поле может быть пустым (рынок не указан)
**ФУНКЦИЯ getMarketLabel():**
```jsx
const getMarketLabel = (market?: string) => {
const marketLabels = {
'sadovod': 'Садовод',
'tyak-moscow': 'ТЯК Москва',
'opt-market': 'ОПТ Маркет',
}
return marketLabels[market as keyof typeof marketLabels] || market
}
```
**СТИЛИ ДЛЯ РЫНКОВ:**
```jsx
const getMarketBadgeStyle = (market?: string) => {
const styles = {
'sadovod': 'bg-green-500/20 text-green-300 border-green-500/30',
'tyak-moscow': 'bg-blue-500/20 text-blue-300 border-blue-500/30',
'opt-market': 'bg-purple-500/20 text-purple-300 border-purple-500/30',
}
return styles[market as keyof typeof styles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30'
}
```
**ПРАВИЛА ОТОБРАЖЕНИЯ:**
- **Условие**: Показывать badge только если supplier.market существует
- **Размер**: text-xs для соответствия ИНН
- **Позиция**: Справа от ИНН в той же строке
- **Приоритет**: Рынок важнее типа организации для селлера
### 3.3 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ
**🎯 ОСНОВНОЙ ПРИНЦИП:**
Выбранные товары в детальном каталоге (блок 3) сохраняются при смене поставщика и могут быть удалены только явным действием пользователя.
**🔄 WORKFLOW СЦЕНАРИИ:**
**СЦЕНАРИЙ 1: Добавление товаров от разных поставщиков**
1. Пользователь выбирает Поставщика А
2. Добавляет Товар 1 и Товар 2 в детальный каталог
3. Переключается на Поставщика Б
4. Товар 1 и Товар 2 остаются в блоке 3
5. Добавляет Товар 3 от Поставщика Б
6. В блоке 3: Товар 1, Товар 2 (от А) + Товар 3 (от Б)
**СЦЕНАРИЙ 2: Визуальная индикация в блоке 2**
- При переключении на поставщика, товары которого уже есть в блоке 3, показываются как "выбранные"
- Товары от других поставщиков в блоке 2 не отображаются
**🛠️ ТЕХНИЧЕСКИЕ ПРАВИЛА:**
**Состояние selectedProductsForDetailView:**
- Глобальное состояние всех выбранных товаров
- НЕ зависит от текущего поставщика
- НЕ очищается при смене поставщика
- Очищается только явными действиями пользователя
**Единственные способы удаления:**
1. Кнопка "Удалить из каталога" в карточке товара (блок 3)
2. Кнопка "Очистить каталог" в заголовке блока 3
3. НЕ при смене поставщика
**🎨 UX ПРАВИЛА:**
- Счетчик товаров: "Детальный каталог (X товаров от Y поставщиков)"
- Визуальная индикация выбранных товаров в блоке 2
- Информация о поставщике для каждого товара в блоке 3
### 3.4 Правила кнопки "Создать поставку" в разделе "Мои поставки"
#### **3.4.1 Общие принципы**
- **КОНТЕКСТНОСТЬ**: Кнопка создания появляется только в активном табе
- **РАСПОЛОЖЕНИЕ**: Правая часть строки таба, на том же уровне что и название
- **СТИЛИСТИКА**: В том же стиле что и сами табы (соответствует уровню иерархии)
- **ФУНКЦИОНАЛЬНОСТЬ**: Кнопка ведет на страницу создания поставки соответствующего типа
#### **3.4.2 Размещение кнопок по табам**
**УРОВЕНЬ 2 (Подтабы фулфилмента):**
- **📦 Товар → Карточки**: Кнопка "Создать поставку" → `/supplies/create-cards`
- **📦 Товар → Поставщики**: Кнопка "Создать поставку" → `/supplies/create-suppliers`
- **🔧 Расходники селлера**: Кнопка "Создать поставку" → `/supplies/create-consumables`
**УРОВЕНЬ 2 (Подтабы маркетплейсов):**
- **🟣 Wildberries**: Кнопка "Создать поставку" → `/supplies/create-wildberries`
- **🔵 Ozon**: Кнопка "Создать поставку" → `/supplies/create-ozon`
#### **3.4.3 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ**
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
##### **3.4.3.1 Размеры и позиционирование**
```tsx
<div className="w-72 flex-shrink-0">
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
```
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
- **Флекс**: `flex-shrink-0` - корзина не сжимается
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
##### **3.4.3.2 Автодобавление товаров**
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
```tsx
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
if (newQuantity > 0) {
// Автоматически добавляем товар в корзину
updateProductQuantity(product.id, newQuantity)
} else {
// Удаляем товар из корзины при количестве 0
removeFromCart(product.id)
}
}
```
#### 3.2.7.5 Синхронизация данных между блоками
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
1. **Дата поставки**: Из Блока 3 (дата пикер)
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
```
Дата поставки: 08.08.2025
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
Логистическая компания: [Выпадающий список]
```
#### 3.2.7.6 Критические требования
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
- Использовать optional chaining для всех вложенных объектов
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
- Мемоизация расчетов: `useMemo` для дорогих вычислений
- Debounce для инпутов количества
🚨 **UX КОНСИСТЕНТНОСТЬ**:
- Единые стили для всех корзин в системе
- Одинаковое поведение auto-add во всех формах
- Синхронная валидация данных
```
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
##### **3.4.3.3 Структура корзины**
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
2. **Список товаров**:
- Название товара (БЕЗ суффикса "(с рецептурой)")
- Цена за единицу × количество
- Кнопка удаления (X справа)
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
4. **Итого**: Общая сумма с выделением зелёным цветом
5. **Кнопка действия**: "Создать поставку" с градиентом
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
##### **3.4.3.4 Единая функция расчета стоимости**
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
```tsx
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
const product = products.find((p) => p.id === productId)
if (!product) return 0
// Базовая цена товара
let total = (product.pricePerUnit || 0) * quantity
// Добавляем услуги
if (product.services && product.services.length > 0) {
const servicesTotal = product.services.reduce((sum, service) => {
return sum + (service.pricePerUnit || 0) * quantity
}, 0)
total += servicesTotal
}
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
if (product.ffConsumables && product.ffConsumables.length > 0) {
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
return sum + (consumable.price || 0) * quantity // ВАЖНО: .price!
}, 0)
total += ffConsumablesTotal
}
// Добавляем расходники продавца
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
return sum + (consumable.pricePerUnit || 0) * quantity
}, 0)
total += sellerConsumablesTotal
}
return total
}
```
### 3.4 Высота основного блока и функционал
#### **3.4.1 Высота основного блока**
**ФОРМУЛА РАСЧЕТА**:
```css
height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
```
**ПРАВИЛО ВЫРАВНИВАНИЯ**:
- Нижняя граница основного блока должна быть на одном уровне с нижней границей sidebar
- При изменении размера окна высота пересчитывается
- Внутренний скролл: `overflow-y-auto`
#### **3.4.2 Сохранение функционала**
**КРИТИЧЕСКИ ВАЖНО**: При добавлении блока статистики весь существующий функционал сохраняется:
- Таблицы с данными поставок
- Фильтры и сортировка
- Кнопки действий
- Детализация при клике
- Пагинация
- Поиск
**ЗАПРЕЩЕНО**:
- Удалять существующие компоненты
- Изменять логику работы таблиц
- Нарушать существующие API вызовы
### 3.5 Табы "Карточки" и "Поставщики" - объединённая логика
#### **3.5.1 Принцип единого типа предмета**
**КЛЮЧЕВОЕ ПРАВИЛО**: Табы "Карточки" и "Поставщики" - это два способа создания поставок одного типа предмета (ТОВАР)
**СПОСОБЫ СОЗДАНИЯ**:
- **Карточки** - импорт товаров через WB API с автоматическим созданием поставки
- **Поставщики** - прямой заказ товаров у поставщика с указанием рецептуры
**РЕЗУЛЬТАТ**: Оба способа создают `SupplyOrder` с товарами типа `PRODUCT`
#### **3.5.2 Общая статистика**
**ПРАВИЛО**: Блок статистики показывает ОДИНАКОВЫЕ данные для обоих табов
**МЕТРИКИ ДЛЯ ТАБОВ "КАРТОЧКИ" И "ПОСТАВЩИКИ"**:
- Всего поставок товаров (из всех источников)
- Активных поставок товаров (в работе)
- Сумма активных поставок товаров
- Товаров в пути (все способы доставки)
**ЗАПРЕЩЕНО**: Разделять статистику по способу создания
#### **3.5.3 Общий основной блок**
**СОДЕРЖИМОЕ**: Единая таблица всех поставок товаров
**ИСТОЧНИКИ ДАННЫХ**:
- Поставки, созданные через импорт карточек WB
- Поставки, созданные через заказ у поставщиков
- Все промежуточные и завершённые поставки
**РАЗЛИЧИЯ ТАБОВ**:
- Только кнопки создания ведут на разные страницы
- Таб "Карточки": `/supplies/create-cards`
- Таб "Поставщики": `/supplies/create-suppliers`
### 3.7 Структура страницы создания поставки расходников
#### **3.7.1 Обязательная структура страницы**
**ПРИНЦИП**: Страница состоит из трёх визуально разделённых блоков
```
┌─────────────────────────────────────────┐
│ 1. БЛОК ТАБОВ (навигация) │
│ - Фиксированная высота │
│ - Glass-эффект │
│ - Иерархическая структура │
├─────────────────────────────────────────┤
│ 2. БЛОК СТАТИСТИКИ (метрики) │
│ - Контекстные данные │
│ - 4 карточки в ряд (desktop) │
│ - Динамическое обновление │
├─────────────────────────────────────────┤
│ 3. ОСНОВНОЙ БЛОК (контент) │
│ - Сохраняет весь функционал │
│ - Таблицы, фильтры, действия │
│ - Высота до низа sidebar │
└─────────────────────────────────────────┘
```
#### **3.5.2 Блок статистики - контекстные метрики**
**ПРАВИЛО**: Статистика меняется в зависимости от выбранных табов
**Для путей "Фулфилмент → Товар → Карточки/Поставщики":**
- Всего поставок
- Активных поставок
- Сумма активных поставок
- В пути
**Для пути "Фулфилмент → Расходники селлера":**
- Всего поставок
- Активных поставок
- Видов расходников
- Критические остатки
**Для путей "Маркетплейсы → Wildberries/Ozon":**
- Поставок на маркетплейс
- Товаров отправлено
- Возвраты за неделю
- Эффективность поставок
### 3.6 Многоуровневая таблица поставок
#### **ПЕРВЫЙ УРОВЕНЬ** _(основной список)_:
- **СОРТИРОВКА**: Номер поставки от большего к меньшему
- **ОБЯЗАТЕЛЬНЫЕ КОЛОНКИ**:
- Порядковый номер поставки
- Количество видов расходников
- Стоимость всей поставки
- Количество категорий
- Статус поставки
#### **ВТОРОЙ УРОВЕНЬ** _(детализация по клику)_:
- **АКТИВАЦИЯ**: По клику на строку первого уровня
- **СОДЕРЖАНИЕ**:
- Название расходника
- Количество
- Цена
- Категория
- Поставщик
- **ОГРАНИЧЕНИЯ**: Только просмотр, редактирование запрещено
---
## 4. 🏭 **WORKFLOW ФУЛФИЛМЕНТА**
> **Источник**: rules-complete.md, раздел 11.2, строки 2003-2022
### 4.1 Трехэтапный процесс
**ЭТАП 1: Приемка товаров**
1. Фулфилмент получает поставки от поставщиков
2. Товары размещаются на складе по модулям
3. Ведется учет поступлений и остатков
**ЭТАП 2: Обработка товаров**
4. Товары обрабатываются согласно рецептурам селлеров
5. Применяются услуги фулфилмента
6. Создаются готовые продукты
**ЭТАП 3: Управление услугами**
7. Фулфилмент предоставляет каталог услуг для рецептур
8. Устанавливает цены на расходники
9. Управляет логистическими маршрутами
### 4.2 Движение товаров
**Поступление товаров**:
- **ПОСТАВКИ**: От поставщиков через систему заказов
- **ВОЗВРАТЫ**: Товары, возвращенные с ПВЗ
- **ПЕРЕМЕЩЕНИЯ**: Между складами и магазинами
**Расход товаров**:
- **ОТГРУЗКА**: Товары отправлены селлерам
- **СПИСАНИЕ**: Брак, утрата, утилизация
- **ВОЗВРАТ**: Возврат поставщику
- **ИСПОЛЬЗОВАНИЕ**: Расходники для операций
---
## 5. 🚚 **WORKFLOW ЛОГИСТИКИ**
> **Источник**: rules-complete.md, раздел 12.2, строки 2063-2089
### 5.1 Четырехэтапный процесс доставки
**ЭТАП 1: Получение заявки**
1. Логистика получает уведомление о новой поставке
2. Заявка появляется в разделе "Заявки" кабинета логистики
3. Логист изучает детали поставки (объем, вес, маршрут)
**ЭТАП 2: Подтверждение доставки**
4. Логист нажимает кнопку "Одобрить"
5. Статус поставки меняется на `LOGISTICS_CONFIRMED`
6. Уведомления отправляются всем участникам
**ЭТАП 3: Забор товара**
7. Логист приезжает к поставщику за товаром
8. Поставщик отгружает товар логисту
9. Поставщик отмечает "Отправлено"
10. Статус меняется на `SHIPPED`, затем `IN_TRANSIT`
**ЭТАП 4: Доставка**
11. Логистика доставляет товар на фулфилмент-центр
12. В кабинете логистики нажимают "Доставлено"
13. Фулфилмент принимает товар и отмечает "Принято"
---
## 6. 🤝 **СИСТЕМА ПАРТНЕРСТВА**
> **Источник**: rules-complete.md, раздел 13.2, строки 2156-2186
### 6.1 Два способа установления партнерства
**СПОСОБ 1: Автоматическое партнерство**
1. Пользователь А указывает email организации Б при создании поставки/заявки
2. Система проверяет: зарегистрирована ли организация с таким email
3. Если ДА → автоматически создается связь Partnership
4. Если НЕТ → отправляется приглашение на email
5. При регистрации по приглашению → автоматически создается Partnership
6. Обе организации видят друг друга в разделе "Партнеры"
7. Могут создавать заказы друг другу
**СПОСОБ 2: Заявочная система**
1. Пользователь А идет в раздел "Партнеры"
2. Нажимает "Добавить партнера"
3. Указывает данные организации Б (ИНН, название)
4. Система создает PartnershipRequest со статусом PENDING
5. Организация Б получает уведомление о заявке
6. Организация Б принимает (ACCEPTED) или отклоняет (REJECTED) заявку
### 6.2 Статусы заявок
**4 статуса PartnershipRequest**:
- **PENDING** - ожидает ответа
- **ACCEPTED** - принята (создается Partnership)
- **REJECTED** - отклонена
- **CANCELLED** - отменена инициатором
---
## 7. 🔧 **ИНТЕГРАЦИЯ С УСЛУГАМИ**
> **Источник**: rules-complete.md, раздел 14.3, строки 2342-2349
### 7.1 5-шаговый workflow использования услуг
1. **Селлер выбирает услугу** из каталога при создании рецептуры
2. **Система включает услугу** в состав заказа
3. **Фулфилмент получает задание** на выполнение услуги
4. **Исполнитель применяет услугу** к товару согласно технологии
5. **Система создает готовый продукт** с учетом всех услуг
### 7.2 Связь с рецептурами
**Архитектура интеграции**:
```
СЕЛЛЕР (создание поставки)
└── Рецептура
├── Товар (от поставщика)
├── Услуги фулфилмента ← Каталог услуг
├── Расходники селлера
└── Расходники фулфилмента
ФУЛФИЛМЕНТ (обработка)
├── Входящие поставки → Товары на складе
└── Услуги → Выполнение по рецептуре
```
---
## 8. 🚨 **КРИТИЧЕСКИЕ СИТУАЦИИ**
> **Источник**: rules-complete.md, раздел 24, строки 3151-3190
### 8.1 Отмена заказов на разных этапах
**ТИП 1: Отмена до подтверждения поставщиком**
- Селлер может отменить заказ в статусе PENDING
- Система меняет статус на CANCELLED
- Уведомление поставщику об отмене
**ТИП 2: Отмена после подтверждения поставщиком**
- Требуется согласие поставщика
- Возможны штрафные санкции
- Согласование через мессенджер
**ТИП 3: Отмена во время транспортировки**
- Связь с логистикой для возврата груза
- Дополнительные транспортные расходы
- Перерасчет стоимости
**ТИП 4: Отмена после доставки**
- Процедура возврата товара
- Контроль качества возвращаемого товара
- Возмещение понесенных расходов
### 8.2 Алгоритм частичной доставки
**ШАГ 1: Выявление недостачи**
- Фулфилмент сверяет план и факт
- Фиксирует недостающие позиции
- Уведомляет всех участников
**ШАГ 2: Принятие решения**
- Селлер выбирает: ждать доставку или принять частично
- Поставщик объясняет причины недостачи
- Согласование дальнейших действий
**ШАГ 3: Обработка частичной доставки**
- Система разделяет заказ на выполненную и невыполненную части
- Перерасчет стоимости и логистики
- Создание нового заказа на недостающее
**ШАГ 4: Документооборот**
- Корректировка документов
- Фиксация фактических показателей
- Закрытие или продление заказа
---
## 9. 🛠️ **ПРОТОКОЛЫ РАЗРАБОТКИ**
> **Источник**: rules-complete.md, раздел "Обязательная последовательность", строки 41-49
### 9.1 7-шаговый workflow разработки
1. **Читать rules-complete.md** - перед любым изменением кода
2. **Следовать правилам взаимодействия** - честность и прозрачность
3. **Проверить специфичные правила кабинета** - если работа с конкретным типом организации
4. **Использовать TodoWrite** - для планирования задач
5. **Следовать техническим правилам** - GraphQL, TypeScript, система партнерства
6. **Проверять реализацию** - соответствие правилам и архитектуре
7. **Проводить финальную проверку** - качество и корректность результата
### 9.2 Протокол высокой сложности
**3-этапный процесс для сложных задач**:
1. **СТОП! ГЛУБОКИЙ АНАЛИЗ** - уточнить все требования у пользователя
2. **ИССЛЕДОВАНИЕ** - изучить все связанные файлы параллельно
3. **ДЕТАЛЬНЫЙ ПЛАН** - с промежуточными проверками и rollback точками
---
## 10. 📊 **СИСТЕМА УЧЕТА ДВИЖЕНИЯ ТОВАРОВ**
> **Источник**: rules-complete.md, раздел 7, строки 742-770
### 10.1 Принципы учета
**ПРИНЦИП 1: Полная прозрачность**
- Каждое движение товара фиксируется
- Доступна история всех операций
- Отчетность в реальном времени
**ПРИНЦИП 2: Двойной контроль**
- План и факт сверяются системой
- Выявление и анализ расхождений
- Автоматические уведомления об отклонениях
**ПРИНЦИП 3: Статусная модель**
- Каждый товар имеет четкий статус
- Переходы между статусами контролируются
- История изменений сохраняется
**ПРИНЦИП 4: Интеграция ролей**
- Каждая роль видит релевантную информацию
- Права доступа разграничены по функциям
- Совместная работа через единую систему
**ПРИНЦИП 5: Автоматизация**
- Минимум ручного ввода данных
- Автоматические расчеты и уведомления
- Система предотвращения ошибок
---
## 11. 🏪 **КАБИНЕТ ПОСТАВЩИКА**
> **Источник**: [rules-complete.md#10-кабинет-поставщика](./rules-complete.md#10--кабинет-поставщика)
### 11.1 Разделение понятий: РЫНОК vs МАРКЕТ
**🔍 КРИТИЧЕСКОЕ РАЗДЕЛЕНИЕ ПОНЯТИЙ:**
### **РЫНОК** 🏪 - физическое торговое место
- **Назначение**: Географическая принадлежность поставщиков
- **Примеры**: Садовод, ТЯК Москва
- **Структура**: Название + адрес
- **Связь**: Поставщик принадлежит рынку
### **МАРКЕТ** 🛒 - раздел системы для торговли
- **Назначение**: Глобальный каталог товаров в системе
- **Роут**: `/market` - просмотр и заказ товаров
- **Содержание**: Все доступные товары от всех поставщиков
- **Связь**: НЕ связан с физическими рынками
**🏢 АРХИТЕКТУРА ПРИНАДЛЕЖНОСТИ:**
```
РЫНОК (физическое место)
└── Поставщик (Organization.market)
└── Товары/Расходники (наследуют рынок от поставщика)
└── Отображаются в МАРКЕТЕ (/market)
```
**🎯 ПРИНЦИПЫ ИЕРАРХИИ:**
1. **РЫНОК → ПОСТАВЩИК**: Поставщик работает на конкретном рынке
2. **ПОСТАВЩИК → ТОВАРЫ**: Товары принадлежат поставщику с его рынка
3. **ТОВАРЫ → МАРКЕТ**: Все товары показываются в глобальном маркете (/market)
4. **НАСЛЕДОВАНИЕ**: Товары получают рынок от организации поставщика
**🏪 ФИЗИЧЕСКИЕ РЫНКИ В СИСТЕМЕ:**
- **"Садовод"** (`sadovod`) - Москва, 14-й км МКАД
- **Цветовая схема**: `bg-green-500/20 text-green-300 border-green-500/30`
- **"ТЯК Москва"** (`tyak-moscow`) - Москва, Алтуфьевское шоссе, 27
- **Цветовая схема**: `bg-blue-500/20 text-blue-300 border-blue-500/30`
**🛒 МАРКЕТ В СИСТЕМЕ:**
- **Роут**: `/market` - глобальный каталог товаров
- **Функции**: Просмотр, поиск, фильтрация, заказ товаров
- **Источник**: Товары от всех поставщиков всех рынков
- **Отображение рынка**: В карточках поставщиков и товаров
**🔧 ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
- **Поле рынка**: `Organization.market` (String?) - принадлежность поставщика к рынку
- **Настройка рынка**: В настройках организации поставщика
- **Отображение в маркете**: Товары показывают рынок через `product.organization.market`
- **Фильтрация**: В маркете по рынку поставщика
### 11.2 Основные возможности
**СОЗДАНИЕ КАРТОЧЕК**:
- **ТОВАР** - базовые товары поставщика
- **РАСХОДНИКИ** - материалы и вспомогательные товары
### 11.3 Обязательные поля карточки
**Базовые параметры**:
- Фото (система загрузки и управления изображениями)
- Название
- Автоматическая генерация артикула СФ
- Описание
- Количество предметов в единицах
- Количество комплектов (если применимо)
- Категория (28 предустановленных + специализированные для расходников)
- Бренд, Цвет, Размер/объем, Вес, Габариты, Материал
- Цена за единицу и за комплект
- Заказано, В пути, Остаток, Продано
### 11.4 Отображение информации в карточках
**Каждая карточка содержит**:
- Основное изображение
- Название и артикул СФ
- Цена за единицу/комплект
- Категория и статус активности
- Данные о движении: остаток, заказано, в пути, продано
- Индикаторы низких остатков
### 11.5 Статистика поставщика
**Блок статистики включает**:
- **ТОВАРЫ**: Общая статистика товаров поставщика
- **РАСХОДНИКИ**: Материалы и вспомогательные товары
- Классифицируются при заказе в зависимости от заказчика
- Общая статистика по всем расходникам
---
## 12. 🏠 **ОБЩИЕ ПРАВИЛА КАБИНЕТОВ**
> **Источник**: [rules-complete.md#8-общие-правила-кабинетов](./rules-complete.md#8--общие-правила-кабинетов)
### 12.1 Универсальная структура кабинетов
**ВСЕ ТИПЫ КАБИНЕТОВ** включают следующие обязательные разделы:
#### 12.1.1 Страница "Главная"
**СТАТУС**: Реализовано
**ДОСТУП**: Через навигацию в sidebar для всех типов кабинетов
**СОДЕРЖАНИЕ**: Универсальная страница с типо-зависимыми компонентами
**ПРАВИЛА**:
- **ОБЯЗАТЕЛЬНО**: Каждый тип кабинета должен иметь страницу "Главная"
- **НАВИГАЦИЯ**: Доступ через кнопку в sidebar (первая в списке)
- **УНИВЕРСАЛЬНОСТЬ**: Одинаковая структура навигации для всех кабинетов
- **РОУТ**: `/home` с универсальным компонентом HomePageWrapper
- **КОМПОНЕНТЫ**: 4 типо-зависимых компонента: SellerHomePage, FulfillmentHomePage, WholesaleHomePage, LogistHomePage
#### 12.1.2 Раздел "Экономика"
**СТАТУС**: Реализовано в системе
**РАСПОЛОЖЕНИЕ**: Перед настройками в каждом кабинете
**СОДЕРЖАНИЕ**: Пустые разделы-заглушки с пометкой "будет добавлен позже"
**ПРАВИЛА**:
- **ОБЯЗАТЕЛЬНО**: Каждый кабинет имеет раздел "Экономика"
- **РОУТ**: `/economics` с универсальным компонентом EconomicsPageWrapper
- **КОМПОНЕНТЫ**: 4 типо-зависимых компонента экономики: SellerEconomicsPage, FulfillmentEconomicsPage, WholesaleEconomicsPage, LogistEconomicsPage
- **КНОПКА**: "Экономика" в sidebar навигации перед настройками
- **БЕЗОПАСНОСТЬ**: Проверки доступа и безопасности в экономических компонентах
#### 12.1.3 Общие разделы для всех кабинетов
**УНИВЕРСАЛЬНЫЕ РАЗДЕЛЫ** (доступны всем типам):
- 🏠 **Главная** - основная страница кабинета (реализовано)
- 🛒 **Маркет** - просмотр и заказ товаров
- 🤝 **Партнеры** - управление контрагентами
- 💬 **Мессенджер** - внутренняя связь
- 💰 **Экономика** - финансовая аналитика (реализовано)
- ⚙️ **Настройки** - профиль и конфигурация
**СПЕЦИАЛИЗИРОВАННЫЕ РАЗДЕЛЫ** (зависят от типа кабинета):
- Определяются в соответствующих разделах каждого кабинета
### 12.2 Правила sidebar навигации
#### 12.2.1 Структура навигации
**ОБЩИЙ ПРИНЦИП**:
- Условное отображение: `{user?.organization?.type === "TYPE" && (...)}`
- Адаптивность: сворачиваемый sidebar с `getSidebarMargin()`
- Состояния активности: подсветка текущего раздела
**ПОРЯДОК РАЗДЕЛОВ В SIDEBAR**:
1. 🏠 **Главная** (реализовано для всех)
2. **Специализированные разделы** (зависят от типа кабинета)
3. 🛒 **Маркет** (универсальный)
4. 🤝 **Партнеры** (универсальный)
5. 💬 **Мессенджер** (универсальный)
6. 💰 **Экономика** (универсальный, реализовано)
7. ⚙️ **Настройки** (универсальный)
8. **Выход** (универсальный)
#### 12.2.2 Типо-зависимая логика
**АДАПТИВНЫЙ РОУТИНГ**:
```typescript
// Пример: кнопка "Поставки" ведет на разные страницы
const handleSuppliesClick = () => {
switch (user?.organization?.type) {
case 'FULFILLMENT':
router.push('/fulfillment-supplies')
break
case 'SELLER':
router.push('/supplies')
break
case 'WHOLESALE':
router.push('/supplies')
break
case 'LOGIST':
router.push('/logistics-orders')
break
}
}
```
---
## 13. 📋 **КАТЕГОРИИ ТОВАРОВ И РАСХОДНИКОВ**
> **Источник**: [rules-complete.md#22-категории-товаров-и-расходников](./rules-complete.md#22--категории-товаров-и-расходников)
### 13.1 Полный список 28 универсальных категорий товаров
1. Одежда и обувь
2. Косметика и парфюмерия
3. Дом и сад
4. Детские товары
5. Спорт и отдых
6. Электроника
7. Книги
8. Здоровье
9. Автотовары
10. Строительство и ремонт
11. Продукты питания
12. Зоотовары
13. Дача, сад и огород
14. Канцелярские товары
15. Хобби и творчество
16. Украшения и аксессуары
17. Сумки и чемоданы
18. Техника для дома
19. Музыкальные инструменты
20. Игры и игрушки
21. Мебель
22. Товары для красоты
23. Бытовая химия
24. Товары для путешествий
25. Медицинские товары
26. Религиозные товары
27. Антиквариат и коллекционирование
28. Прочие товары
### 13.2 12 специализированных категорий расходников
#### 🎁 **1. УПАКОВКА И ЗАЩИТА**
- Коробки (различных размеров)
- Пакеты (полиэтиленовые, бумажные, фирменные)
- Пузырчатая пленка, воздушные подушки
- Стрейч-пленка, гофрокартон
- Паллетная пленка, защитные уголки
#### 🏷️ **2. МАРКИРОВКА И ИДЕНТИФИКАЦИЯ**
- Этикетки (адресные, штрих-код, QR-код)
- Бирки (ценники, размерники)
- Стикеры и наклейки
- Маркеры и ручки
- Штампы и печати, термоэтикетки
#### 🔧 **3. КРЕПЕЖ И СОЕДИНЕНИЕ**
- Скотч (прозрачный, цветной, армированный)
- Клей и клеевые составы
- Стяжки пластиковые
- Степлер и скобы
- Веревки и шнуры, стрейч-лента
#### 📄 **4. ДОКУМЕНТООБОРОТ И ВКЛАДЫШИ**
- Накладные и сопроводительные документы
- Инструкции по эксплуатации
- Гарантийные талоны
- Рекламные буклеты, визитки и флаеры
- Благодарственные письма, купоны и промокоды
#### 🧼 **5. ГИГИЕНА И БЕЗОПАСНОСТЬ**
- Перчатки (латексные, нитриловые)
- Маски и респираторы
- Антисептики и дезинфекторы
- Салфетки и тряпки
- Фартуки и халаты, бахилы
#### 🛠️ **6. ИНСТРУМЕНТЫ И ПРИСПОСОБЛЕНИЯ**
- Ножи и резаки, ножницы
- Линейки и рулетки
- Упаковочные машины (ленточные)
- Дозаторы скотча
- Пистолеты для термоклея
- Весы и мерная тара
#### 🎨 **7. БРЕНДИНГ И ДИЗАЙН**
- Фирменные пакеты с логотипом
- Брендированные коробки
- Цветная упаковочная бумага
- Ленты и банты
- Наклейки с логотипом компании
- Подарочная упаковка
#### ⚡ **8. СПЕЦИАЛИЗИРОВАННЫЕ МАТЕРИАЛЫ**
- Антистатические пакеты
- Влагопоглотители
- Температурные индикаторы
- Хрупкие наклейки
- Пломбы и пломбировочные материалы
- Защита от краж (магнитные датчики)
#### 🏪 **9. ТОРГОВОЕ ОБОРУДОВАНИЕ**
- Манекены и вешалки
- Ценникодержатели
- Подставки и стойки
- Корзины и тележки
- Зеркала примерочные
- Освещение витрин
#### 🚚 **10. ЛОГИСТИКА И СКЛАДИРОВАНИЕ**
- Паллеты и поддоны
- Контейнеры и ящики
- Стеллажные системы
- Погрузочные ремни
- Защитные чехлы
- Адресные ярлыки для груза
#### 💻 **11. ТЕХНИЧЕСКИЕ РАСХОДНИКИ**
- Картриджи для принтеров
- Термоголовки, красящие ленты
- Батарейки для сканеров
- Чистящие средства для техники
- Запчасти для упаковочного оборудования
#### 🎪 **12. СЕЗОННЫЕ И ПРАЗДНИЧНЫЕ**
- Новогодняя упаковка
- Подарочные мешки
- Праздничные ленты
- Тематические наклейки
- Открытки и поздравления
- Сезонная упаковочная бумага
**ПРИМЕЧАНИЕ**: Данные категории являются рекомендательными и могут быть адаптированы под специфику конкретного поставщика расходников.
---
## 📊 **СТАТИСТИКА ПРОЦЕССОВ**
### По объему (строки):
- **UI процессы селлера**: ~942 строки (самый объемный)
- **Процесс создания продукта**: ~175 строк (самый детализированный)
- **Категории товаров и расходников**: ~141 строка (классификационная система)
- **Кабинет поставщика**: ~103 строки (процессы РЫНОК vs МАРКЕТ)
- **Общие правила кабинетов**: ~96 строк (универсальные процессы)
- **Workflow поставок**: ~45 строк (самый критичный)
- **Система партнерства**: ~31 строка
- **Workflow логистики**: ~27 строк
- **Workflow фулфилмента**: ~20 строк
### По ролям:
- **Селлер**: 6 процессов
- **Поставщик**: 5 процессов
- **Фулфилмент**: 5 процессов
- **Логистика**: 3 процесса
- **Универсальные**: 3 процесса
### По критичности:
- **Критические**: Workflow поставок, Создание продукта, РЫНОК vs МАРКЕТ
- **Важные**: UI процессы, Категории товаров, Общие правила кабинетов
- **Вспомогательные**: Система партнерства, Учет движения, Протоколы разработки
---
**Дата создания**: Август 2025
**Общий объем**: 1733 строки процессов (фактический подсчет)
**Файл содержит**: 1805 строк всего (включая навигацию и статистику)
**Источник**: [rules-complete.md](./rules-complete.md)
**СТАТУС**: ПОЛНОСТЬЮ ЗАПОЛНЕН - все ключевые процессы добавлены
**Связанные файлы**:
- [rules-complete.md](./rules-complete.md) - Основной файл с бизнес-правилами
- [interaction-integrity-rules.md](./interaction-integrity-rules.md) - Методология работы
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила