first commit

This commit is contained in:
Bivekich
2025-06-26 06:59:19 +03:00
commit 18e1f3ffb1
124 changed files with 52448 additions and 0 deletions

798
prisma/schema.prisma Normal file
View File

@ -0,0 +1,798 @@
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
firstName String
lastName String
email String @unique
password String
avatar String?
role UserRole @default(ADMIN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Связь с логами аудита
auditLogs AuditLog[]
productHistory ProductHistory[]
managedClients Client[] // Клиенты, которыми управляет менеджер
balanceChanges ClientBalanceHistory[] // История изменений баланса
@@map("users")
}
model AuditLog {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
action AuditAction
details String?
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
@@map("audit_logs")
}
// Модели каталога товаров
model Category {
id String @id @default(cuid())
name String
slug String @unique
description String?
seoTitle String?
seoDescription String?
image String?
isHidden Boolean @default(false)
includeSubcategoryProducts Boolean @default(false)
parentId String?
parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
children Category[] @relation("CategoryHierarchy")
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("categories")
}
model Product {
id String @id @default(cuid())
name String
slug String @unique
article String? @unique
description String?
videoUrl String?
wholesalePrice Float?
retailPrice Float?
weight Float?
dimensions String? // ДхШхВ в формате "10x20x30"
unit String @default("шт")
isVisible Boolean @default(true)
applyDiscounts Boolean @default(true)
stock Int @default(0)
// Связи
categories Category[]
images ProductImage[]
options ProductOption[]
characteristics ProductCharacteristic[]
relatedProducts Product[] @relation("RelatedProducts")
relatedTo Product[] @relation("RelatedProducts")
accessoryProducts Product[] @relation("AccessoryProducts")
accessoryTo Product[] @relation("AccessoryProducts")
history ProductHistory[]
orderItems OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("products")
}
model ProductImage {
id String @id @default(cuid())
url String
alt String?
order Int @default(0)
productId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@map("product_images")
}
model Option {
id String @id @default(cuid())
name String @unique
type OptionType @default(SINGLE)
values OptionValue[]
products ProductOption[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("options")
}
model OptionValue {
id String @id @default(cuid())
value String
price Float @default(0)
optionId String
option Option @relation(fields: [optionId], references: [id], onDelete: Cascade)
products ProductOption[]
createdAt DateTime @default(now())
@@map("option_values")
}
model ProductOption {
id String @id @default(cuid())
productId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
optionId String
option Option @relation(fields: [optionId], references: [id], onDelete: Cascade)
optionValueId String
optionValue OptionValue @relation(fields: [optionValueId], references: [id], onDelete: Cascade)
@@unique([productId, optionId, optionValueId])
@@map("product_options")
}
model Characteristic {
id String @id @default(cuid())
name String @unique
products ProductCharacteristic[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("characteristics")
}
model ProductCharacteristic {
id String @id @default(cuid())
value String
productId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
characteristicId String
characteristic Characteristic @relation(fields: [characteristicId], references: [id], onDelete: Cascade)
@@unique([productId, characteristicId])
@@map("product_characteristics")
}
model ProductHistory {
id String @id @default(cuid())
productId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
action String // CREATE, UPDATE, DELETE
changes Json? // JSON с изменениями
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@map("product_history")
}
enum UserRole {
ADMIN
MODERATOR
USER
}
enum AuditAction {
USER_LOGIN
USER_LOGOUT
USER_CREATE
USER_UPDATE
USER_DELETE
PASSWORD_CHANGE
AVATAR_UPLOAD
PROFILE_UPDATE
CATEGORY_CREATE
CATEGORY_UPDATE
CATEGORY_DELETE
PRODUCT_CREATE
PRODUCT_UPDATE
PRODUCT_DELETE
}
enum OptionType {
SINGLE
MULTIPLE
}
// Модели для клиентов
model Client {
id String @id @default(cuid())
clientNumber String @unique
type ClientType @default(INDIVIDUAL)
name String
email String?
phone String
city String?
markup Float? @default(0)
isConfirmed Boolean @default(false)
profileId String?
profile ClientProfile? @relation(fields: [profileId], references: [id])
managerId String? // Личный менеджер
manager User? @relation(fields: [managerId], references: [id])
balance Float @default(0)
comment String?
// Уведомления
emailNotifications Boolean @default(true)
smsNotifications Boolean @default(true)
pushNotifications Boolean @default(false)
// Поля для юридических лиц
legalEntityType String? // ООО, ИП, АО и т.д.
legalEntityName String? // Наименование юрлица
inn String?
kpp String?
ogrn String?
okpo String?
legalAddress String?
actualAddress String?
bankAccount String?
bankName String?
bankBik String?
correspondentAccount String?
// Связи
vehicles ClientVehicle[]
discounts ClientDiscount[]
deliveryAddresses ClientDeliveryAddress[]
contacts ClientContact[]
contracts ClientContract[]
legalEntities ClientLegalEntity[]
bankDetails ClientBankDetails[]
balanceHistory ClientBalanceHistory[]
orders Order[]
partsSearchHistory PartsSearchHistory[]
favorites Favorite[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("clients")
}
// Модель для избранных товаров
model Favorite {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
// Данные о товаре - для внешних товаров (AutoEuro, PartsAPI)
productId String? // ID товара во внешней системе или внутренний ID
offerKey String? // Ключ предложения (для AutoEuro)
name String // Название товара
brand String // Бренд
article String // Артикул
price Float? // Цена (может отсутствовать)
currency String? // Валюта
image String? // URL изображения
createdAt DateTime @default(now())
// Уникальность по клиенту и комбинации идентификаторов товара
@@unique([clientId, productId, offerKey, article, brand])
@@map("favorites")
}
model ClientProfile {
id String @id @default(cuid())
code String @unique
name String @unique
description String?
baseMarkup Float @default(0)
autoSendInvoice Boolean @default(true)
vinRequestModule Boolean @default(false)
clients Client[]
// Связи с дополнительными настройками
priceRangeMarkups ProfilePriceRangeMarkup[]
orderDiscounts ProfileOrderDiscount[]
supplierMarkups ProfileSupplierMarkup[]
brandMarkups ProfileBrandMarkup[]
categoryMarkups ProfileCategoryMarkup[]
excludedBrands ProfileExcludedBrand[]
excludedCategories ProfileExcludedCategory[]
paymentTypes ProfilePaymentType[]
discountProfiles DiscountProfile[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_profiles")
}
// Наценки от стоимости товара
model ProfilePriceRangeMarkup {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
priceFrom Float
priceTo Float
markupType MarkupType @default(PERCENTAGE)
markupValue Float
createdAt DateTime @default(now())
@@map("profile_price_range_markups")
}
// Скидки от суммы заказа
model ProfileOrderDiscount {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
minOrderSum Float
discountType DiscountType @default(PERCENTAGE)
discountValue Float
createdAt DateTime @default(now())
@@map("profile_order_discounts")
}
// Наценки на поставщиков
model ProfileSupplierMarkup {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
supplierName String
markupType MarkupType @default(PERCENTAGE)
markupValue Float
createdAt DateTime @default(now())
@@map("profile_supplier_markups")
}
// Наценки на бренды
model ProfileBrandMarkup {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
brandName String
markupType MarkupType @default(PERCENTAGE)
markupValue Float
createdAt DateTime @default(now())
@@map("profile_brand_markups")
}
// Наценки на категории товаров
model ProfileCategoryMarkup {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
categoryName String
markupType MarkupType @default(PERCENTAGE)
markupValue Float
createdAt DateTime @default(now())
@@map("profile_category_markups")
}
// Исключенные бренды
model ProfileExcludedBrand {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
brandName String
createdAt DateTime @default(now())
@@map("profile_excluded_brands")
}
// Исключенные категории
model ProfileExcludedCategory {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
categoryName String
createdAt DateTime @default(now())
@@map("profile_excluded_categories")
}
// Типы платежей для профиля
model ProfilePaymentType {
id String @id @default(cuid())
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
paymentType PaymentType
isEnabled Boolean @default(true)
createdAt DateTime @default(now())
@@map("profile_payment_types")
}
enum MarkupType {
PERCENTAGE // Процентная наценка
FIXED_AMOUNT // Фиксированная сумма
}
enum PaymentType {
CASH // Наличные
CARD // Банковская карта
BANK_TRANSFER // Банковский перевод
ONLINE // Онлайн платежи
CREDIT // В кредит
}
model ClientVehicle {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
name String // Название авто
vin String?
frame String?
licensePlate String?
brand String?
model String?
modification String?
year Int?
mileage Int?
comment String?
createdAt DateTime @default(now())
@@map("client_vehicles")
}
// История поиска запчастей
model PartsSearchHistory {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
searchQuery String // Поисковый запрос
searchType SearchType // Тип поиска
brand String? // Бренд (если искали по бренду)
articleNumber String? // Артикул (если искали по артикулу)
// Информация об автомобиле (если поиск был для конкретного авто)
vehicleBrand String?
vehicleModel String?
vehicleYear Int?
resultCount Int @default(0) // Количество найденных результатов
createdAt DateTime @default(now())
@@map("parts_search_history")
}
enum SearchType {
TEXT // Текстовый поиск
ARTICLE // Поиск по артикулу
OEM // Поиск по OEM номеру
VIN // Поиск автомобиля по VIN/Frame
PLATE // Поиск автомобиля по госномеру
WIZARD // Поиск автомобиля по параметрам
PART_VEHICLES // Поиск автомобилей по артикулу детали
}
// Адреса доставки
model ClientDeliveryAddress {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
name String // Название адреса
address String // Полный адрес
deliveryType DeliveryType @default(COURIER)
comment String?
// Дополнительные поля для курьерской доставки
entrance String? // Подъезд
floor String? // Этаж
apartment String? // Квартира/офис
intercom String? // Домофон
deliveryTime String? // Желаемое время доставки
contactPhone String? // Контактный телефон
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_delivery_addresses")
}
// Контакты
model ClientContact {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
phone String?
email String?
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_contacts")
}
// Договоры
model ClientContract {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
contractNumber String
contractDate DateTime
name String
ourLegalEntity String // Наше ЮЛ
clientLegalEntity String // ЮЛ клиента
balance Float @default(0)
currency String @default("RUB")
isActive Boolean @default(true)
isDefault Boolean @default(false)
contractType String // Тип договора
relationship String // Отношение
paymentDelay Boolean @default(false)
creditLimit Float?
delayDays Int?
fileUrl String? // Ссылка на файл договора
balanceInvoices BalanceInvoice[] // Счета на пополнение баланса
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_contracts")
}
// Счета на пополнение баланса
model BalanceInvoice {
id String @id @default(cuid())
contractId String
contract ClientContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
amount Float
currency String @default("RUB")
status InvoiceStatus @default(PENDING)
invoiceNumber String @unique
qrCode String // QR код для оплаты
pdfUrl String? // Ссылка на PDF счета
paymentUrl String? // Ссылка на оплату
expiresAt DateTime // Срок действия счета
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("balance_invoices")
}
enum InvoiceStatus {
PENDING // Ожидает оплаты
PAID // Оплачен
EXPIRED // Просрочен
CANCELLED // Отменен
}
// Юридические лица клиента
model ClientLegalEntity {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
shortName String // Короткое наименование
fullName String // Полное наименование
form String // Форма (ООО, ИП и т.д.)
legalAddress String // Юридический адрес
actualAddress String? // Фактический адрес
taxSystem String // Система налогообложения
responsiblePhone String? // Телефон ответственного
responsiblePosition String? // Должность ответственного
responsibleName String? // ФИО ответственного
accountant String? // Бухгалтер
signatory String? // Подписант
registrationReasonCode String? // Код причины постановки на учет
ogrn String? // ОГРН
inn String // ИНН
vatPercent Float @default(20) // НДС в процентах
bankDetails ClientBankDetails[] // Банковские реквизиты
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_legal_entities")
}
// Банковские реквизиты
model ClientBankDetails {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
legalEntityId String?
legalEntity ClientLegalEntity? @relation(fields: [legalEntityId], references: [id])
name String // Название реквизитов
accountNumber String // Расчетный счет
bankName String // Наименование банка
bik String // БИК
correspondentAccount String // Корреспондентский счет
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_bank_details")
}
// История изменения баланса
model ClientBalanceHistory {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
oldValue Float
newValue Float
comment String?
createdAt DateTime @default(now())
@@map("client_balance_history")
}
model ClientDiscount {
id String @id @default(cuid())
clientId String
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
name String
type DiscountType
value Float // процент или фиксированная сумма
isActive Boolean @default(true)
validFrom DateTime?
validTo DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_discounts")
}
model ClientStatus {
id String @id @default(cuid())
name String @unique
color String @default("#6B7280")
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("client_statuses")
}
enum ClientType {
INDIVIDUAL // Физическое лицо
LEGAL_ENTITY // Юридическое лицо
}
enum DiscountType {
PERCENTAGE // Процентная скидка
FIXED_AMOUNT // Фиксированная сумма
}
// Модели для скидок и промокодов
model Discount {
id String @id @default(cuid())
name String
type DiscountCodeType @default(DISCOUNT)
code String? @unique // Промокод (если есть)
minOrderAmount Float? @default(0)
discountType DiscountType @default(PERCENTAGE)
discountValue Float
isActive Boolean @default(true)
validFrom DateTime?
validTo DateTime?
// Связи с профилями
profiles DiscountProfile[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("discounts")
}
// Связь скидок с профилями клиентов
model DiscountProfile {
id String @id @default(cuid())
discountId String
discount Discount @relation(fields: [discountId], references: [id], onDelete: Cascade)
profileId String
profile ClientProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([discountId, profileId])
@@map("discount_profiles")
}
enum DiscountCodeType {
DISCOUNT // Обычная скидка
PROMOCODE // Промокод
}
enum DeliveryType {
COURIER // Курьер
PICKUP // Самовывоз
POST // Почта России
TRANSPORT // Транспортная компания
}
// Модели для заказов и платежей
model Order {
id String @id @default(cuid())
orderNumber String @unique
clientId String?
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
clientEmail String? // Для гостевых заказов
clientPhone String? // Для гостевых заказов
clientName String? // Для гостевых заказов
status OrderStatus @default(PENDING)
totalAmount Float
discountAmount Float @default(0)
finalAmount Float // totalAmount - discountAmount
currency String @default("RUB")
items OrderItem[]
payments Payment[]
deliveryAddress String?
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("orders")
}
model OrderItem {
id String @id @default(cuid())
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
productId String? // Для внутренних товаров
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
// Для внешних товаров (AutoEuro)
externalId String? // ID товара во внешней системе
name String // Название товара
article String? // Артикул
brand String? // Бренд
price Float // Цена за единицу
quantity Int // Количество
totalPrice Float // price * quantity
createdAt DateTime @default(now())
@@map("order_items")
}
model Payment {
id String @id @default(cuid())
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
yookassaPaymentId String @unique // ID платежа в YooKassa
status PaymentStatus @default(PENDING)
amount Float
currency String @default("RUB")
paymentMethod String? // Способ оплаты
description String?
confirmationUrl String? // URL для подтверждения платежа
// Метаданные от YooKassa
metadata Json?
// Даты
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
paidAt DateTime? // Дата успешной оплаты
canceledAt DateTime? // Дата отмены
@@map("payments")
}
enum OrderStatus {
PENDING // Ожидает оплаты
PAID // Оплачен
PROCESSING // В обработке
SHIPPED // Отправлен
DELIVERED // Доставлен
CANCELED // Отменен
REFUNDED // Возвращен
}
enum PaymentStatus {
PENDING // Ожидает оплаты
WAITING_FOR_CAPTURE // Ожидает подтверждения
SUCCEEDED // Успешно оплачен
CANCELED // Отменен
REFUNDED // Возвращен
}