feat: реализовать V2 backend для товарных поставок селлера

- Создать модели: SellerGoodsSupplyOrder, SellerGoodsInventory, GoodsSupplyRecipeItem
- Реализовать полные GraphQL resolvers с валидацией и авторизацией
- Добавить автоматическое создание инвентаря при статусе DELIVERED
- Внедрить нормализованную рецептуру с RecipeType enum
- Подготовить функции для будущих отгрузок на маркетплейсы
- Интегрировать V2 resolvers модульно в основную схему
- Протестировать создание таблиц в БД

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-09-01 14:39:33 +03:00
parent be891f5354
commit a5816518be
9 changed files with 2257 additions and 767 deletions

View File

@ -27,6 +27,7 @@ model User {
// === НОВЫЕ СВЯЗИ С ПРИЕМКОЙ ПОСТАВОК V2 ===
fulfillmentSupplyOrdersReceived FulfillmentConsumableSupplyOrder[] @relation("FFSupplyOrdersReceiver")
sellerSupplyOrdersReceived SellerConsumableSupplyOrder[] @relation("SellerSupplyOrdersReceiver")
sellerGoodsSupplyOrdersReceived SellerGoodsSupplyOrder[] @relation("SellerGoodsSupplyOrdersReceiver")
@@map("users")
}
@ -143,6 +144,16 @@ model Organization {
// === СВЯЗИ С ИНВЕНТАРЕМ РАСХОДНИКОВ СЕЛЛЕРА V2 ===
sellerInventoryAsOwner SellerConsumableInventory[] @relation("SellerInventory")
sellerInventoryAsWarehouse SellerConsumableInventory[] @relation("SellerInventoryWarehouse")
// === СВЯЗИ С ТОВАРНЫМИ ПОСТАВКАМИ СЕЛЛЕРА V2 ===
sellerGoodsSupplyOrdersAsSeller SellerGoodsSupplyOrder[] @relation("SellerGoodsSupplyOrdersSeller")
sellerGoodsSupplyOrdersAsFulfillment SellerGoodsSupplyOrder[] @relation("SellerGoodsSupplyOrdersFulfillment")
sellerGoodsSupplyOrdersAsSupplier SellerGoodsSupplyOrder[] @relation("SellerGoodsSupplyOrdersSupplier")
sellerGoodsSupplyOrdersAsLogistics SellerGoodsSupplyOrder[] @relation("SellerGoodsSupplyOrdersLogistics")
// === СВЯЗИ С ИНВЕНТАРЕМ ТОВАРОВ СЕЛЛЕРА V2 ===
sellerGoodsInventoryAsOwner SellerGoodsInventory[] @relation("SellerGoodsInventoryOwner")
sellerGoodsInventoryAsWarehouse SellerGoodsInventory[] @relation("SellerGoodsInventoryWarehouse")
@@index([referralCode])
@@index([referredById])
@ -314,6 +325,10 @@ model Product {
// === НОВЫЕ СВЯЗИ СО СКЛАДСКИМИ ОСТАТКАМИ V2 ===
inventoryRecords FulfillmentConsumableInventory[] @relation("InventoryProducts")
sellerInventoryRecords SellerConsumableInventory[] @relation("SellerInventoryProducts")
// === СВЯЗИ С ТОВАРНЫМИ ПОСТАВКАМИ V2 ===
goodsSupplyRecipeItems GoodsSupplyRecipeItem[] @relation("GoodsSupplyRecipeItems")
sellerGoodsInventoryRecords SellerGoodsInventory[] @relation("SellerGoodsInventoryProduct")
@@unique([organizationId, article])
@@map("products")
@ -1043,3 +1058,126 @@ model SellerConsumableInventory {
@@index([sellerId, lastSupplyDate])
@@map("seller_consumable_inventory")
}
// ===============================================
// 🛒 SELLER GOODS SUPPLY SYSTEM V2.0 - ТОВАРНЫЕ ПОСТАВКИ
// ===============================================
// Модель для поставок товаров селлера (V2)
model SellerGoodsSupplyOrder {
// === БАЗОВЫЕ ПОЛЯ ===
id String @id @default(cuid())
status SellerSupplyOrderStatus @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// === ДАННЫЕ СЕЛЛЕРА (создатель) ===
sellerId String // кто заказывает (FK: Organization SELLER)
fulfillmentCenterId String // куда доставлять (FK: Organization FULFILLMENT)
requestedDeliveryDate DateTime // когда нужно
notes String? // заметки селлера
// === ДАННЫЕ ПОСТАВЩИКА ===
supplierId String? // кто поставляет (FK: Organization WHOLESALE)
supplierApprovedAt DateTime? // когда одобрил
packagesCount Int? // количество грузомест
estimatedVolume Decimal? @db.Decimal(8, 3) // объем груза в м³
supplierContractId String? // номер договора
supplierNotes String? // заметки поставщика
// === ДАННЫЕ ЛОГИСТИКИ ===
logisticsPartnerId String? // кто везет (FK: Organization LOGIST)
estimatedDeliveryDate DateTime? // план доставки
routeId String? // маршрут (FK: LogisticsRoute)
logisticsCost Decimal? @db.Decimal(10, 2) // стоимость доставки
logisticsNotes String? // заметки логистики
// === ДАННЫЕ ОТГРУЗКИ ===
shippedAt DateTime? // факт отгрузки
trackingNumber String? // номер отслеживания
// === ДАННЫЕ ПОЛУЧЕНИЯ ===
deliveredAt DateTime? // факт доставки
receivedById String? // кто принял (FK: User)
receiptNotes String? // заметки при получении
// === ФИНАНСЫ ===
totalCostWithDelivery Decimal @default(0) @db.Decimal(12, 2) // общая стоимость с доставкой
actualDeliveryCost Decimal @default(0) @db.Decimal(10, 2) // фактическая стоимость доставки
// === СВЯЗИ ===
seller Organization @relation("SellerGoodsSupplyOrdersSeller", fields: [sellerId], references: [id])
fulfillmentCenter Organization @relation("SellerGoodsSupplyOrdersFulfillment", fields: [fulfillmentCenterId], references: [id])
supplier Organization? @relation("SellerGoodsSupplyOrdersSupplier", fields: [supplierId], references: [id])
logisticsPartner Organization? @relation("SellerGoodsSupplyOrdersLogistics", fields: [logisticsPartnerId], references: [id])
receivedBy User? @relation("SellerGoodsSupplyOrdersReceiver", fields: [receivedById], references: [id])
recipeItems GoodsSupplyRecipeItem[] // нормализованная рецептура товаров
@@map("seller_goods_supply_orders")
}
// Нормализованная рецептура для товарных поставок
model GoodsSupplyRecipeItem {
id String @id @default(cuid())
supplyOrderId String // связь с поставкой (FK: SellerGoodsSupplyOrder)
productId String // какой товар (FK: Product)
quantity Int // количество в рецептуре
recipeType RecipeType // тип компонента в рецептуре
createdAt DateTime @default(now())
// === СВЯЗИ ===
supplyOrder SellerGoodsSupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade)
product Product @relation("GoodsSupplyRecipeItems", fields: [productId], references: [id])
@@unique([supplyOrderId, productId]) // один товар = одна запись в рецептуре
@@map("goods_supply_recipe_items")
}
// Enum для типов компонентов в рецептуре
enum RecipeType {
MAIN_PRODUCT // Основной товар
COMPONENT // Компонент товара
PACKAGING // Упаковка
ACCESSORY // Аксессуар
}
// Инвентарь товаров селлера на складе фулфилмента (V2)
model SellerGoodsInventory {
// === ИДЕНТИФИКАЦИЯ ===
id String @id @default(cuid())
// === СВЯЗИ ===
sellerId String // кому принадлежат товары (FK: Organization SELLER)
fulfillmentCenterId String // где хранятся (FK: Organization FULFILLMENT)
productId String // что хранится (FK: Product)
// === СКЛАДСКИЕ ДАННЫЕ ===
currentStock Int @default(0) // текущий остаток на складе фулфилмента
reservedStock Int @default(0) // зарезервировано для отгрузок
inPreparationStock Int @default(0) // в подготовке к отгрузке
totalReceived Int @default(0) // всего получено с момента создания
totalShipped Int @default(0) // всего отгружено
// === ПОРОГИ ДЛЯ АВТОЗАКАЗА ===
minStock Int @default(0) // минимальный порог для автозаказа
maxStock Int? // максимальный порог (опционально)
// === ЦЕНЫ ===
averageCost Decimal @default(0) @db.Decimal(10, 2) // средняя себестоимость покупки
salePrice Decimal @default(0) @db.Decimal(10, 2) // цена продажи
// === МЕТАДАННЫЕ ===
lastSupplyDate DateTime? // последняя поставка
lastShipDate DateTime? // последняя отгрузка
notes String? // заметки по складскому учету
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// === СВЯЗИ ===
seller Organization @relation("SellerGoodsInventoryOwner", fields: [sellerId], references: [id])
fulfillmentCenter Organization @relation("SellerGoodsInventoryWarehouse", fields: [fulfillmentCenterId], references: [id])
product Product @relation("SellerGoodsInventoryProduct", fields: [productId], references: [id])
@@unique([sellerId, fulfillmentCenterId, productId]) // уникальность: селлер + фф + товар
@@map("seller_goods_inventory")
}