
- 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>
287 lines
10 KiB
Markdown
287 lines
10 KiB
Markdown
# 🔄 ПРАВИЛА СИНХРОНИЗАЦИИ ДАННЫХ МЕЖДУ КОМПОНЕНТАМИ
|
||
|
||
> **Цель:** Обеспечить консистентность данных между различными компонентами системы 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('Обнаружена рассинхронизация данных')
|
||
}
|
||
```
|
||
|
||
**Следование этим правилам обеспечит надёжную синхронизацию данных между всеми компонентами системы!** 🚀
|