# 📦 АРХИТЕКТУРА СИСТЕМЫ ИНВЕНТАРЯ SFERA > **Версия:** 2.0 > **Дата:** 2025-09-03 > **Статус:** Активная разработка (миграция V1→V2) --- ## 🎯 ОБЗОР СИСТЕМЫ Система инвентаря SFERA представляет собой комплексную архитектуру для управления складскими остатками, ценообразованием и бизнес-процессами между 4 типами организаций: **FULFILLMENT**, **SELLER**, **WHOLESALE**, **LOGIST**. ### 🏗️ АРХИТЕКТУРНЫЕ ПРИНЦИПЫ 1. **Доменная изоляция** - каждая организация видит только свои данные 2. **Двойная система учета** - V1 (legacy) + V2 (современная) с автосинхронизацией 3. **Модульная структура** - независимые резолверы и компоненты 4. **Типобезопасность** - полная типизация TypeScript + GraphQL --- ## 📊 МОДЕЛИ ДАННЫХ ### 🔄 V2 СИСТЕМА (Актуальная) #### **FulfillmentConsumableInventory** - Складские остатки фулфилмента ```prisma model FulfillmentConsumableInventory { id String @id @default(cuid()) fulfillmentCenterId String // Изоляция по фулфилменту productId String // Связь с каталогом товаров // СКЛАДСКИЕ ДАННЫЕ currentStock Int @default(0) // Текущий остаток minStock Int @default(0) // Минимальный порог maxStock Int? // Максимальный порог reservedStock Int @default(0) // Зарезервировано totalReceived Int @default(0) // Всего получено totalShipped Int @default(0) // Всего отгружено // ЦЕНЫ averageCost Decimal @default(0) @db.Decimal(10, 2) // Себестоимость resalePrice Decimal? @db.Decimal(10, 2) // Цена селлерам // МЕТАДАННЫЕ lastSupplyDate DateTime? // Последняя поставка lastUsageDate DateTime? // Последнее использование notes String? // Заметки // СВЯЗИ fulfillmentCenter Organization @relation(\"FFInventory\") product Product @relation(\"InventoryProducts\") catalogItems FulfillmentConsumable[] // → Каталог услуг @@unique([fulfillmentCenterId, productId]) } ``` #### **FulfillmentConsumable** - Каталог услуг фулфилмента ```prisma model FulfillmentConsumable { id String @id @default(cuid()) fulfillmentId String // Владелец услуги inventoryId String? // 🔗 Связь со складом // КАТАЛОЖНЫЕ ДАННЫЕ name String // Оригинальное название nameForSeller String? // 🆕 Кастомное название для селлеров article String? // Артикул pricePerUnit Decimal @default(0) @db.Decimal(10, 2) // Цена селлерам unit String @default(\"шт\") // СКЛАДСКИЕ ДАННЫЕ (синхронизируются из Inventory) currentStock Int @default(0) // Текущий остаток minStock Int @default(0) // Минимальный порог isAvailable Boolean @default(false) // Доступность // МЕТАДАННЫЕ imageUrl String? // Фото товара sortOrder Int @default(0) // Порядок сортировки createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // СВЯЗИ fulfillment Organization @relation(\"FulfillmentServices\") inventory FulfillmentConsumableInventory? @relation(\"CatalogInventory\") } ``` #### **SellerGoodsInventory** - Складские остатки товаров селлера ```prisma model SellerGoodsInventory { id String @id @default(cuid()) sellerId String // Владелец товаров fulfillmentCenterId String // Где хранится productId String // Что хранится // СКЛАДСКИЕ ДАННЫЕ 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) // Цена продажи // СВЯЗИ seller Organization @relation(\"SellerInventory\") fulfillmentCenter Organization @relation(\"SellerInventoryWarehouse\") product Product @relation(\"SellerInventoryProducts\") @@unique([sellerId, fulfillmentCenterId, productId]) } ``` --- ## 🔄 БИЗНЕС-ПРОЦЕССЫ ### 📋 ПОТОК ТОВАРОВ: СЕЛЛЕР → ПОСТАВЩИК → ФУЛФИЛМЕНТ ```mermaid sequenceDiagram participant S as Селлер participant UI as CreateSupplyForm participant API as GraphQL API participant W as Поставщик participant L as Логистика participant F as Фулфилмент participant INV as InventorySystem S->>UI: 1. Создает поставку UI->>API: 2. createSellerGoodsSupply() API->>W: 3. Уведомление о заказе W->>API: 4. Одобряет заказ API->>L: 5. Назначение логистики L->>API: 6. Подтверждение доставки API->>F: 7. Уведомление о приемке F->>INV: 8. processSupplyOrderReceipt() INV->>INV: 9. updateInventory() - V2 INV->>INV: 10. Автосинхронизация V1 ``` ### 🎯 ДЕТАЛЬНЫЕ ЭТАПЫ ПРОЦЕССА #### ЭТАП 1: СОЗДАНИЕ ЗАКАЗА (Селлер) **UI Компоненты:** ```typescript // CreateSuppliersSupplyPage/index.tsx const CreateSuppliersSupplyPage = () => { const [selectedSupplier, setSelectedSupplier] = useState(null) const [selectedGoods, setSelectedGoods] = useState([]) const [productRecipes, setProductRecipes] = useState({}) const [deliverySettings, setDeliverySettings] = useState({}) const handleCreateSupply = async () => { // Валидация формы if (!isFormValid) return // Трансформация V1 → V2 const v2InputData = adaptV1ToV2Format(formData, selectedGoods, productRecipes) // Создание поставки await createSellerGoodsSupply({ variables: { input: v2InputData } }) } } ``` **GraphQL Мутация:** ```graphql mutation CreateSellerGoodsSupply($input: CreateSellerGoodsSupplyInput!) { createSellerGoodsSupply(input: $input) { success message supplyOrder { id status totalAmount seller { id name } supplier { id name } fulfillmentCenter { id name } recipeItems { id productId quantity recipeType product { name article price } } } } } ``` #### ЭТАП 2: ОБРАБОТКА ЗАКАЗА (Backend) **Резолвер:** ```typescript // goods-supply-v2.ts createSellerGoodsSupply: async (_, { input }, context) => { // 1. Валидация пользователя const user = await validateSellerUser(context) // 2. Проверка партнерских отношений const fulfillmentPartner = await validatePartnership( user.organizationId, input.fulfillmentCenterId, 'FULFILLMENT' ) // 3. Создание поставки const supplyOrder = await prisma.sellerGoodsSupplyOrder.create({ data: { sellerId: user.organizationId, fulfillmentCenterId: input.fulfillmentCenterId, supplierId: input.supplierId, status: 'PENDING', requestedDeliveryDate: new Date(input.requestedDeliveryDate), notes: input.notes, totalAmount: calculateTotalAmount(input.recipeItems), // 4. Нормализованная рецептура recipeItems: { create: input.recipeItems.map(item => ({ productId: item.productId, quantity: item.quantity, recipeType: item.recipeType, unitPrice: item.unitPrice, totalPrice: item.unitPrice * item.quantity, })) } }, include: { recipeItems: { include: { product: true }}, seller: true, supplier: true, fulfillmentCenter: true } }) // 5. Уведомления участникам await notifyOrderCreated(supplyOrder) return { success: true, message: 'Поставка успешно создана', supplyOrder } } ``` #### ЭТАП 3: ПРИЕМКА НА СКЛАДЕ (Фулфилмент) **Мутация приемки:** ```typescript // fulfillment-goods-v2.ts receiveSellerGoodsSupply: async (_, { input }, context) => { // 1. Обновление статуса поставки const supplyOrder = await prisma.sellerGoodsSupplyOrder.update({ where: { id: input.supplyOrderId }, data: { status: 'DELIVERED', deliveredAt: new Date(), actualPackagesCount: input.actualPackagesCount, actualVolume: input.actualVolume } }) // 2. Обработка приемки через inventory-management await processSellerGoodsSupplyReceipt( input.supplyOrderId, input.receivedItems // [ { productId, receivedQuantity, unitPrice } ] ) } ``` **Управление инвентарем:** ```typescript // inventory-management-goods.ts export async function processSellerGoodsSupplyReceipt( supplyOrderId: string, items: ReceivedItem[] ): Promise { for (const item of items) { // Обновляем инвентарь товаров селлера await updateSellerGoodsInventory({ sellerId: supplyOrder.sellerId, fulfillmentCenterId: supplyOrder.fulfillmentCenterId, productId: item.productId, quantity: item.receivedQuantity, type: 'INCOMING', sourceType: 'SUPPLY_ORDER', unitCost: item.unitPrice }) } } ``` --- ## 💰 СИСТЕМА ЦЕНООБРАЗОВАНИЯ ### 🎯 ДВУХУРОВНЕВАЯ СИСТЕМА ЦЕН #### **1. СЕБЕСТОИМОСТЬ (averageCost)** ```typescript // Рассчитывается автоматически методом взвешенной средней async function recalculateAverageCost( inventoryId: string, newQuantity: number, newUnitCost: number ): Promise { const inventory = await prisma.fulfillmentConsumableInventory.findUnique({ where: { id: inventoryId } }) const oldTotalValue = inventory.averageCost * (inventory.currentStock - newQuantity) const newTotalValue = newUnitCost * newQuantity const totalQuantity = inventory.currentStock const newAverageCost = (oldTotalValue + newTotalValue) / totalQuantity await prisma.fulfillmentConsumableInventory.update({ where: { id: inventoryId }, data: { averageCost: newAverageCost } }) } ``` #### **2. ЦЕНА ДЛЯ СЕЛЛЕРОВ (resalePrice/pricePerUnit)** ```typescript // Устанавливается фулфилментом вручную updateFulfillmentConsumable: async (_, { input }) => { await prisma.fulfillmentConsumable.update({ where: { id: input.id }, data: { pricePerUnit: input.pricePerUnit, // Цена для селлеров nameForSeller: input.nameForSeller // Название для селлеров } }) // 🔄 Синхронизация с системой инвентаря await prisma.fulfillmentConsumableInventory.update({ where: { id: catalogItem.inventoryId }, data: { resalePrice: input.pricePerUnit } }) } ``` #### **3. МАРЖА И АНАЛИТИКА** ```typescript interface PriceAnalytics { averageCost: number // Себестоимость resalePrice: number // Цена продажи margin: number // Абсолютная маржа marginPercent: number // Процент маржи turnoverDays: number // Оборачиваемость в днях } const calculateMargin = (averageCost: number, resalePrice: number) => ({ margin: resalePrice - averageCost, marginPercent: ((resalePrice - averageCost) / averageCost) * 100 }) // Пример: себестоимость 15.50₽, цена 25.00₽ = 61% маржа ``` --- ## 🔄 АВТОСИНХРОНИЗАЦИЯ V1 ↔ V2 ### 🎯 СТРАТЕГИЯ СИНХРОНИЗАЦИИ ```typescript // inventory-management.ts - основная логика синхронизации export async function updateInventory(movement: InventoryMovement): Promise { // 1. Обновление V2 системы (источник истины) const inventory = await prisma.fulfillmentConsumableInventory.upsert({ where: { fulfillmentCenterId_productId: { fulfillmentCenterId: movement.fulfillmentCenterId, productId: movement.productId, }, }, create: { fulfillmentCenterId: movement.fulfillmentCenterId, productId: movement.productId, currentStock: movement.quantity, totalReceived: movement.type === 'INCOMING' ? movement.quantity : 0, totalShipped: movement.type === 'OUTGOING' ? movement.quantity : 0, averageCost: movement.unitCost || 0, lastSupplyDate: movement.type === 'INCOMING' ? new Date() : undefined, lastUsageDate: movement.type === 'OUTGOING' ? new Date() : undefined, }, update: { currentStock: { increment: movement.type === 'INCOMING' ? movement.quantity : -movement.quantity, }, totalReceived: movement.type === 'INCOMING' ? { increment: movement.quantity } : undefined, totalShipped: movement.type === 'OUTGOING' ? { increment: movement.quantity } : undefined, lastSupplyDate: movement.type === 'INCOMING' ? new Date() : undefined, lastUsageDate: movement.type === 'OUTGOING' ? new Date() : undefined, }, include: { product: true }, }) // 2. 🔄 АВТОСИНХРОНИЗАЦИЯ V1 ← V2 (при поступлении товаров) if (movement.type === 'INCOMING') { try { const existingCatalogItem = await prisma.fulfillmentConsumable.findFirst({ where: { fulfillmentId: movement.fulfillmentCenterId, name: inventory.product.name, }, }) if (existingCatalogItem) { // Обновляем существующую запись в каталоге await prisma.fulfillmentConsumable.update({ where: { id: existingCatalogItem.id }, data: { currentStock: inventory.currentStock, isAvailable: inventory.currentStock > 0, inventoryId: inventory.id, updatedAt: new Date(), }, }) } else { // Создаем новую запись в каталоге await prisma.fulfillmentConsumable.create({ data: { fulfillmentId: movement.fulfillmentCenterId, inventoryId: inventory.id, name: inventory.product.name, article: inventory.product.article || '', pricePerUnit: 0, // Цену устанавливает фулфилмент вручную unit: inventory.product.unit || 'шт', minStock: inventory.minStock, currentStock: inventory.currentStock, isAvailable: inventory.currentStock > 0, imageUrl: inventory.product.imageUrl, sortOrder: 0, }, }) } } catch (syncError) { console.error('⚠️ Failed to sync FulfillmentConsumable:', syncError) // Не останавливаем процесс, если синхронизация не удалась } } // 3. Пересчет средневзвешенной стоимости if (movement.type === 'INCOMING' && movement.unitCost) { await recalculateAverageCost(inventory.id, movement.quantity, movement.unitCost) } } ``` ### 📊 ТРИГГЕРЫ СИНХРОНИЗАЦИИ ```typescript // Когда срабатывает автосинхронизация: 1. **Поступление товаров** → updateInventory(type: 'INCOMING') ├── Обновляет FulfillmentConsumableInventory └── Создает/обновляет FulfillmentConsumable 2. **Использование товаров** → updateInventory(type: 'OUTGOING') ├── Обновляет FulfillmentConsumableInventory └── Обновляет FulfillmentConsumable.currentStock 3. **Изменение цен** → updateFulfillmentConsumable() ├── Обновляет FulfillmentConsumable.pricePerUnit └── Синхронизирует с FulfillmentConsumableInventory.resalePrice 4. **Одноразовая миграция** → syncInventoryToCatalog() └── Переносит все данные из Системы B в Систему A ``` --- ## 🔧 ТЕХНИЧЕСКИЕ ДЕТАЛИ ### 📁 СТРУКТУРА ФАЙЛОВ ```bash # INVENTORY CORE /src/lib/inventory-management.ts # Ядро системы управления остатками /src/lib/inventory-management-goods.ts # Управление товарным инвентарем /src/scripts/sync-inventory-to-catalog.ts # Скрипт одноразовой синхронизации # GRAPHQL LAYER /src/graphql/resolvers/ ├── fulfillment-services-v2.ts # V2 услуги фулфилмента (расходники) ├── fulfillment-inventory-v2.ts # V2 складские остатки фулфилмента ├── fulfillment-consumables-v2.ts # V2 расходники фулфилмента ├── seller-inventory-v2.ts # V2 остатки селлеров ├── goods-supply-v2.ts # V2 товарные поставки └── seller-consumables-v2.ts # V2 расходники селлеров /src/graphql/queries/ ├── fulfillment-services-v2.ts # Запросы для услуг фулфилмента ├── seller-consumables-v2.ts # Запросы для расходников селлера └── goods-supply-v2.ts # Запросы для товарных поставок # UI LAYER /src/app/seller/create/ ├── goods/page.tsx # Создание товарных поставок └── consumables/page.tsx # Создание расходников /src/components/supplies/ ├── create-suppliers/ # Форма создания товарных поставок ├── create-consumables-supply-page.tsx # Форма создания расходников ├── supplies-dashboard.tsx # Дашборд поставок └── multilevel-supplies-table/ # Таблица поставок /src/components/services/ ├── supplies-tab.tsx # V2 таб расходников фулфилмента ├── services-tab.tsx # V2 таб услуг └── logistics-tab.tsx # V2 таб логистики ``` ### 🎨 UI АРХИТЕКТУРА #### **Модульная структура компонентов:** ```typescript // CreateSuppliersSupplyPage - главный компонент CreateSuppliersSupplyPage/ ├── blocks/ # UI блоки │ ├── SuppliersBlock.tsx # Выбор поставщика │ ├── ProductCardsBlock.tsx # Карточки товаров │ ├── DetailedCatalogBlock.tsx # Детальный каталог │ └── CartBlock.tsx # Корзина ├── hooks/ # Бизнес-логика │ ├── useSupplierSelection.ts # Логика выбора поставщика │ ├── useProductCatalog.ts # Управление каталогом │ ├── useRecipeBuilder.ts # Построение рецептуры │ └── useSupplyCart.ts # Логика корзины └── types/ # Типы TypeScript └── supply-creation.types.ts ``` #### **Корзина поставки:** ```typescript interface SupplyCartState { selectedGoods: GoodsItem[] // Товары в корзине productRecipes: Record // Рецептуры для каждого товара deliverySettings: DeliverySettings // Настройки доставки totalAmount: number // Общая сумма заказа isFormValid: boolean // Валидность формы isSubmitting: boolean // Состояние отправки } interface Recipe { selectedServices: string[] // ID услуг фулфилмента fulfillmentConsumables: ConsumableItem[] // Расходники фулфилмента sellerConsumables: ConsumableItem[] // Расходники селлера marketplaceCardId?: string // Карточка WB } ``` ### 🔧 РЕЗОЛВЕРЫ И API #### **V2 API Endpoints:** **Queries (чтение данных):** ```typescript // Для фулфилмента myFulfillmentServices // Услуги фулфилмента myFulfillmentConsumables // Расходники из каталога myFulfillmentLogistics // Логистические маршруты myFulfillmentSupplies // Складские остатки (V2) // Для селлера mySellerGoodsSupplies // Товарные поставки селлера mySellerConsumableSupplies // Поставки расходников селлера mySellerGoodsInventory // Остатки товаров на складах mySellerConsumableInventory // Остатки расходников // Для поставщика mySupplierGoodsOrders // Заказы на товары mySupplierConsumableOrders // Заказы на расходники // Универсальные counterpartySupplies // Каталог расходников партнеров partnersWithServices // Партнеры с их услугами ``` **Mutations (изменение данных):** ```typescript // Создание поставок createSellerGoodsSupply // Товарная поставка селлера createSellerConsumableSupply // Поставка расходников селлера createFulfillmentConsumableSupply // Заказ расходников фулфилментом // Обновление статусов updateSupplyOrderStatus // Универсальное обновление статуса receiveSellerGoodsSupply // Приемка товаров селлера receiveConsumableSupply // Приемка расходников // Управление каталогом updateFulfillmentConsumable // Цены и названия расходников updateFulfillmentService // Услуги фулфилмента updateFulfillmentLogistics // Логистические маршруты // Управление инвентарем updateInventoryStock // Ручная корректировка остатков transferInventoryBetweenWarehuses // Перемещение между складами ``` --- ## ⚡ ПРОИЗВОДИТЕЛЬНОСТЬ И ОПТИМИЗАЦИЯ ### 🚀 СТРАТЕГИИ ОПТИМИЗАЦИИ #### **1. Batch операции** ```typescript // Групповые обновления для больших поставок export async function batchUpdateInventory( operations: InventoryOperation[] ): Promise { const operations_grouped = groupBy(operations, 'fulfillmentCenterId') await Promise.all( Object.entries(operations_grouped).map(async ([fulfillmentId, ops]) => { return await prisma.$transaction(async (tx) => { for (const op of ops) { await updateInventorySingle(op, tx) } }) }) ) } ``` #### **2. Кеширование каталогов** ```typescript // Redis кеш для часто запрашиваемых каталогов export class CatalogCache { private redis = new Redis(process.env.REDIS_URL) async getCachedSupplies(fulfillmentId: string): Promise { const cacheKey = `supplies:${fulfillmentId}` const cached = await this.redis.get(cacheKey) if (cached) { return JSON.parse(cached) } const supplies = await this.fetchFromDB(fulfillmentId) await this.redis.setex(cacheKey, 300, JSON.stringify(supplies)) // 5 мин TTL return supplies } } ``` #### **3. Оптимистичные обновления UI** ```typescript // useOptimisticInventory.ts export function useOptimisticInventory() { const [optimisticUpdates, setOptimisticUpdates] = useState([]) const updateOptimistically = (inventoryId: string, stockDelta: number) => { setOptimisticUpdates(prev => [ ...prev, { inventoryId, stockDelta, timestamp: Date.now() } ]) // Откат через 5 секунд, если сервер не подтвердил setTimeout(() => revertOptimisticUpdate(inventoryId), 5000) } } ``` --- ## 🎨 UI/UX PATTERNS ### 🧩 КОМПОНЕНТНАЯ АРХИТЕКТУРА #### **1. Smart Components (Containers)** ```typescript // Содержат бизнес-логику и состояние export function SuppliesManagementPage() { const { supplies, loading, error } = useSuppliesQuery() const { updateStock, isUpdating } = useInventoryMutations() return ( ) } ``` #### **2. Presentation Components** ```typescript // Чистые UI компоненты без логики interface SuppliesTableProps { data: Supply[] loading: boolean onUpdateStock: (id: string, quantity: number) => void } export function SuppliesTable({ data, loading, onUpdateStock }: SuppliesTableProps) { // Только рендеринг, никакой бизнес-логики } ``` #### **3. Custom Hooks** ```typescript // Переиспользуемая бизнес-логика export function useSupplyCart() { const [cartItems, setCartItems] = useState([]) const [totalAmount, setTotalAmount] = useState(0) const addToCart = useCallback((item: GoodsItem) => { setCartItems(prev => [...prev, adaptGoodsToCartItem(item)]) }, []) const removeFromCart = useCallback((itemId: string) => { setCartItems(prev => prev.filter(item => item.id !== itemId)) }, []) return { cartItems, totalAmount, addToCart, removeFromCart, clearCart: () => setCartItems([]), isCartEmpty: cartItems.length === 0 } } ``` ### 🎯 UX PATTERNS #### **1. Progressive Disclosure** ```typescript // Постепенное раскрытие сложности Выбор поставщика {/* Простой выбор */} Выбор товаров {/* Средняя сложность */} Создание рецептуры {/* Высокая сложность */} Настройки доставки {/* Финализация */} ``` #### **2. Optimistic Updates** ```typescript // Мгновенная отзывчивость UI const handleStockUpdate = async (itemId: string, newStock: number) => { // 1. Мгновенно обновляем UI updateUIOptimistically(itemId, newStock) try { // 2. Отправляем запрос на сервер await updateInventoryStock({ itemId, newStock }) // 3. Подтверждаем успех confirmOptimisticUpdate(itemId) } catch (error) { // 4. Откатываем в случае ошибки revertOptimisticUpdate(itemId) showErrorToast('Не удалось обновить остаток') } } ``` #### **3. Real-time Feedback** ```typescript // Мгновенная обратная связь пользователю { updateField('currentStock', newValue) // Мгновенная валидация if (newValue < minStock) { showWarning('Остаток ниже минимального') } // Мгновенный расчет updateTotalAmount(calculateNewTotal(newValue)) }} validators={[ (value) => value >= 0 || 'Остаток не может быть отрицательным', (value) => value <= maxStock || 'Превышен максимальный остаток' ]} /> ``` --- ## ⚠️ ПРОБЛЕМЫ И РИСКИ ### 🚨 КРИТИЧЕСКИЕ ПРОБЛЕМЫ #### **1. Потенциальная рассинхронизация данных** ```typescript // ПРОБЛЕМА: Если автосинхронизация не сработает if (type === 'INCOMING') { try { await syncWithFulfillmentConsumable(inventory) } catch (syncError) { console.error('⚠️ Failed to sync:', syncError) // Данные могут рассинхрониться между V1 и V2 } } // РЕШЕНИЕ: Добавить систему retry и мониторинг const syncWithRetry = async (data, maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { await syncWithFulfillmentConsumable(data) return } catch (error) { if (attempt === maxRetries) { await logSyncFailure(data, error) throw error } await delay(1000 * attempt) // Exponential backoff } } } ``` #### **2. Дублирование бизнес-логики** ```typescript // ПРОБЛЕМА: Одинаковая логика в разных файлах // В supplies-dashboard.tsx const calculateTotalAmount = (items) => items.reduce((sum, item) => sum + item.price * item.quantity, 0) // В useSupplyCart.ts const calculateCartTotal = (cartItems) => cartItems.reduce((total, item) => total + (item.unitPrice * item.quantity), 0) // РЕШЕНИЕ: Выносить в utils // /src/lib/utils/calculations.ts export const calculateSupplyTotal = (items: SupplyItem[]) => { return items.reduce((sum, item) => sum + (item.unitPrice * item.quantity), 0) } ``` #### **3. Сложность отладки** ```typescript // ПРОБЛЕМА: Логи разбросаны по разным файлам и системам console.log('V1 Supply created') // в supplies-dashboard.tsx console.warn('V2 Inventory updated') // в inventory-management.ts console.error('GraphQL error') // в resolvers.ts // РЕШЕНИЕ: Централизованное логирование // /src/lib/logging/inventory-logger.ts export class InventoryLogger { static operation(system: 'V1' | 'V2', operation: string, data: any) { const logEntry = { timestamp: new Date().toISOString(), system, operation, data: JSON.stringify(data), sessionId: getCurrentSessionId(), userId: getCurrentUserId() } console.log('📦 INVENTORY:', logEntry) // Отправляем в внешние системы мониторинга if (process.env.NODE_ENV === 'production') { sendToLogAggregator(logEntry) } } } ``` ### ⚡ ПРОИЗВОДИТЕЛЬНОСТЬ #### **1. N+1 queries проблема** ```typescript // ПРОБЛЕМА: Запросы в цикле const supplies = await prisma.supply.findMany() for (const supply of supplies) { supply.organization = await prisma.organization.findUnique({ where: { id: supply.organizationId } }) } // РЕШЕНИЕ: Include relations const supplies = await prisma.supply.findMany({ include: { organization: true, items: { include: { product: true } } } }) ``` #### **2. Большие формы с множественными полями** ```typescript // ПРОБЛЕМА: Частые ререндеры при изменении формы const [formData, setFormData] = useState(initialFormData) // Каждое изменение поля вызывает ререндер всей формы const updateField = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })) } // РЕШЕНИЕ: React Hook Form с оптимизациями const form = useForm({ defaultValues: initialFormData, mode: 'onChange', reValidateMode: 'onChange' }) // Регистрация полей без ререндера родительского компонента ``` --- ## 🛠️ РЕКОМЕНДАЦИИ ### 🎯 КРАТКОСРОЧНЫЕ (1-2 недели) #### **1. Система мониторинга синхронизации** ```typescript // /src/lib/monitoring/sync-monitor.ts export class SyncMonitor { static async checkSyncHealth(): Promise { const v1Count = await prisma.supply.count() const v2InventoryCount = await prisma.fulfillmentConsumableInventory.count() const v2CatalogCount = await prisma.fulfillmentConsumable.count() const syncDrift = Math.abs(v2InventoryCount - v2CatalogCount) return { isHealthy: syncDrift < 5, // Допустимое расхождение v1Records: v1Count, v2InventoryRecords: v2InventoryCount, v2CatalogRecords: v2CatalogCount, syncDrift, lastSyncCheck: new Date() } } } ``` #### **2. Унификация API для фронтенда** ```typescript // /src/lib/api/unified-inventory.ts export class UnifiedInventoryAPI { // Единый интерфейс для всех типов инвентаря static async getInventory( organizationType: OrganizationType, organizationId: string, filters: InventoryFilters ): Promise { switch (organizationType) { case 'FULFILLMENT': return await this.getFulfillmentInventory(organizationId, filters) case 'SELLER': return await this.getSellerInventory(organizationId, filters) default: throw new Error(`Unsupported organization type: ${organizationType}`) } } } ``` #### **3. Валидация данных на всех уровнях** ```typescript // /src/lib/validation/inventory-validation.ts export const inventoryValidationSchema = z.object({ productId: z.string().cuid(), quantity: z.number().min(0), unitCost: z.number().min(0).optional(), type: z.enum(['INCOMING', 'OUTGOING']), fulfillmentCenterId: z.string().cuid() }) export function validateInventoryOperation(data: unknown): InventoryOperation { return inventoryValidationSchema.parse(data) } ``` ### 🚀 СРЕДНЕСРОЧНЫЕ (1-2 месяца) #### **1. Микросервисная декомпозиция** ```typescript // Выделение инвентаря в отдельный сервис class InventoryService { async updateStock(operation: StockOperation): Promise async getAnalytics(filters: AnalyticsFilters): Promise async validateOperation(operation: StockOperation): Promise async syncWithExternalSystems(data: SyncData): Promise } ``` #### **2. Event-driven архитектура** ```typescript // Асинхронная обработка через события export const inventoryEvents = { STOCK_UPDATED: 'inventory:stock-updated', PRICE_CHANGED: 'inventory:price-changed', LOW_STOCK_ALERT: 'inventory:low-stock-alert', SYNC_COMPLETED: 'inventory:sync-completed' } // Event handlers EventBus.on(inventoryEvents.STOCK_UPDATED, async (data) => { await updateRelatedSystems(data) await triggerAnalyticsRecalculation(data.productId) await checkLowStockAlerts(data.fulfillmentCenterId) }) ``` ### 🏗️ ДОЛГОСРОЧНЫЕ (3-6 месяцев) #### **1. Real-time синхронизация** ```typescript // WebSocket интеграция для мгновенных обновлений export class InventoryWebSocket { static notify(event: InventoryEvent) { const channels = [ `fulfillment:${event.fulfillmentId}`, `seller:${event.sellerId}`, `admin:global` ] channels.forEach(channel => { webSocketManager.emit(channel, { type: event.type, data: event.data, timestamp: new Date().toISOString() }) }) } } ``` #### **2. Интеграция с внешними системами** ```typescript // 1С/SAP интеграция export class ERPIntegration { async syncInventoryToERP(inventoryData: InventorySnapshot): Promise async importSuppliersFromERP(): Promise async exportTransactionsToAccounting(period: DateRange): Promise } // Маркетплейс интеграция export class MarketplaceSync { async updateWildberriesStock(productId: string, newStock: number): Promise async importOzonOrders(): Promise async syncPricesAcrossMarketplaces(): Promise } ``` --- ## 📈 МЕТРИКИ И АНАЛИТИКА ### 📊 KPI СИСТЕМЫ ИНВЕНТАРЯ ```typescript interface InventoryKPI { // Основные метрики totalProducts: number // Общее количество SKU totalStockValue: number // Общая стоимость остатков avgTurnoverDays: number // Средняя оборачиваемость lowStockItemsCount: number // Товаров с низким остатком // Эффективность stockoutFrequency: number // Частота дефицита overstockValue: number // Стоимость излишков warehouseUtilization: number // Заполненность складов // Финансовые avgMarginPercent: number // Средняя маржинальность inventoryROI: number // ROI по инвентарю costOfGoods: number // Себестоимость товаров } ``` --- ## 🎉 ЗАКЛЮЧЕНИЕ Система инвентаря SFERA представляет собой **сложную, но хорошо структурированную архитектуру** с активной миграцией с V1 на V2. **Ключевые достижения:** - ✅ Модульная архитектура с четким разделением ответственности - ✅ Автосинхронизация между системами V1 и V2 - ✅ Типобезопасная GraphQL схема - ✅ Производительные UI компоненты с оптимистичными обновлениями **Основные вызовы:** - ⚠️ Переходный период с дублированием функционала - ⚠️ Сложность отладки из-за множественных систем - ⚠️ Потенциальная рассинхронизация данных **Следующие шаги:** 1. Завершить миграцию всех компонентов на V2 2. Добавить мониторинг синхронизации 3. Оптимизировать производительность запросов 4. Интегрировать с внешними системами **📊 Статистика системы:** - **Моделей Prisma:** 12+ (инвентарь + связанные) - **GraphQL резолверов:** 45+ - **UI компонентов:** 25+ - **API endpoints:** 30+ - **Бизнес-процессов:** 8 основных - **Статусов поставок:** 13 различных