sync_provider.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import 'package:drift/drift.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:ristoris/core/constants.dart';
  4. import 'package:ristoris/database/database.dart';
  5. import '../services/api_service.dart';
  6. import '../services/sync/agente_sync_service.dart';
  7. import '../services/sync/cliente_sync_service.dart';
  8. import '../services/sync/ordine_sync_service.dart';
  9. import '../services/sync/filiale_sync_service.dart';
  10. import '../services/sync/reference_sync_service.dart';
  11. import '../services/image_service.dart';
  12. import 'article_provider.dart';
  13. import 'auth_provider.dart';
  14. import 'customer_provider.dart';
  15. import 'dashboard_provider.dart';
  16. import 'database_provider.dart';
  17. import 'order_provider.dart';
  18. import '../ui/pages/payments/payments_page.dart';
  19. final apiServiceProvider = Provider<ApiService>((ref) {
  20. final parametriDao = ref.read(parametriDaoProvider);
  21. return ApiService(parametriDao);
  22. });
  23. final imageServiceProvider = Provider<ImageService>((ref) {
  24. final parametriDao = ref.read(parametriDaoProvider);
  25. return ImageService(parametriDao);
  26. });
  27. final agenteSyncServiceProvider = Provider<AgenteSyncService>((ref) {
  28. final agentiDao = ref.read(agentiDaoProvider);
  29. return AgenteSyncService(ref.read(apiServiceProvider), agentiDao);
  30. });
  31. final clienteSyncServiceProvider = Provider<ClienteSyncService>((ref) {
  32. final api = ref.read(apiServiceProvider);
  33. final clientiDao = ref.read(clientiDaoProvider);
  34. final clientiAgenteDao = ref.read(clientiAgenteDaoProvider);
  35. return ClienteSyncService(api, clientiDao, clientiAgenteDao);
  36. });
  37. final ordineSyncServiceProvider = Provider<OrdineSyncService>((ref) {
  38. return OrdineSyncService(
  39. ref.read(apiServiceProvider),
  40. ref.read(ordiniDaoProvider),
  41. ref.read(clientiDaoProvider),
  42. ref.read(filialiDaoProvider),
  43. ref.read(agentiDaoProvider),
  44. );
  45. });
  46. final filialeSyncServiceProvider = Provider<FilialeSyncService>((ref) {
  47. return FilialeSyncService(
  48. ref.read(apiServiceProvider),
  49. ref.read(filialiDaoProvider),
  50. ref.read(clientiDaoProvider),
  51. );
  52. });
  53. final referenceSyncServiceProvider = Provider<ReferenceSyncService>((ref) {
  54. return ReferenceSyncService(
  55. ref.read(apiServiceProvider),
  56. ref.read(tabelleDaoProvider),
  57. ref.read(pagamentiDaoProvider),
  58. ref.read(scontiDaoProvider),
  59. ref.read(articoliDaoProvider),
  60. ref.read(listiniDaoProvider),
  61. ref.read(promozioniDaoProvider),
  62. ref.read(scadenzeDaoProvider),
  63. ref.read(statisticheDaoProvider),
  64. ref.read(parametriDaoProvider),
  65. ref.read(clientiDaoProvider),
  66. );
  67. });
  68. enum SyncPhaseStatus { waiting, downloading, processing, completed }
  69. class SyncPhaseInfo {
  70. final String name;
  71. final SyncPhaseStatus status;
  72. final int? currentRecord;
  73. final int? totalRecords;
  74. const SyncPhaseInfo({
  75. required this.name,
  76. this.status = SyncPhaseStatus.waiting,
  77. this.currentRecord,
  78. this.totalRecords,
  79. });
  80. SyncPhaseInfo copyWith({
  81. SyncPhaseStatus? status,
  82. int? currentRecord,
  83. int? totalRecords,
  84. }) {
  85. return SyncPhaseInfo(
  86. name: name,
  87. status: status ?? this.status,
  88. currentRecord: currentRecord ?? this.currentRecord,
  89. totalRecords: totalRecords ?? this.totalRecords,
  90. );
  91. }
  92. String get statusText {
  93. switch (status) {
  94. case SyncPhaseStatus.waiting:
  95. return 'IN ATTESA';
  96. case SyncPhaseStatus.downloading:
  97. return 'IN SCARICAMENTO';
  98. case SyncPhaseStatus.processing:
  99. if (currentRecord != null && totalRecords != null) {
  100. return 'Elaborazione $currentRecord di $totalRecords';
  101. }
  102. return 'IN ELABORAZIONE';
  103. case SyncPhaseStatus.completed:
  104. return 'COMPLETATO';
  105. }
  106. }
  107. }
  108. const List<String> syncPhaseNames = [
  109. 'Invio nuovi clienti',
  110. 'Invio nuove filiali',
  111. 'Invio nuovi ordini',
  112. 'Scaricamento tabelle',
  113. 'Scaricamento pagamenti',
  114. 'Scaricamento sconti',
  115. 'Scaricamento articoli',
  116. 'Scaricamento listini',
  117. 'Scaricamento listini articolo',
  118. 'Scaricamento promozioni',
  119. 'Scaricamento clienti',
  120. 'Scaricamento filiali',
  121. 'Scaricamento scadenze',
  122. 'Scaricamento listini cliente',
  123. 'Scaricamento sconti cliente',
  124. 'Scaricamento statistiche',
  125. ];
  126. class SyncState {
  127. final bool isSyncing;
  128. final double progress;
  129. final String message;
  130. final String? error;
  131. final List<SyncPhaseInfo> phases;
  132. final DateTime? lastSyncTime;
  133. final String? currentAgentCode;
  134. final List<String> agentiCodici;
  135. const SyncState({
  136. this.isSyncing = false,
  137. this.progress = 0,
  138. this.message = '',
  139. this.error,
  140. this.phases = const [],
  141. this.lastSyncTime,
  142. this.currentAgentCode,
  143. this.agentiCodici = const [],
  144. });
  145. }
  146. class SyncNotifier extends Notifier<SyncState> {
  147. DateTime _lastProgressUpdate = DateTime.fromMillisecondsSinceEpoch(0);
  148. @override
  149. SyncState build() {
  150. Future.microtask(() async {
  151. try {
  152. final parametriDao = ref.read(parametriDaoProvider);
  153. final lastSyncTimeStr = await parametriDao.read(AppConstants.kLastUpdateParam);
  154. if (lastSyncTimeStr != null) {
  155. final lastSyncTime = DateTime.tryParse(lastSyncTimeStr);
  156. if (lastSyncTime != null) {
  157. state = SyncState(
  158. phases: state.phases,
  159. lastSyncTime: lastSyncTime,
  160. );
  161. }
  162. }
  163. } catch (_) {}
  164. });
  165. return const SyncState();
  166. }
  167. List<SyncPhaseInfo> _initPhases() {
  168. return syncPhaseNames.map((name) => SyncPhaseInfo(name: name)).toList();
  169. }
  170. void _setPhase(
  171. int index,
  172. SyncPhaseStatus status, {
  173. int? currentRecord,
  174. int? totalRecords,
  175. }) {
  176. final phases = List<SyncPhaseInfo>.from(state.phases);
  177. phases[index] = phases[index].copyWith(
  178. status: status,
  179. currentRecord: currentRecord,
  180. totalRecords: totalRecords,
  181. );
  182. final progress = (index + 1) / syncPhaseNames.length;
  183. state = SyncState(
  184. isSyncing: true,
  185. progress: progress,
  186. phases: phases,
  187. lastSyncTime: state.lastSyncTime,
  188. currentAgentCode: state.currentAgentCode,
  189. agentiCodici: state.agentiCodici,
  190. );
  191. }
  192. void _resetAgentPhases() {
  193. final phases = List<SyncPhaseInfo>.from(state.phases);
  194. for (int i = 10; i <= 14; i++) {
  195. phases[i] = SyncPhaseInfo(name: syncPhaseNames[i]);
  196. }
  197. state = SyncState(
  198. isSyncing: true,
  199. progress: state.progress,
  200. phases: phases,
  201. lastSyncTime: state.lastSyncTime,
  202. currentAgentCode: state.currentAgentCode,
  203. agentiCodici: state.agentiCodici,
  204. );
  205. }
  206. void _setCurrentAgent(String codAgente) {
  207. state = SyncState(
  208. isSyncing: true,
  209. progress: state.progress,
  210. phases: state.phases,
  211. lastSyncTime: state.lastSyncTime,
  212. currentAgentCode: codAgente,
  213. agentiCodici: state.agentiCodici,
  214. );
  215. }
  216. void Function(int current, int total) _throttledProgress(int phaseIndex) {
  217. return (int current, int total) {
  218. final now = DateTime.now();
  219. if (now.difference(_lastProgressUpdate).inMilliseconds >= 500) {
  220. _lastProgressUpdate = now;
  221. _setPhase(
  222. phaseIndex,
  223. SyncPhaseStatus.processing,
  224. currentRecord: current,
  225. totalRecords: total,
  226. );
  227. }
  228. };
  229. }
  230. Future<void> startFullSync(
  231. int idAgente,
  232. String codAgente, {
  233. bool forceFullSync = false,
  234. }) async {
  235. _lastProgressUpdate = DateTime.fromMillisecondsSinceEpoch(0);
  236. final parametriDao = ref.read(parametriDaoProvider);
  237. final lastSyncTimeStr = await parametriDao.read(AppConstants.kLastUpdateParam);
  238. final lastSyncTime = lastSyncTimeStr != null ? DateTime.tryParse(lastSyncTimeStr) : null;
  239. state = SyncState(
  240. isSyncing: true,
  241. phases: _initPhases(),
  242. message: 'Avvio sincronizzazione...',
  243. lastSyncTime: lastSyncTime,
  244. );
  245. try {
  246. final agentiDao = ref.read(agentiDaoProvider);
  247. final agente = await agentiDao.findByCode(codAgente);
  248. if (agente?.pwd != null) {
  249. final authService = ref.read(authServiceProvider);
  250. final token = await authService.loginRemote(codAgente, agente!.pwd!);
  251. ref.read(apiServiceProvider).token = token;
  252. }
  253. final agenteSync = ref.read(agenteSyncServiceProvider);
  254. final ordineSync = ref.read(ordineSyncServiceProvider);
  255. final filialeSync = ref.read(filialeSyncServiceProvider);
  256. final clienteSync = ref.read(clienteSyncServiceProvider);
  257. final refSync = ref.read(referenceSyncServiceProvider);
  258. final lastUpdateStr = forceFullSync ? '200001010000' : (await parametriDao.read(AppConstants.kLastUpdateParam) ?? '200001010000');
  259. await agenteSync.downloadAgenti(idAgente, codAgente, lastUpdateStr);
  260. // Phase 0: Invio nuovi clienti
  261. _setPhase(0, SyncPhaseStatus.processing);
  262. await clienteSync.uploadNewCustomers(
  263. idAgente,
  264. codAgente,
  265. onProgress: _throttledProgress(0),
  266. );
  267. _setPhase(0, SyncPhaseStatus.completed);
  268. // Phase 1: Invio nuove filiali
  269. _setPhase(1, SyncPhaseStatus.processing);
  270. await filialeSync.uploadNewDestinations(
  271. idAgente,
  272. codAgente,
  273. onProgress: _throttledProgress(1),
  274. );
  275. _setPhase(1, SyncPhaseStatus.completed);
  276. // Phase 2: Invio nuovi ordini
  277. _setPhase(2, SyncPhaseStatus.processing);
  278. await ordineSync.uploadNewOrders(
  279. idAgente,
  280. codAgente,
  281. onProgress: _throttledProgress(2),
  282. );
  283. _setPhase(2, SyncPhaseStatus.completed);
  284. // Phases 3-9: Reference downloads
  285. _setPhase(3, SyncPhaseStatus.downloading);
  286. await refSync.downloadTabelle(
  287. lastUpdateStr,
  288. onProgress: _throttledProgress(3),
  289. );
  290. _setPhase(3, SyncPhaseStatus.completed);
  291. _setPhase(4, SyncPhaseStatus.downloading);
  292. await refSync.downloadPagamenti(
  293. lastUpdateStr,
  294. onProgress: _throttledProgress(4),
  295. );
  296. _setPhase(4, SyncPhaseStatus.completed);
  297. _setPhase(5, SyncPhaseStatus.downloading);
  298. await refSync.downloadSconti(
  299. lastUpdateStr,
  300. onProgress: _throttledProgress(5),
  301. );
  302. _setPhase(5, SyncPhaseStatus.completed);
  303. _setPhase(6, SyncPhaseStatus.downloading);
  304. await refSync.downloadArticoli(
  305. lastUpdateStr,
  306. onProgress: _throttledProgress(6),
  307. );
  308. _setPhase(6, SyncPhaseStatus.completed);
  309. _setPhase(7, SyncPhaseStatus.downloading);
  310. await refSync.downloadListini(
  311. lastUpdateStr,
  312. onProgress: _throttledProgress(7),
  313. );
  314. _setPhase(7, SyncPhaseStatus.completed);
  315. _setPhase(8, SyncPhaseStatus.downloading);
  316. await refSync.downloadListiniArticolo(
  317. lastUpdateStr,
  318. onProgress: _throttledProgress(8),
  319. );
  320. _setPhase(8, SyncPhaseStatus.completed);
  321. _setPhase(9, SyncPhaseStatus.downloading);
  322. await refSync.downloadPromozioni(
  323. lastUpdateStr,
  324. onProgress: _throttledProgress(9),
  325. );
  326. _setPhase(9, SyncPhaseStatus.completed);
  327. // Per-agent downloads
  328. final List<Agente> agenti = await agentiDao.listAll();
  329. final agentiCodici = agenti.map((a) => a.codAgente!).toList();
  330. state = SyncState(
  331. isSyncing: true,
  332. progress: state.progress,
  333. phases: state.phases,
  334. lastSyncTime: state.lastSyncTime,
  335. agentiCodici: agentiCodici,
  336. );
  337. for (final a in agenti) {
  338. _resetAgentPhases();
  339. _setCurrentAgent(a.codAgente!);
  340. // Phase 10: Scaricamento clienti
  341. _setPhase(10, SyncPhaseStatus.downloading);
  342. await clienteSync.downloadCustomers(
  343. a.idAgente,
  344. a.codAgente!,
  345. lastUpdateStr,
  346. onProgress: _throttledProgress(10),
  347. );
  348. _setPhase(10, SyncPhaseStatus.completed);
  349. // Phase 11: Scaricamento filiali
  350. _setPhase(11, SyncPhaseStatus.downloading);
  351. await filialeSync.downloadDestinations(
  352. a.idAgente,
  353. a.codAgente!,
  354. lastUpdateStr,
  355. onProgress: _throttledProgress(11),
  356. );
  357. _setPhase(11, SyncPhaseStatus.completed);
  358. // Phase 12: Scaricamento scadenze
  359. _setPhase(12, SyncPhaseStatus.downloading);
  360. await refSync.downloadScadenze(
  361. a.idAgente,
  362. a.codAgente!,
  363. lastUpdateStr,
  364. onProgress: _throttledProgress(12),
  365. );
  366. _setPhase(12, SyncPhaseStatus.completed);
  367. // Phase 13: Scaricamento listini cliente
  368. _setPhase(13, SyncPhaseStatus.downloading);
  369. await refSync.downloadListiniCliente(
  370. a.idAgente,
  371. a.codAgente!,
  372. lastUpdateStr,
  373. onProgress: _throttledProgress(13),
  374. );
  375. _setPhase(13, SyncPhaseStatus.completed);
  376. // Phase 14: Scaricamento sconti cliente
  377. _setPhase(14, SyncPhaseStatus.downloading);
  378. await refSync.downloadScontiCliente(
  379. a.idAgente,
  380. a.codAgente!,
  381. lastUpdateStr,
  382. onProgress: _throttledProgress(14),
  383. );
  384. _setPhase(14, SyncPhaseStatus.completed);
  385. }
  386. // Phase 15: Scaricamento statistiche
  387. _setPhase(15, SyncPhaseStatus.downloading);
  388. await refSync.downloadStatistiche(
  389. idAgente,
  390. codAgente,
  391. lastUpdateStr,
  392. onProgress: _throttledProgress(15),
  393. );
  394. _setPhase(15, SyncPhaseStatus.completed);
  395. // Download ordini remoti (no visual phase)
  396. for (final a in agenti) {
  397. await ordineSync.downloadRemoteOrders(
  398. a.idAgente,
  399. a.codAgente!,
  400. lastUpdateStr,
  401. );
  402. }
  403. // Download parametro
  404. await refSync.downloadParametro('PALLET_PERC');
  405. // Save timestamps
  406. final now = DateTime.now();
  407. final newLastUpdate = '${now.year}${_pad(now.month)}${_pad(now.day)}${_pad(now.hour)}${_pad(now.minute)}';
  408. await parametriDao.set(AppConstants.kLastUpdateParam, newLastUpdate);
  409. // await parametriDao.set(AppConstants.kLastUpdateParam, now.toIso8601String());
  410. state = SyncState(
  411. message: 'Sincronizzazione completata!',
  412. progress: 1.0,
  413. phases: state.phases,
  414. lastSyncTime: now,
  415. agentiCodici: state.agentiCodici,
  416. );
  417. ref.invalidate(articleSearchProvider);
  418. ref.invalidate(activeArticleSearchProvider);
  419. ref.invalidate(priceCodesProvider);
  420. ref.invalidate(customerListProvider);
  421. ref.invalidate(customerSearchProvider);
  422. ref.invalidate(customerDestinationsProvider);
  423. ref.invalidate(customerPaymentsProvider);
  424. ref.invalidate(ordersViewProvider);
  425. ref.invalidate(ordersSearchViewProvider);
  426. ref.invalidate(orderStatsProvider);
  427. ref.invalidate(orderProvider);
  428. ref.invalidate(dashboardProvider);
  429. ref.invalidate(paymentsProvider);
  430. } catch (e) {
  431. state = SyncState(
  432. error: e.toString(),
  433. message: 'Errore durante la sincronizzazione',
  434. );
  435. }
  436. }
  437. String _pad(int n) => n.toString().padLeft(2, '0');
  438. }
  439. final syncProvider = NotifierProvider<SyncNotifier, SyncState>(
  440. SyncNotifier.new,
  441. );