import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:uuid/uuid.dart'; import '../models/time_registration.dart'; import '../models/deviation.dart'; import '../models/sync_operation.dart'; import 'sync_service.dart'; class TimeService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final SyncService _syncService; final Uuid _uuid = const Uuid(); TimeService(this._syncService); // Start ny timeregistrering (stempling) Future startTimer({ required String userId, required String organizationId, RegistrationType type = RegistrationType.ordinary, String? projectId, String? customerId, String? comment, }) async { try { final registration = TimeRegistration( id: _uuid.v4(), userId: userId, organizationId: organizationId, startTime: DateTime.now(), endTime: null, // Ikke avsluttet ennå duration: 0, type: type, projectId: projectId, customerId: customerId, comment: comment, breaks: [], createdAt: DateTime.now(), updatedAt: DateTime.now(), createdBy: userId, isManual: false, ); if (_syncService.isOnline) { await _firestore .collection('time_registrations') .doc(registration.id) .set(registration.toFirestore()); } else { await _syncService.queueOperation( collection: 'time_registrations', docId: registration.id, action: SyncAction.create, data: registration.toFirestore(), ); } return registration.id; } catch (e) { throw Exception('Kunne ikke starte timer: $e'); } } // Stopp pågående timeregistrering Future stopTimer(String registrationId, String userId) async { try { // Hvis vi er offline, kan vi ikke hente dokumentet for å beregne varighet nøyaktig // basert på server-data. Vi må stole på lokal logikk eller gjøre en "blind" oppdatering. // For enkelhets skyld antar vi her at vi har start-tiden tilgjengelig i UI/State // eller at vi gjør en update med serverTimestamp for slutt-tid. // Men for offline-støtte må vi beregne alt lokalt. // Dette krever at vi har den aktive registreringen lagret lokalt. // For nå implementerer vi en enkel offline-støtte som bare køer oppdateringen. // NB: Dette er en forenkling. I en fullverdig offline-app bør vi lese fra lokal cache. final endTime = DateTime.now(); // Vi sender en oppdatering som setter endTime. // Cloud Functions eller en senere sync vil måtte håndtere duration-beregning hvis vi ikke har start-tid. // Men vent, vi kan ikke stole på at Cloud Functions kjører offline. // Løsning: Vi gjør en 'update' operasjon. final updates = { 'endTime': Timestamp.fromDate(endTime), 'updatedAt': Timestamp.fromDate(DateTime.now()), 'lastModifiedBy': userId, // Vi kan ikke sette duration her uten å vite starttid. // Hvis vi er online, henter vi og beregner. }; if (_syncService.isOnline) { final doc = await _firestore .collection('time_registrations') .doc(registrationId) .get(); if (!doc.exists) { throw Exception('Timeregistrering ikke funnet'); } final registration = TimeRegistration.fromFirestore(doc); final duration = endTime.difference(registration.startTime).inMinutes; updates['duration'] = duration; await _firestore .collection('time_registrations') .doc(registrationId) .update(updates); } else { // Offline: Vi køer oppdateringen. // NB: Duration vil mangle eller være feil hvis vi ikke har starttid lokalt. // Dette er en begrensning i denne enkle implementasjonen. // En bedre løsning ville være å cache aktive registreringer lokalt. await _syncService.queueOperation( collection: 'time_registrations', docId: registrationId, action: SyncAction.update, data: updates, ); } } catch (e) { throw Exception('Kunne ikke stoppe timer: $e'); } } // Opprett manuell timeregistrering Future createManualEntry({ required String userId, required String organizationId, required DateTime startTime, required DateTime endTime, RegistrationType type = RegistrationType.ordinary, List breaks = const [], String? projectId, String? customerId, String? comment, }) async { try { final duration = endTime.difference(startTime).inMinutes; final registration = TimeRegistration( id: _uuid.v4(), userId: userId, organizationId: organizationId, startTime: startTime, endTime: endTime, duration: duration, type: type, projectId: projectId, customerId: customerId, comment: comment, breaks: breaks, createdAt: DateTime.now(), updatedAt: DateTime.now(), createdBy: userId, isManual: true, ); if (_syncService.isOnline) { await _firestore .collection('time_registrations') .doc(registration.id) .set(registration.toFirestore()); } else { await _syncService.queueOperation( collection: 'time_registrations', docId: registration.id, action: SyncAction.create, data: registration.toFirestore(), ); } return registration.id; } catch (e) { throw Exception('Kunne ikke opprette timeregistrering: $e'); } } // Oppdater eksisterende timeregistrering Future updateRegistration({ required String registrationId, required String userId, DateTime? startTime, DateTime? endTime, RegistrationType? type, List? breaks, String? projectId, String? customerId, String? comment, }) async { try { final Map updates = { 'updatedAt': Timestamp.fromDate(DateTime.now()), 'lastModifiedBy': userId, }; if (startTime != null) updates['startTime'] = Timestamp.fromDate(startTime); if (endTime != null) updates['endTime'] = Timestamp.fromDate(endTime); if (type != null) updates['type'] = type.name; if (breaks != null) updates['breaks'] = breaks.map((b) => b.toMap()).toList(); if (projectId != null) updates['projectId'] = projectId; if (customerId != null) updates['customerId'] = customerId; if (comment != null) updates['comment'] = comment; if (_syncService.isOnline) { // Hvis online, hent og beregn duration if (startTime != null || endTime != null) { final doc = await _firestore.collection('time_registrations').doc(registrationId).get(); if (doc.exists) { final reg = TimeRegistration.fromFirestore(doc); final newStart = startTime ?? reg.startTime; final newEnd = endTime ?? reg.endTime; if (newEnd != null) { updates['duration'] = newEnd.difference(newStart).inMinutes; } } } await _firestore .collection('time_registrations') .doc(registrationId) .update(updates); } else { // Offline: Kø oppdatering. Duration beregnes ikke her (begrensning). await _syncService.queueOperation( collection: 'time_registrations', docId: registrationId, action: SyncAction.update, data: updates, ); } } catch (e) { throw Exception('Kunne ikke oppdatere timeregistrering: $e'); } } // Slett timeregistrering Future deleteRegistration(String registrationId) async { try { if (_syncService.isOnline) { await _firestore .collection('time_registrations') .doc(registrationId) .delete(); } else { await _syncService.queueOperation( collection: 'time_registrations', docId: registrationId, action: SyncAction.delete, data: {}, ); } } catch (e) { throw Exception('Kunne ikke slette timeregistrering: $e'); } } // Hent alle timeregistreringer for bruker i en periode Future> getRegistrations({ required String userId, DateTime? startDate, DateTime? endDate, }) async { try { // TODO: Implementer lokal caching for lesing offline // For nå kaster vi feil hvis offline og ingen cache if (!_syncService.isOnline) { // Her burde vi returnere fra lokal cache // return []; } Query query = _firestore .collection('time_registrations') .where('userId', isEqualTo: userId); if (startDate != null) { query = query.where('startTime', isGreaterThanOrEqualTo: Timestamp.fromDate(startDate)); } if (endDate != null) { query = query.where('startTime', isLessThanOrEqualTo: Timestamp.fromDate(endDate)); } final snapshot = await query.orderBy('startTime', descending: true).get(); return snapshot.docs .map((doc) => TimeRegistration.fromFirestore(doc)) .toList(); } catch (e) { throw Exception('Kunne ikke hente timeregistreringer: $e'); } } // Hent timeregistreringer for en spesifikk dag Future> getRegistrationsByDay({ required String userId, required DateTime date, }) async { final startOfDay = DateTime(date.year, date.month, date.day); final endOfDay = DateTime(date.year, date.month, date.day, 23, 59, 59); return getRegistrations( userId: userId, startDate: startOfDay, endDate: endOfDay, ); } // Hent pågående timeregistrering Future getActiveRegistration(String userId) async { try { // TODO: Sjekk lokal cache først final snapshot = await _firestore .collection('time_registrations') .where('userId', isEqualTo: userId) .where('endTime', isNull: true) .limit(1) .get(); if (snapshot.docs.isNotEmpty) { return TimeRegistration.fromFirestore(snapshot.docs.first); } return null; } catch (e) { throw Exception('Kunne ikke hente aktiv timeregistrering: $e'); } } // Stream av timeregistreringer for bruker Stream> registrationsStream({ required String userId, DateTime? startDate, DateTime? endDate, }) { // TODO: Kombiner lokal cache og firestore stream Query query = _firestore .collection('time_registrations') .where('userId', isEqualTo: userId); if (startDate != null) { query = query.where('startTime', isGreaterThanOrEqualTo: Timestamp.fromDate(startDate)); } if (endDate != null) { query = query.where('startTime', isLessThanOrEqualTo: Timestamp.fromDate(endDate)); } return query.orderBy('startTime', descending: true).snapshots().map( (snapshot) => snapshot.docs .map((doc) => TimeRegistration.fromFirestore(doc)) .toList(), ); } // Beregn total arbeidstid for en periode Future> calculateTotalHours({ required String userId, required DateTime startDate, required DateTime endDate, }) async { final registrations = await getRegistrations( userId: userId, startDate: startDate, endDate: endDate, ); int totalMinutes = 0; int ordinaryMinutes = 0; int overtimeMinutes = 0; for (final reg in registrations) { if (reg.endTime != null) { totalMinutes += reg.netWorkMinutes; if (reg.type == RegistrationType.ordinary) { ordinaryMinutes += reg.netWorkMinutes; } else if (reg.type == RegistrationType.overtime) { overtimeMinutes += reg.netWorkMinutes; } } } return { 'total': totalMinutes, 'ordinary': ordinaryMinutes, 'overtime': overtimeMinutes, }; } // Hent avvik for organisasjon Future> getDeviations({ required String organizationId, bool? onlyUnacknowledged, }) async { try { Query query = _firestore .collection('deviations') .where('organizationId', isEqualTo: organizationId); if (onlyUnacknowledged == true) { query = query.where('acknowledgedAt', isNull: true); } final snapshot = await query.orderBy('detectedAt', descending: true).get(); return snapshot.docs.map((doc) => Deviation.fromFirestore(doc)).toList(); } catch (e) { throw Exception('Kunne ikke hente avvik: $e'); } } // Kvitter ut avvik Future acknowledgeDeviation({ required String deviationId, required String userId, String? comment, }) async { try { final updates = { 'acknowledgedAt': Timestamp.now(), 'acknowledgedBy': userId, if (comment != null) 'comment': comment, }; if (_syncService.isOnline) { await _firestore.collection('deviations').doc(deviationId).update(updates); } else { await _syncService.queueOperation( collection: 'deviations', docId: deviationId, action: SyncAction.update, data: updates, ); } } catch (e) { throw Exception('Kunne ikke kvittere ut avvik: $e'); } } }