import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../app/providers.dart'; import '../../../domain/entities/shift_report.dart'; import '../../../domain/entities/sync_event.dart'; import '../../auth/providers/auth_controller.dart'; import '../../pos/providers/catalog_providers.dart'; /// Progress of a catalogue pull. sealed class ImportState { const ImportState(); } class ImportIdle extends ImportState { const ImportIdle(); } class ImportRunning extends ImportState { const ImportRunning(this.progress, this.stage); final double progress; final String stage; } class ImportDone extends ImportState { const ImportDone(this.event); final SyncEvent event; } class ImportFailed extends ImportState { const ImportFailed(this.message); final String message; } /// Bumped after every successful import so catalogue providers refetch. final catalogueVersionProvider = StateProvider((ref) => 0); /// Whether the terminal has products to sell. The POS is gated on this. final catalogueReadyProvider = Provider((ref) { ref.watch(catalogueVersionProvider); return ref.watch(syncRepositoryProvider).hasCatalogue; }); final lastImportAtProvider = Provider((ref) { ref.watch(catalogueVersionProvider); return ref.watch(syncRepositoryProvider).lastImportAt; }); class CatalogueImportController extends StateNotifier { CatalogueImportController(this._ref) : super(const ImportIdle()); final Ref _ref; Future run() async { if (state is ImportRunning) return false; state = const ImportRunning(0, 'Starting…'); final event = await _ref.read(syncRepositoryProvider).importCatalogue( onProgress: (progress, stage) { if (mounted) state = ImportRunning(progress, stage); }, ); if (event.status == SyncStatus.synced) { // Force every catalogue-backed provider to refetch. _ref.read(catalogueVersionProvider.notifier).state++; _ref.invalidate(allProductsProvider); _ref.invalidate(visibleProductsProvider); _ref.invalidate(categoryCountsProvider); _ref.invalidate(lowStockProductsProvider); state = ImportDone(event); return true; } state = ImportFailed(event.error ?? 'Import failed.'); return false; } void reset() => state = const ImportIdle(); } final catalogueImportProvider = StateNotifierProvider( (ref) => CatalogueImportController(ref), ); // ------------------------------------------------------------------ Events /// Bumped whenever the event log changes. final syncVersionProvider = StateProvider((ref) => 0); final syncEventsProvider = Provider>((ref) { ref.watch(syncVersionProvider); ref.watch(catalogueVersionProvider); return ref.watch(syncRepositoryProvider).events; }); final hasUnsyncedProvider = Provider((ref) { ref.watch(syncVersionProvider); ref.watch(catalogueVersionProvider); return ref.watch(syncRepositoryProvider).hasUnsyncedEvents; }); /// Today's takings, recomputed from local sales on every change. final shiftReportProvider = Provider((ref) { ref.watch(syncVersionProvider); final session = ref.watch(cashierSessionProvider); final user = ref.watch(currentUserProvider); return ref.watch(syncRepositoryProvider).buildShiftReport( businessDate: DateTime.now(), terminalId: session.terminalId, cashierName: user?.name ?? session.name, ); }); /// Drives the push button and the sign-out dialog. class ReportPushController extends StateNotifier { ReportPushController(this._ref) : super(false); final Ref _ref; /// Pushes today's report. Returns the resulting event so the caller can /// tell the cashier whether it landed. Future pushToday() async { state = true; try { final report = _ref.read(shiftReportProvider); final event = await _ref.read(syncRepositoryProvider).pushShiftReport(report); _ref.read(syncVersionProvider.notifier).state++; return event; } finally { if (mounted) state = false; } } Future retry(String eventId) async { state = true; try { final event = await _ref.read(syncRepositoryProvider).retry(eventId); _ref.read(syncVersionProvider.notifier).state++; return event; } finally { if (mounted) state = false; } } } final reportPushProvider = StateNotifierProvider( (ref) => ReportPushController(ref), );