Files
sfera-new/docs/presentation-layer/DATA_SYNCHRONIZATION_RULES.md
Veronika Smirnova 121a4dece1 docs: создать правила для синхронизации данных, layout и статистических компонентов
- DATA_SYNCHRONIZATION_RULES.md - правила синхронизации между компонентами
- GRAPHQL_CACHE_RULES.md - настройки кеширования и fetchPolicy
- CSS_LAYOUT_SCROLL_RULES.md - решение проблем с overflow и scroll
- STATISTICAL_COMPONENTS_RULES.md - правила Master-Detail архитектуры

Документация основана на исправлениях в кабинете фулфилмента

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 12:29:00 +03:00

10 KiB
Raw Blame History

🔄 ПРАВИЛА СИНХРОНИЗАЦИИ ДАННЫХ МЕЖДУ КОМПОНЕНТАМИ

Цель: Обеспечить консистентность данных между различными компонентами системы SFERA

📋 ОСНОВНЫЕ ПРИНЦИПЫ

1. ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ (Single Source of Truth)

  • Один резолвер = одна таблица БД для одного типа данных
  • V2 система - приоритет над legacy кодом
  • Никогда не дублируйте одни данные в разных резолверах

2. КОНСИСТЕНТНАЯ CACHE POLICY

// ✅ ПРАВИЛЬНО - одинаковая 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

Для связанных компонентов:

fetchPolicy: 'cache-and-network', // Всегда актуальные данные
pollInterval: 30000,              // Автообновление каждые 30 сек

Для dashboard/статистики:

fetchPolicy: 'cache-and-network', // Синхронизация с основными данными
pollInterval: 30000,              // Регулярное обновление
errorPolicy: 'all',               // Показывать частичные данные при ошибках

🏢 ПРАВИЛА ДЛЯ ФУЛФИЛМЕНТА

ТАБЛИЦЫ V2 СИСТЕМЫ

  • fulfillmentConsumableInventory - складские остатки расходников
  • fulfillmentConsumableSupplyOrder - поставки расходников
  • fulfillmentInventoryV2 - общий инвентарь фулфилмента

СВЯЗАННЫЕ КОМПОНЕНТЫ

// Все эти компоненты должны использовать одинаковые данные:

1. Главный dashboard склада (/fulfillment-warehouse)
   - Карточка "РАСХОДНИКИ ФУЛФИЛМЕНТА"

2. Подраздел расходников (/fulfillment-warehouse/supplies)
   - Карточка "ОСТАТОК"
   - Главная таблица поставок

3. Раздел услуг (/services)
   - Вкладка "Расходники"

4. История поставок
   - Раскрывающиеся детали каждого товара

СИНХРОНИЗИРОВАННЫЕ ПОЛЯ

interface SynchronizedData {
  currentStock: number // Текущий остаток (одинаковый везде)
  totalReceived: number // Общее количество поступлений
  productId: string // ID для группировки истории поставок
  fulfillmentCenterId: string // ID фулфилмент-центра
}

📊 ПРАВИЛА ДЛЯ СТАТИСТИЧЕСКИХ КОМПОНЕНТОВ

1. АРХИТЕКТУРА MASTER-DETAIL

// 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. ПРАВИЛА АГРЕГАЦИИ

// ✅ ПРАВИЛЬНО - агрегация из одного источника данных
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

🔗 ПРАВИЛА СВЯЗЫВАНИЯ ДАННЫХ

ОБЯЗАТЕЛЬНЫЕ ПОЛЯ ДЛЯ СВЯЗИ

// В 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!   // ← Для подсчёта остатков
}

ФИЛЬТРАЦИЯ ПО СВЯЗАННЫМ ДАННЫМ

// ✅ ПРАВИЛЬНО - фильтрация по 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 ОБНОВЛЕНИЙ

АВТОМАТИЧЕСКАЯ СИНХРОНИЗАЦИЯ

// Компоненты должны обновляться автоматически при изменениях

// 1. При приёме поставки на склад
prisma.fulfillmentConsumableInventory.update({
  where: { id: itemId },
  data: { currentStock: newStock },
})
// → Автоматически обновятся: статистика, таблица, услуги

// 2. При отгрузке товаров
prisma.fulfillmentConsumableInventory.update({
  where: { id: itemId },
  data: { currentStock: { decrement: shippedQuantity } },
})
// → Автоматически обновятся все связанные компоненты

POLLING ИНТЕРВАЛЫ

pollInterval: 30000 // 30 сек - для статистики и dashboard
pollInterval: 60000 // 1 мин - для списков и таблиц
pollInterval: 120000 // 2 мин - для отчётов и архивных данных

🚨 АНТИ-ПАТТЕРНЫ И ТИПИЧНЫЕ ОШИБКИ

НИКОГДА НЕ ДЕЛАЙТЕ:

1. Разные источники для одних данных

// ❌ ПЛОХО
const stats = useQuery(GET_OLD_SUPPLIES) // legacy таблица
const details = useQuery(GET_NEW_SUPPLIES) // V2 таблица

2. Разные cache policies для связанных данных

// ❌ ПЛОХО
const master = useQuery(QUERY_A, { fetchPolicy: 'cache-and-network' })
const detail = useQuery(QUERY_B, {}) // default cache-first

3. Ручная синхронизация через состояние

// ❌ ПЛОХО
const [manualSync, setManualSync] = useState(0)
useEffect(() => {
  // Попытка ручной синхронизации через state
}, [manualSync])

4. Группировка по нестабильным полям

// ❌ ПЛОХО - названия могут изменяться
supplies.filter((s) => s.name === targetName)

// ✅ ХОРОШО - ID стабильны
supplies.filter((s) => s.productId === targetProductId)

🎯 ЧЕКЛИСТ СИНХРОНИЗАЦИИ

При создании связанных компонентов:

  • Используют одну таблицу БД как источник данных
  • Одинаковые fetchPolicy настройки
  • Одинаковые pollInterval значения
  • Связь через стабильные ID поля
  • Одинаковая логика агрегации/фильтрации
  • Обработка ошибок и loading состояний

При тестировании синхронизации:

  • Изменение данных обновляет все связанные компоненты
  • Значения в master и detail компонентах совпадают
  • Нет задержек между обновлениями (< 30 сек)
  • Ошибки в одном компоненте не ломают другие

📈 МОНИТОРИНГ СИНХРОНИЗАЦИИ

DEBUG ЛОГИ

// Добавляйте логи для контроля синхронизации
console.warn('SYNC CHECK:', {
  masterValue: masterData?.totalStock,
  detailValue: detailData?.reduce(sum, 0),
  timestamp: new Date().toISOString(),
  source: 'fulfillmentInventorySync',
})

УВЕДОМЛЕНИЯ О РАССИНХРОНИЗАЦИИ

// Проверка консистентности данных
if (Math.abs(masterValue - detailValue) > 0) {
  console.error('🚨 DATA SYNCHRONIZATION ERROR', {
    master: masterValue,
    detail: detailValue,
    component: 'FulfillmentStats',
  })

  // Отправка уведомления разработчикам
  toast.error('Обнаружена рассинхронизация данных')
}

Следование этим правилам обеспечит надёжную синхронизацию данных между всеми компонентами системы! 🚀