Files
sfera-new/docs/data-layer/PRISMA_MODEL_RULES.md
Veronika Smirnova 12fd8ddf61 feat(supplier-orders): добавить параметры поставки в таблицу заявок
- Добавлены колонки Объём и Грузовые места между Цена товаров и Статус
- Реализованы инпуты для ввода volume и packagesCount в статусе PENDING для роли WHOLESALE
- Добавлена мутация UPDATE_SUPPLY_PARAMETERS с проверками безопасности
- Скрыта строка Поставщик для роли WHOLESALE (поставщик знает свои данные)
- Исправлено выравнивание таблицы при скрытии уровня поставщика
- Реорганизованы документы: legacy-rules/, docs/, docs-and-reports/

ВНИМАНИЕ: Компонент multilevel-supplies-table.tsx (1697 строк) нарушает правило модульной архитектуры (>800 строк требует рефакторинга)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 18:47:23 +03:00

514 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ПРАВИЛА PRISMA МОДЕЛЕЙ СИСТЕМЫ SFERA
## 🎯 ОБЩИЕ ПРИНЦИПЫ МОДЕЛИРОВАНИЯ
### 1. СОГЛАШЕНИЯ ИМЕНОВАНИЯ
```prisma
// ✅ Правильное именование
model Organization {
id String @id @default(cuid()) // PascalCase для моделей
createdAt DateTime @default(now()) // camelCase для полей
updatedAt DateTime @updatedAt // Автоматические временные метки
}
// ✅ Маппинг таблиц
@@map("organizations") // snake_case для таблиц БД
// ❌ Неправильное именование
model organization { ... } // Должно быть PascalCase
model User {
user_id String // Должно быть camelCase: userId
}
```
### 2. ОБЯЗАТЕЛЬНЫЕ ПОЛЯ ДЛЯ ВСЕХ МОДЕЛЕЙ
```prisma
model BaseModel {
id String @id @default(cuid()) // Всегда CUID как PK
createdAt DateTime @default(now()) // Дата создания
updatedAt DateTime @updatedAt // Автоматическое обновление
@@map("base_models")
}
```
### 3. ТИПЫ ДАННЫХ И ОГРАНИЧЕНИЯ
```prisma
// ✅ Правильные типы для денежных величин
price Decimal @db.Decimal(12, 2) // Высокая точность
totalPrice Decimal @db.Decimal(15, 2) // Для больших сумм
// ✅ JSON для гибких данных
phones Json? // Массивы телефонов
validationData Json? // API данные
// ✅ Ограничения уникальности
inn String @unique // Уникальные бизнес-идентификаторы
phone String @unique // Уникальные контактные данные
referralCode String? @unique // Опциональные уникальные коды
```
## 📋 ОСНОВНЫЕ ENUMS
### OrganizationType - Типы организаций
```prisma
enum OrganizationType {
FULFILLMENT // Фулфилмент-центры
SELLER // Селлеры (продавцы)
LOGIST // Логистические компании
WHOLESALE // Поставщики (оптовики)
}
```
**Правила использования:**
- ✅ Обязательное поле в Organization
- ✅ Определяет доступные связи и функции
- ❌ Нельзя изменить после создания организации
### SupplyType - Типы расходников
```prisma
enum SupplyType {
FULFILLMENT_CONSUMABLES // Расходники для операций ФФ
SELLER_CONSUMABLES // Расходники селлеров на хранении
}
```
**Критическое разделение:**
- `FULFILLMENT_CONSUMABLES`: Принадлежат ФФ, для внутренних операций
- `SELLER_CONSUMABLES`: Принадлежат селлеру, хранятся на складе ФФ
### SupplyOrderStatus - Статусы поставок
```prisma
enum SupplyOrderStatus {
PENDING // Ожидает одобрения поставщика
SUPPLIER_APPROVED // Одобрено поставщиком → логистика
LOGISTICS_CONFIRMED // Подтверждено логистикой → отгрузка
SHIPPED // Отправлено → доставка
DELIVERED // Доставлено → завершено
CANCELLED // Отменено
// Legacy (обратная совместимость):
CONFIRMED // → SUPPLIER_APPROVED
IN_TRANSIT // → SHIPPED
}
```
## 🏢 КОРНЕВЫЕ МОДЕЛИ СИСТЕМЫ
### User - Пользователи
```prisma
model User {
id String @id @default(cuid())
phone String @unique // Уникальный идентификатор
avatar String? // Аватар (опционально)
managerName String? // Имя менеджера
organizationId String? // Связь с организацией
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Связи
organization Organization? @relation(fields: [organizationId], references: [id])
sentMessages Message[] @relation("SentMessages")
smsCodes SmsCode[]
@@map("users")
}
```
**Критические правила:**
-`phone` - единственный способ входа в систему
- ✅ Один пользователь может быть связан только с одной организацией
-`organizationId` опциональный - пользователь может существовать без организации
### Organization - Организации
```prisma
model Organization {
id String @id @default(cuid())
inn String @unique // Уникальный ИНН
type OrganizationType // Тип организации
name String? // Название (опционально)
fullName String? // Полное название
// Реквизиты из API Dadata
ogrn String?
address String?
// ... другие поля из API
dadataData Json? // Полные данные Dadata
// Реферальная система
referralCode String? @unique // Уникальный реферальный код
referredById String? // Кто пригласил
referralPoints Int @default(0) // Накопленные баллы
// Связи (критически важные)
users User[] // Пользователи организации
apiKeys ApiKey[] // Ключи маркетплейсов
// Партнерство
counterpartyOf Counterparty[] @relation("CounterpartyOf")
organizationCounterparties Counterparty[] @relation("OrganizationCounterparties")
receivedRequests CounterpartyRequest[] @relation("ReceivedRequests")
sentRequests CounterpartyRequest[] @relation("SentRequests")
@@map("organizations")
}
```
**Критические правила:**
-`inn` уникален - одна организация = один ИНН
-`type` определяет доступный функционал
-`referralCode` генерируется автоматически при создании
- ❌ Нельзя изменить `type` после создания
## 🔄 МОДЕЛИ БИЗНЕС-ПРОЦЕССОВ
### SupplyOrder - Заказы поставок
```prisma
model SupplyOrder {
id String @id @default(cuid())
organizationId String // Заказчик
partnerId String // Поставщик
fulfillmentCenterId String? // ФФ-получатель
logisticsPartnerId String? // Логистика
deliveryDate DateTime // Желаемая дата доставки
status SupplyOrderStatus @default(PENDING) // Текущий статус
// Суммарные данные
totalAmount Decimal @db.Decimal(12, 2) // Общая сумма
totalItems Int // Количество позиций
// Параметры поставки (дополнительные)
packagesCount Int? // Количество грузовых мест - параметр поставки
volume Float? // Объём груза в м³ - параметр поставки
// Управление
responsibleEmployee String? // ID ответственного
notes String? // Комментарии
consumableType String? // Тип расходников
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Связи
organization Organization @relation(fields: [organizationId], references: [id])
partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id])
items SupplyOrderItem[]
routes SupplyRoute[]
@@map("supply_orders")
}
```
**Workflow правила:**
-`status` может изменяться только по определенной последовательности
-`partnerId` - всегда WHOLESALE организация
-`fulfillmentCenterId` - всегда FULFILLMENT организация
- ❌ Нельзя удалить SupplyOrder со статусом DELIVERED
### Supply - Расходники/Товары
```prisma
model Supply {
id String @id @default(cuid())
name String // Название
article String // Артикул
description String? // Описание
price Decimal @db.Decimal(10, 2) // Цена за единицу
quantity Int @default(0) // Количество
unit String @default("шт") // Единица измерения
// Классификация
type SupplyType @default(FULFILLMENT_CONSUMABLES)
category String @default("Расходники")
// Управление складом
minStock Int @default(0) // Минимальный остаток
currentStock Int @default(0) // Текущий остаток
usedStock Int @default(0) // Использованное количество
// Для SELLER_CONSUMABLES
sellerOwnerId String? // ID селлера-владельца
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
@@map("supplies")
}
```
**Критические правила типов:**
-`FULFILLMENT_CONSUMABLES`: `sellerOwnerId` должен быть NULL
-`SELLER_CONSUMABLES`: `sellerOwnerId` обязателен
-`organizationId` для SELLER_CONSUMABLES = ID фулфилмента (место хранения)
- ❌ Нельзя изменить `type` после создания расходника
## 🤝 МОДЕЛИ ПАРТНЕРСТВА
### Counterparty - Контрагенты
```prisma
model Counterparty {
id String @id @default(cuid())
organizationId String // Моя организация
counterpartyId String // Организация-контрагент
type CounterpartyType // Тип партнерства
createdAt DateTime @default(now())
// Связи
organization Organization @relation("OrganizationCounterparties", fields: [organizationId], references: [id])
counterpartyOrg Organization @relation("CounterpartyOf", fields: [counterpartyId], references: [id])
@@unique([organizationId, counterpartyId]) // Уникальность пары
@@map("counterparties")
}
```
### CounterpartyRequest - Заявки на партнерство
```prisma
model CounterpartyRequest {
id String @id @default(cuid())
fromId String // Отправитель
toId String // Получатель
status CounterpartyRequestStatus @default(PENDING)
message String? // Сообщение заявки
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Связи
from Organization @relation("SentRequests", fields: [fromId], references: [id])
to Organization @relation("ReceivedRequests", fields: [toId], references: [id])
@@unique([fromId, toId]) // Одна заявка между организациями
@@map("counterparty_requests")
}
```
**Правила партнерства:**
- ✅ Организация не может отправить заявку сама себе
- ✅ Между двумя организациями может быть только одна активная заявка
- ✅ При принятии заявки (ACCEPTED) создается Counterparty запись
## 📊 МОДЕЛИ ИНТЕГРАЦИЙ
### ApiKey - Ключи маркетплейсов
```prisma
model ApiKey {
id String @id @default(cuid())
marketplace MarketplaceType // WB/Ozon
apiKey String // Зашифрованный ключ
isActive Boolean @default(true)
validationData Json? // Данные валидации
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
@@unique([organizationId, marketplace]) // Один ключ на маркетплейс
@@map("api_keys")
}
```
**Правила безопасности:**
-`apiKey` хранится в зашифрованном виде
- ✅ Только одни ключ на маркетплейс на организацию
-`validationData` содержит результаты проверки ключа
- ❌ Нельзя получить расшифрованный ключ через GraphQL
## 🎯 ПРАВИЛА СВЯЗЕЙ (RELATIONS)
### 1. КАСКАДНЫЕ УДАЛЕНИЯ
```prisma
// ✅ Правильное использование onDelete
model Organization {
supplies Supply[] @relation(onDelete: Cascade) // Удалить все расходники
apiKeys ApiKey[] @relation(onDelete: Cascade) // Удалить все ключи
}
model SupplyOrder {
items SupplyOrderItem[] @relation(onDelete: Cascade) // Удалить все позиции
}
// ❌ Неправильно - потеря критических данных
model User {
organization Organization? @relation(onDelete: Cascade) // Не удалять организацию!
}
```
### 2. ОБЯЗАТЕЛЬНЫЕ И ОПЦИОНАЛЬНЫЕ СВЯЗИ
```prisma
// ✅ Обязательные связи
model Supply {
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
}
// ✅ Опциональные связи
model User {
organizationId String?
organization Organization? @relation(fields: [organizationId], references: [id])
}
```
### 3. ИМЕНОВАНИЕ СВЯЗЕЙ
```prisma
// ✅ Явные имена связей для множественных отношений
model Organization {
// Отправленные заявки
sentRequests CounterpartyRequest[] @relation("SentRequests")
// Полученные заявки
receivedRequests CounterpartyRequest[] @relation("ReceivedRequests")
// Я контрагент для кого-то
counterpartyOf Counterparty[] @relation("CounterpartyOf")
// Мои контрагенты
organizationCounterparties Counterparty[] @relation("OrganizationCounterparties")
}
```
## 🔒 ПРАВИЛА БЕЗОПАСНОСТИ
### 1. ИНДЕКСИРОВАНИЕ ДЛЯ ПРОИЗВОДИТЕЛЬНОСТИ
```prisma
model Organization {
inn String @unique // Автоматический индекс
type OrganizationType
@@index([type]) // Индекс для поиска по типу
}
model SupplyOrder {
organizationId String
status SupplyOrderStatus
createdAt DateTime
@@index([organizationId, status]) // Составной индекс для фильтрации
@@index([createdAt]) // Индекс для сортировки по дате
}
```
### 2. ОГРАНИЧЕНИЯ УНИКАЛЬНОСТИ
```prisma
model Counterparty {
organizationId String
counterpartyId String
@@unique([organizationId, counterpartyId]) // Предотвращает дубли
}
model ApiKey {
organizationId String
marketplace MarketplaceType
@@unique([organizationId, marketplace]) // Один ключ на маркетплейс
}
```
### 3. ВАЛИДАЦИЯ НА УРОВНЕ БД
```prisma
model Supply {
quantity Int @default(0) // Не может быть отрицательным
currentStock Int @default(0)
price Decimal @db.Decimal(10, 2) // Точность денежных сумм
// Проверки через CHECK constraints (на уровне БД):
// CHECK (quantity >= 0)
// CHECK (currentStock >= 0)
// CHECK (price >= 0)
}
```
## 🔄 ПРАВИЛА МИГРАЦИЙ
### 1. БЕЗОПАСНЫЕ ИЗМЕНЕНИЯ (не ломают код)
```prisma
// ✅ Добавление новых опциональных полей
model Organization {
// Существующие поля...
newField String? // Новое поле - nullable
}
// ✅ Добавление новых моделей
model NewFeature {
id String @id @default(cuid())
// ...поля
}
```
### 2. ОПАСНЫЕ ИЗМЕНЕНИЯ (ломают код)
```prisma
// ❌ Изменение типа существующего поля
model Organization {
// Было: referralPoints Int
referralPoints Float // ЛОМАЕТ существующий код
}
// ❌ Удаление существующих полей
model User {
// phone String @unique - УДАЛЕНО, ЛОМАЕТ код
email String @unique // Заменено на email
}
// ❌ Изменение обязательности поля
model Organization {
// Было: name String?
name String! // ЛОМАЕТ записи с NULL
}
```
### 3. СТРАТЕГИЯ БЕЗОПАСНЫХ МИГРАЦИЙ
```prisma
// Этап 1: Добавить новое поле (nullable)
model Organization {
oldField String? // Старое поле
newField String? // Новое поле
}
// Этап 2: Заполнить данные в коде приложения
// UPDATE organizations SET newField = oldField WHERE newField IS NULL
// Этап 3: Сделать поле обязательным
model Organization {
oldField String? // Еще оставляем
newField String! // Теперь обязательное
}
// Этап 4: Удалить старое поле (через несколько версий)
model Organization {
newField String! // Только новое поле
}
```
---
_Извлечено из анализа: Prisma schema, бизнес-логика, правила БД_
ата создания: 2025-08-21_
_Основано на файле: prisma/schema.prisma_