import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ristoris/core/constants.dart'; import 'package:ristoris/database/database.dart'; import '../services/api_service.dart'; import '../services/sync/agente_sync_service.dart'; import '../services/sync/cliente_sync_service.dart'; import '../services/sync/ordine_sync_service.dart'; import '../services/sync/filiale_sync_service.dart'; import '../services/sync/reference_sync_service.dart'; import '../services/image_service.dart'; import 'article_provider.dart'; import 'auth_provider.dart'; import 'customer_provider.dart'; import 'dashboard_provider.dart'; import 'database_provider.dart'; import 'order_provider.dart'; import '../ui/pages/payments/payments_page.dart'; final apiServiceProvider = Provider((ref) { final parametriDao = ref.read(parametriDaoProvider); return ApiService(parametriDao); }); final imageServiceProvider = Provider((ref) { final parametriDao = ref.read(parametriDaoProvider); return ImageService(parametriDao); }); final agenteSyncServiceProvider = Provider((ref) { final agentiDao = ref.read(agentiDaoProvider); return AgenteSyncService(ref.read(apiServiceProvider), agentiDao); }); final clienteSyncServiceProvider = Provider((ref) { final api = ref.read(apiServiceProvider); final clientiDao = ref.read(clientiDaoProvider); final clientiAgenteDao = ref.read(clientiAgenteDaoProvider); return ClienteSyncService(api, clientiDao, clientiAgenteDao); }); final ordineSyncServiceProvider = Provider((ref) { return OrdineSyncService( ref.read(apiServiceProvider), ref.read(ordiniDaoProvider), ref.read(clientiDaoProvider), ref.read(filialiDaoProvider), ref.read(agentiDaoProvider), ); }); final filialeSyncServiceProvider = Provider((ref) { return FilialeSyncService( ref.read(apiServiceProvider), ref.read(filialiDaoProvider), ref.read(clientiDaoProvider), ); }); final referenceSyncServiceProvider = Provider((ref) { return ReferenceSyncService( ref.read(apiServiceProvider), ref.read(tabelleDaoProvider), ref.read(pagamentiDaoProvider), ref.read(scontiDaoProvider), ref.read(articoliDaoProvider), ref.read(listiniDaoProvider), ref.read(promozioniDaoProvider), ref.read(scadenzeDaoProvider), ref.read(statisticheDaoProvider), ref.read(parametriDaoProvider), ref.read(clientiDaoProvider), ); }); enum SyncPhaseStatus { waiting, downloading, processing, completed } class SyncPhaseInfo { final String name; final SyncPhaseStatus status; final int? currentRecord; final int? totalRecords; const SyncPhaseInfo({ required this.name, this.status = SyncPhaseStatus.waiting, this.currentRecord, this.totalRecords, }); SyncPhaseInfo copyWith({ SyncPhaseStatus? status, int? currentRecord, int? totalRecords, }) { return SyncPhaseInfo( name: name, status: status ?? this.status, currentRecord: currentRecord ?? this.currentRecord, totalRecords: totalRecords ?? this.totalRecords, ); } String get statusText { switch (status) { case SyncPhaseStatus.waiting: return 'IN ATTESA'; case SyncPhaseStatus.downloading: return 'IN SCARICAMENTO'; case SyncPhaseStatus.processing: if (currentRecord != null && totalRecords != null) { return 'Elaborazione $currentRecord di $totalRecords'; } return 'IN ELABORAZIONE'; case SyncPhaseStatus.completed: return 'COMPLETATO'; } } } const List syncPhaseNames = [ 'Invio nuovi clienti', 'Invio nuove filiali', 'Invio nuovi ordini', 'Scaricamento tabelle', 'Scaricamento pagamenti', 'Scaricamento sconti', 'Scaricamento articoli', 'Scaricamento listini', 'Scaricamento listini articolo', 'Scaricamento promozioni', 'Scaricamento clienti', 'Scaricamento filiali', 'Scaricamento scadenze', 'Scaricamento listini cliente', 'Scaricamento sconti cliente', 'Scaricamento statistiche', ]; class SyncState { final bool isSyncing; final double progress; final String message; final String? error; final List phases; final DateTime? lastSyncTime; final String? currentAgentCode; final List agentiCodici; const SyncState({ this.isSyncing = false, this.progress = 0, this.message = '', this.error, this.phases = const [], this.lastSyncTime, this.currentAgentCode, this.agentiCodici = const [], }); } class SyncNotifier extends Notifier { DateTime _lastProgressUpdate = DateTime.fromMillisecondsSinceEpoch(0); @override SyncState build() { Future.microtask(() async { try { final parametriDao = ref.read(parametriDaoProvider); final lastSyncTimeStr = await parametriDao.read(AppConstants.kLastUpdateParam); if (lastSyncTimeStr != null) { final lastSyncTime = DateTime.tryParse(lastSyncTimeStr); if (lastSyncTime != null) { state = SyncState( phases: state.phases, lastSyncTime: lastSyncTime, ); } } } catch (_) {} }); return const SyncState(); } List _initPhases() { return syncPhaseNames.map((name) => SyncPhaseInfo(name: name)).toList(); } void _setPhase( int index, SyncPhaseStatus status, { int? currentRecord, int? totalRecords, }) { final phases = List.from(state.phases); phases[index] = phases[index].copyWith( status: status, currentRecord: currentRecord, totalRecords: totalRecords, ); final progress = (index + 1) / syncPhaseNames.length; state = SyncState( isSyncing: true, progress: progress, phases: phases, lastSyncTime: state.lastSyncTime, currentAgentCode: state.currentAgentCode, agentiCodici: state.agentiCodici, ); } void _resetAgentPhases() { final phases = List.from(state.phases); for (int i = 10; i <= 14; i++) { phases[i] = SyncPhaseInfo(name: syncPhaseNames[i]); } state = SyncState( isSyncing: true, progress: state.progress, phases: phases, lastSyncTime: state.lastSyncTime, currentAgentCode: state.currentAgentCode, agentiCodici: state.agentiCodici, ); } void _setCurrentAgent(String codAgente) { state = SyncState( isSyncing: true, progress: state.progress, phases: state.phases, lastSyncTime: state.lastSyncTime, currentAgentCode: codAgente, agentiCodici: state.agentiCodici, ); } void Function(int current, int total) _throttledProgress(int phaseIndex) { return (int current, int total) { final now = DateTime.now(); if (now.difference(_lastProgressUpdate).inMilliseconds >= 500) { _lastProgressUpdate = now; _setPhase( phaseIndex, SyncPhaseStatus.processing, currentRecord: current, totalRecords: total, ); } }; } Future startFullSync( int idAgente, String codAgente, { bool forceFullSync = false, }) async { _lastProgressUpdate = DateTime.fromMillisecondsSinceEpoch(0); final parametriDao = ref.read(parametriDaoProvider); final lastSyncTimeStr = await parametriDao.read(AppConstants.kLastUpdateParam); final lastSyncTime = lastSyncTimeStr != null ? DateTime.tryParse(lastSyncTimeStr) : null; state = SyncState( isSyncing: true, phases: _initPhases(), message: 'Avvio sincronizzazione...', lastSyncTime: lastSyncTime, ); try { final agentiDao = ref.read(agentiDaoProvider); final agente = await agentiDao.findByCode(codAgente); if (agente?.pwd != null) { final authService = ref.read(authServiceProvider); final token = await authService.loginRemote(codAgente, agente!.pwd!); ref.read(apiServiceProvider).token = token; } final agenteSync = ref.read(agenteSyncServiceProvider); final ordineSync = ref.read(ordineSyncServiceProvider); final filialeSync = ref.read(filialeSyncServiceProvider); final clienteSync = ref.read(clienteSyncServiceProvider); final refSync = ref.read(referenceSyncServiceProvider); final lastUpdateStr = forceFullSync ? '200001010000' : (await parametriDao.read(AppConstants.kLastUpdateParam) ?? '200001010000'); await agenteSync.downloadAgenti(idAgente, codAgente, lastUpdateStr); // Phase 0: Invio nuovi clienti _setPhase(0, SyncPhaseStatus.processing); await clienteSync.uploadNewCustomers( idAgente, codAgente, onProgress: _throttledProgress(0), ); _setPhase(0, SyncPhaseStatus.completed); // Phase 1: Invio nuove filiali _setPhase(1, SyncPhaseStatus.processing); await filialeSync.uploadNewDestinations( idAgente, codAgente, onProgress: _throttledProgress(1), ); _setPhase(1, SyncPhaseStatus.completed); // Phase 2: Invio nuovi ordini _setPhase(2, SyncPhaseStatus.processing); await ordineSync.uploadNewOrders( idAgente, codAgente, onProgress: _throttledProgress(2), ); _setPhase(2, SyncPhaseStatus.completed); // Phases 3-9: Reference downloads _setPhase(3, SyncPhaseStatus.downloading); await refSync.downloadTabelle( lastUpdateStr, onProgress: _throttledProgress(3), ); _setPhase(3, SyncPhaseStatus.completed); _setPhase(4, SyncPhaseStatus.downloading); await refSync.downloadPagamenti( lastUpdateStr, onProgress: _throttledProgress(4), ); _setPhase(4, SyncPhaseStatus.completed); _setPhase(5, SyncPhaseStatus.downloading); await refSync.downloadSconti( lastUpdateStr, onProgress: _throttledProgress(5), ); _setPhase(5, SyncPhaseStatus.completed); _setPhase(6, SyncPhaseStatus.downloading); await refSync.downloadArticoli( lastUpdateStr, onProgress: _throttledProgress(6), ); _setPhase(6, SyncPhaseStatus.completed); _setPhase(7, SyncPhaseStatus.downloading); await refSync.downloadListini( lastUpdateStr, onProgress: _throttledProgress(7), ); _setPhase(7, SyncPhaseStatus.completed); _setPhase(8, SyncPhaseStatus.downloading); await refSync.downloadListiniArticolo( lastUpdateStr, onProgress: _throttledProgress(8), ); _setPhase(8, SyncPhaseStatus.completed); _setPhase(9, SyncPhaseStatus.downloading); await refSync.downloadPromozioni( lastUpdateStr, onProgress: _throttledProgress(9), ); _setPhase(9, SyncPhaseStatus.completed); // Per-agent downloads final List agenti = await agentiDao.listAll(); final agentiCodici = agenti.map((a) => a.codAgente!).toList(); state = SyncState( isSyncing: true, progress: state.progress, phases: state.phases, lastSyncTime: state.lastSyncTime, agentiCodici: agentiCodici, ); for (final a in agenti) { _resetAgentPhases(); _setCurrentAgent(a.codAgente!); // Phase 10: Scaricamento clienti _setPhase(10, SyncPhaseStatus.downloading); await clienteSync.downloadCustomers( a.idAgente, a.codAgente!, lastUpdateStr, onProgress: _throttledProgress(10), ); _setPhase(10, SyncPhaseStatus.completed); // Phase 11: Scaricamento filiali _setPhase(11, SyncPhaseStatus.downloading); await filialeSync.downloadDestinations( a.idAgente, a.codAgente!, lastUpdateStr, onProgress: _throttledProgress(11), ); _setPhase(11, SyncPhaseStatus.completed); // Phase 12: Scaricamento scadenze _setPhase(12, SyncPhaseStatus.downloading); await refSync.downloadScadenze( a.idAgente, a.codAgente!, lastUpdateStr, onProgress: _throttledProgress(12), ); _setPhase(12, SyncPhaseStatus.completed); // Phase 13: Scaricamento listini cliente _setPhase(13, SyncPhaseStatus.downloading); await refSync.downloadListiniCliente( a.idAgente, a.codAgente!, lastUpdateStr, onProgress: _throttledProgress(13), ); _setPhase(13, SyncPhaseStatus.completed); // Phase 14: Scaricamento sconti cliente _setPhase(14, SyncPhaseStatus.downloading); await refSync.downloadScontiCliente( a.idAgente, a.codAgente!, lastUpdateStr, onProgress: _throttledProgress(14), ); _setPhase(14, SyncPhaseStatus.completed); } // Phase 15: Scaricamento statistiche _setPhase(15, SyncPhaseStatus.downloading); await refSync.downloadStatistiche( idAgente, codAgente, lastUpdateStr, onProgress: _throttledProgress(15), ); _setPhase(15, SyncPhaseStatus.completed); // Download ordini remoti (no visual phase) for (final a in agenti) { await ordineSync.downloadRemoteOrders( a.idAgente, a.codAgente!, lastUpdateStr, ); } // Download parametro await refSync.downloadParametro('PALLET_PERC'); // Save timestamps final now = DateTime.now(); final newLastUpdate = '${now.year}${_pad(now.month)}${_pad(now.day)}${_pad(now.hour)}${_pad(now.minute)}'; await parametriDao.set(AppConstants.kLastUpdateParam, newLastUpdate); // await parametriDao.set(AppConstants.kLastUpdateParam, now.toIso8601String()); state = SyncState( message: 'Sincronizzazione completata!', progress: 1.0, phases: state.phases, lastSyncTime: now, agentiCodici: state.agentiCodici, ); ref.invalidate(articleSearchProvider); ref.invalidate(activeArticleSearchProvider); ref.invalidate(priceCodesProvider); ref.invalidate(customerListProvider); ref.invalidate(customerSearchProvider); ref.invalidate(customerDestinationsProvider); ref.invalidate(customerPaymentsProvider); ref.invalidate(ordersViewProvider); ref.invalidate(ordersSearchViewProvider); ref.invalidate(orderStatsProvider); ref.invalidate(orderProvider); ref.invalidate(dashboardProvider); ref.invalidate(paymentsProvider); } catch (e) { state = SyncState( error: e.toString(), message: 'Errore durante la sincronizzazione', ); } } String _pad(int n) => n.toString().padLeft(2, '0'); } final syncProvider = NotifierProvider( SyncNotifier.new, );