#!/usr/bin/env tsx /** * СКРИПТ СИНХРОНИЗАЦИИ СИСТЕМА B → СИСТЕМА A * * Синхронизирует существующие данные из FulfillmentConsumableInventory * в FulfillmentConsumable для обеспечения доступности расходников в каталоге */ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() interface SyncStats { totalInventoryItems: number existingCatalogItems: number createdCatalogItems: number updatedCatalogItems: number errors: string[] } const SYNC_LOG_PREFIX = '[INVENTORY→CATALOG SYNC]' /** * Главная функция синхронизации */ async function syncInventoryToCatalog(dryRun = true): Promise { const stats: SyncStats = { totalInventoryItems: 0, existingCatalogItems: 0, createdCatalogItems: 0, updatedCatalogItems: 0, errors: [], } console.warn(`${SYNC_LOG_PREFIX} Starting sync (DRY RUN: ${dryRun})`) console.warn(`${SYNC_LOG_PREFIX} Timestamp: ${new Date().toISOString()}`) try { // Получаем все записи из Системы B (склад) const inventoryItems = await prisma.fulfillmentConsumableInventory.findMany({ include: { product: true, fulfillmentCenter: true, }, orderBy: { createdAt: 'asc', }, }) stats.totalInventoryItems = inventoryItems.length console.warn(`${SYNC_LOG_PREFIX} Found ${stats.totalInventoryItems} inventory items`) if (stats.totalInventoryItems === 0) { console.warn(`${SYNC_LOG_PREFIX} No inventory items to sync`) return stats } // Синхронизируем каждую запись for (const inventoryItem of inventoryItems) { try { await syncInventoryItem(inventoryItem, stats, dryRun) // Логируем прогресс каждые 5 записей if ((stats.createdCatalogItems + stats.updatedCatalogItems) % 5 === 0) { console.warn(`${SYNC_LOG_PREFIX} Progress: ${stats.createdCatalogItems + stats.updatedCatalogItems}/${inventoryItems.length}`) } } catch (error) { const errorMsg = `Failed to sync inventory item ${inventoryItem.id}: ${error}` stats.errors.push(errorMsg) console.error(`${SYNC_LOG_PREFIX} ERROR: ${errorMsg}`) } } } catch (error) { const errorMsg = `Sync failed: ${error}` stats.errors.push(errorMsg) console.error(`${SYNC_LOG_PREFIX} CRITICAL ERROR: ${errorMsg}`) } // Финальная отчетность printSyncReport(stats, dryRun) return stats } /** * Синхронизирует одну запись из склада в каталог */ async function syncInventoryItem(inventoryItem: any, stats: SyncStats, dryRun: boolean): Promise { const fulfillmentCenterId = inventoryItem.fulfillmentCenterId const productName = inventoryItem.product.name // Проверяем существует ли запись в каталоге const existingCatalogItem = await prisma.fulfillmentConsumable.findFirst({ where: { fulfillmentId: fulfillmentCenterId, name: productName, }, }) if (existingCatalogItem) { stats.existingCatalogItems++ if (dryRun) { console.warn(`${SYNC_LOG_PREFIX} [DRY RUN] Would update: ${productName} ` + `(stock: ${inventoryItem.currentStock})`) return } // Обновляем существующую запись в каталоге await prisma.fulfillmentConsumable.update({ where: { id: existingCatalogItem.id }, data: { currentStock: inventoryItem.currentStock, minStock: inventoryItem.minStock, isAvailable: inventoryItem.currentStock > 0, inventoryId: inventoryItem.id, updatedAt: new Date(), }, }) stats.updatedCatalogItems++ console.warn(`${SYNC_LOG_PREFIX} ✅ Updated catalog: ${productName}`) } else { if (dryRun) { console.warn(`${SYNC_LOG_PREFIX} [DRY RUN] Would create: ${productName} ` + `(stock: ${inventoryItem.currentStock})`) return } // Создаем новую запись в каталоге await prisma.fulfillmentConsumable.create({ data: { fulfillmentId: fulfillmentCenterId, inventoryId: inventoryItem.id, name: inventoryItem.product.name, article: inventoryItem.product.article || '', pricePerUnit: 0, // Цену фулфилмент устанавливает вручную unit: inventoryItem.product.unit || 'шт', minStock: inventoryItem.minStock, currentStock: inventoryItem.currentStock, isAvailable: inventoryItem.currentStock > 0, imageUrl: inventoryItem.product.imageUrl, sortOrder: 0, }, }) stats.createdCatalogItems++ console.warn(`${SYNC_LOG_PREFIX} ✅ Created catalog: ${productName}`) } } /** * Печатает детальный отчет о синхронизации */ function printSyncReport(stats: SyncStats, dryRun: boolean): void { console.log('\n' + '='.repeat(60)) console.warn(`${SYNC_LOG_PREFIX} SYNC REPORT`) console.log('='.repeat(60)) console.warn(`Mode: ${dryRun ? 'DRY RUN' : 'PRODUCTION'}`) console.warn(`Timestamp: ${new Date().toISOString()}`) console.log('') console.log('📊 STATISTICS:') console.warn(` Inventory items found: ${stats.totalInventoryItems}`) console.warn(` Existing catalog items: ${stats.existingCatalogItems}`) console.warn(` Created catalog items: ${stats.createdCatalogItems}`) console.warn(` Updated catalog items: ${stats.updatedCatalogItems}`) console.warn(` Errors encountered: ${stats.errors.length}`) console.log('') if (stats.errors.length > 0) { console.log('❌ ERRORS:') stats.errors.forEach((error, index) => { console.warn(` ${index + 1}. ${error}`) }) console.log('') } if (dryRun) { console.log('🔄 TO RUN ACTUAL SYNC:') console.log(' node scripts/sync-inventory-to-catalog.ts --production') } else { console.log('✅ SYNC COMPLETED!') console.log('🔄 TO VERIFY RESULTS:') console.log(' Check FulfillmentConsumable table') console.log(' Refresh http://localhost:3000/fulfillment/services/consumables') } console.log('='.repeat(60)) } /** * CLI интерфейс */ async function main() { const args = process.argv.slice(2) const isProduction = args.includes('--production') const dryRun = !isProduction if (dryRun) { console.log('🔍 Running in DRY RUN mode (no actual changes)') console.log('📝 Add --production flag to run actual sync') } else { console.log('⚠️ Running in PRODUCTION mode (will make changes)') console.log('Press Ctrl+C to cancel...') // Ждем 3 секунды в production режиме await new Promise(resolve => setTimeout(resolve, 3000)) } try { const stats = await syncInventoryToCatalog(dryRun) if (stats.errors.length > 0) { console.error(`${SYNC_LOG_PREFIX} Sync completed with errors`) process.exit(1) } console.warn(`${SYNC_LOG_PREFIX} Sync completed successfully`) process.exit(0) } catch (error) { console.error(`${SYNC_LOG_PREFIX} Sync failed:`, error) process.exit(1) } finally { await prisma.$disconnect() } } // Запускаем только если скрипт вызван напрямую if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error) } export { syncInventoryToCatalog }