# 🔄 ПРАВИЛА СИНХРОНИЗАЦИИ ДАННЫХ МЕЖДУ КОМПОНЕНТАМИ > **Цель:** Обеспечить консистентность данных между различными компонентами системы SFERA ## 📋 **ОСНОВНЫЕ ПРИНЦИПЫ** ### 1. **ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ (Single Source of Truth)** - ✅ **Один резолвер = одна таблица БД** для одного типа данных - ✅ **V2 система** - приоритет над legacy кодом - ❌ **Никогда не дублируйте** одни данные в разных резолверах ### 2. **КОНСИСТЕНТНАЯ CACHE POLICY** ```typescript // ✅ ПРАВИЛЬНО - одинаковая policy для связанных данных const query1 = useQuery(QUERY_A, { fetchPolicy: 'cache-and-network' }) const query2 = useQuery(QUERY_B, { fetchPolicy: 'cache-and-network' }) // ❌ НЕПРАВИЛЬНО - разные policies создают рассинхронизацию const query1 = useQuery(QUERY_A, { fetchPolicy: 'cache-and-network' }) const query2 = useQuery(QUERY_B, {}) // default cache-first policy ``` ### 3. **ОБЯЗАТЕЛЬНЫЕ FETCH POLICIES** #### **Для связанных компонентов:** ```typescript fetchPolicy: 'cache-and-network', // Всегда актуальные данные pollInterval: 30000, // Автообновление каждые 30 сек ``` #### **Для dashboard/статистики:** ```typescript fetchPolicy: 'cache-and-network', // Синхронизация с основными данными pollInterval: 30000, // Регулярное обновление errorPolicy: 'all', // Показывать частичные данные при ошибках ``` --- ## 🏢 **ПРАВИЛА ДЛЯ ФУЛФИЛМЕНТА** ### **ТАБЛИЦЫ V2 СИСТЕМЫ** - **`fulfillmentConsumableInventory`** - складские остатки расходников - **`fulfillmentConsumableSupplyOrder`** - поставки расходников - **`fulfillmentInventoryV2`** - общий инвентарь фулфилмента ### **СВЯЗАННЫЕ КОМПОНЕНТЫ** ```typescript // Все эти компоненты должны использовать одинаковые данные: 1. Главный dashboard склада (/fulfillment-warehouse) - Карточка "РАСХОДНИКИ ФУЛФИЛМЕНТА" 2. Подраздел расходников (/fulfillment-warehouse/supplies) - Карточка "ОСТАТОК" - Главная таблица поставок 3. Раздел услуг (/services) - Вкладка "Расходники" 4. История поставок - Раскрывающиеся детали каждого товара ``` ### **СИНХРОНИЗИРОВАННЫЕ ПОЛЯ** ```typescript interface SynchronizedData { currentStock: number // Текущий остаток (одинаковый везде) totalReceived: number // Общее количество поступлений productId: string // ID для группировки истории поставок fulfillmentCenterId: string // ID фулфилмент-центра } ``` --- ## 📊 **ПРАВИЛА ДЛЯ СТАТИСТИЧЕСКИХ КОМПОНЕНТОВ** ### **1. АРХИТЕКТУРА MASTER-DETAIL** ```typescript // MASTER компонент (главный раздел) const masterStats = useQuery(GET_WAREHOUSE_STATS, { fetchPolicy: 'cache-and-network', pollInterval: 30000, }) // DETAIL компонент (подраздел) const detailStats = useQuery(GET_DETAILED_SUPPLIES, { fetchPolicy: 'cache-and-network', // ← ОБЯЗАТЕЛЬНО то же самое! pollInterval: 30000, // ← ОБЯЗАТЕЛЬНО то же самое! }) // Вычисление должно давать одинаковые результаты const masterValue = masterStats.data?.fulfillmentSupplies?.current const detailValue = detailStats.data?.supplies?.reduce((sum, s) => sum + s.currentStock, 0) // masterValue === detailValue ← ОБЯЗАТЕЛЬНО! ``` ### **2. ПРАВИЛА АГРЕГАЦИИ** ```typescript // ✅ ПРАВИЛЬНО - агрегация из одного источника данных const totalStock = inventoryItems.reduce((sum, item) => sum + item.currentStock, 0) // ❌ НЕПРАВИЛЬНО - агрегация из разных источников const totalStock1 = supplies.reduce((sum, s) => sum + s.currentStock, 0) // источник A const totalStock2 = inventory.reduce((sum, i) => sum + i.remainingStock, 0) // источник B ``` --- ## 🔗 **ПРАВИЛА СВЯЗЫВАНИЯ ДАННЫХ** ### **ОБЯЗАТЕЛЬНЫЕ ПОЛЯ ДЛЯ СВЯЗИ** ```typescript // В GraphQL схемах ОБЯЗАТЕЛЬНО указывать связующие поля: type Supply { id: ID! productId: ID! // ← Для фильтрации истории поставок fulfillmentCenterId: ID // ← Для привязки к фулфилменту } type SupplyOrder { id: ID! fulfillmentCenterId: ID! // ← Для фильтрации по фулфилменту items: [SupplyOrderItem!]! } type SupplyOrderItem { productId: ID! // ← Для связи с inventory requestedQuantity: Int! receivedQuantity: Int! // ← Для подсчёта остатков } ``` ### **ФИЛЬТРАЦИЯ ПО СВЯЗАННЫМ ДАННЫМ** ```typescript // ✅ ПРАВИЛЬНО - фильтрация по ID const getSupplyHistory = (supply: Supply) => { return allDeliveries.filter((delivery) => delivery.items?.some((item) => item.productId === supply.productId)) } // ❌ НЕПРАВИЛЬНО - фильтрация по названию (может быть дубли) const getSupplyHistory = (supply: Supply) => { return allDeliveries.filter((delivery) => delivery.name === supply.name && delivery.category === supply.category) } ``` --- ## ⚡ **ПРАВИЛА REAL-TIME ОБНОВЛЕНИЙ** ### **АВТОМАТИЧЕСКАЯ СИНХРОНИЗАЦИЯ** ```typescript // Компоненты должны обновляться автоматически при изменениях // 1. При приёме поставки на склад prisma.fulfillmentConsumableInventory.update({ where: { id: itemId }, data: { currentStock: newStock }, }) // → Автоматически обновятся: статистика, таблица, услуги // 2. При отгрузке товаров prisma.fulfillmentConsumableInventory.update({ where: { id: itemId }, data: { currentStock: { decrement: shippedQuantity } }, }) // → Автоматически обновятся все связанные компоненты ``` ### **POLLING ИНТЕРВАЛЫ** ```typescript pollInterval: 30000 // 30 сек - для статистики и dashboard pollInterval: 60000 // 1 мин - для списков и таблиц pollInterval: 120000 // 2 мин - для отчётов и архивных данных ``` --- ## 🚨 **АНТИ-ПАТТЕРНЫ И ТИПИЧНЫЕ ОШИБКИ** ### **❌ НИКОГДА НЕ ДЕЛАЙТЕ:** #### **1. Разные источники для одних данных** ```typescript // ❌ ПЛОХО const stats = useQuery(GET_OLD_SUPPLIES) // legacy таблица const details = useQuery(GET_NEW_SUPPLIES) // V2 таблица ``` #### **2. Разные cache policies для связанных данных** ```typescript // ❌ ПЛОХО const master = useQuery(QUERY_A, { fetchPolicy: 'cache-and-network' }) const detail = useQuery(QUERY_B, {}) // default cache-first ``` #### **3. Ручная синхронизация через состояние** ```typescript // ❌ ПЛОХО const [manualSync, setManualSync] = useState(0) useEffect(() => { // Попытка ручной синхронизации через state }, [manualSync]) ``` #### **4. Группировка по нестабильным полям** ```typescript // ❌ ПЛОХО - названия могут изменяться supplies.filter((s) => s.name === targetName) // ✅ ХОРОШО - ID стабильны supplies.filter((s) => s.productId === targetProductId) ``` --- ## 🎯 **ЧЕКЛИСТ СИНХРОНИЗАЦИИ** ### **При создании связанных компонентов:** - [ ] Используют одну таблицу БД как источник данных - [ ] Одинаковые `fetchPolicy` настройки - [ ] Одинаковые `pollInterval` значения - [ ] Связь через стабильные ID поля - [ ] Одинаковая логика агрегации/фильтрации - [ ] Обработка ошибок и loading состояний ### **При тестировании синхронизации:** - [ ] Изменение данных обновляет все связанные компоненты - [ ] Значения в master и detail компонентах совпадают - [ ] Нет задержек между обновлениями (< 30 сек) - [ ] Ошибки в одном компоненте не ломают другие --- ## 📈 **МОНИТОРИНГ СИНХРОНИЗАЦИИ** ### **DEBUG ЛОГИ** ```typescript // Добавляйте логи для контроля синхронизации console.warn('SYNC CHECK:', { masterValue: masterData?.totalStock, detailValue: detailData?.reduce(sum, 0), timestamp: new Date().toISOString(), source: 'fulfillmentInventorySync', }) ``` ### **УВЕДОМЛЕНИЯ О РАССИНХРОНИЗАЦИИ** ```typescript // Проверка консистентности данных if (Math.abs(masterValue - detailValue) > 0) { console.error('🚨 DATA SYNCHRONIZATION ERROR', { master: masterValue, detail: detailValue, component: 'FulfillmentStats', }) // Отправка уведомления разработчикам toast.error('Обнаружена рассинхронизация данных') } ``` **Следование этим правилам обеспечит надёжную синхронизацию данных между всеми компонентами системы!** 🚀