feat: Introduce offline synchronization for time registrations via a new sync service.
This commit is contained in:
+155
-73
@@ -2,11 +2,16 @@ 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<String> startTimer({
|
||||
required String userId,
|
||||
@@ -35,10 +40,19 @@ class TimeService {
|
||||
isManual: false,
|
||||
);
|
||||
|
||||
await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registration.id)
|
||||
.set(registration.toFirestore());
|
||||
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) {
|
||||
@@ -49,28 +63,63 @@ class TimeService {
|
||||
// Stopp pågående timeregistrering
|
||||
Future<void> stopTimer(String registrationId, String userId) async {
|
||||
try {
|
||||
final doc = await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registrationId)
|
||||
.get();
|
||||
|
||||
if (!doc.exists) {
|
||||
throw Exception('Timeregistrering ikke funnet');
|
||||
}
|
||||
|
||||
final registration = TimeRegistration.fromFirestore(doc);
|
||||
// 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();
|
||||
final duration = endTime.difference(registration.startTime).inMinutes;
|
||||
|
||||
await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registrationId)
|
||||
.update({
|
||||
|
||||
// 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),
|
||||
'duration': duration,
|
||||
'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');
|
||||
}
|
||||
@@ -109,10 +158,19 @@ class TimeService {
|
||||
isManual: true,
|
||||
);
|
||||
|
||||
await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registration.id)
|
||||
.set(registration.toFirestore());
|
||||
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) {
|
||||
@@ -133,54 +191,46 @@ class TimeService {
|
||||
String? comment,
|
||||
}) async {
|
||||
try {
|
||||
final doc = await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registrationId)
|
||||
.get();
|
||||
|
||||
if (!doc.exists) {
|
||||
throw Exception('Timeregistrering ikke funnet');
|
||||
}
|
||||
|
||||
final registration = TimeRegistration.fromFirestore(doc);
|
||||
final Map<String, dynamic> 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 (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;
|
||||
|
||||
// Beregn ny varighet hvis start eller slutt er endret
|
||||
final newStartTime = startTime ?? registration.startTime;
|
||||
final newEndTime = endTime ?? registration.endTime;
|
||||
if (newEndTime != null) {
|
||||
updates['duration'] = newEndTime.difference(newStartTime).inMinutes;
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registrationId)
|
||||
.update(updates);
|
||||
} catch (e) {
|
||||
throw Exception('Kunne ikke oppdatere timeregistrering: $e');
|
||||
}
|
||||
@@ -189,10 +239,19 @@ class TimeService {
|
||||
// Slett timeregistrering
|
||||
Future<void> deleteRegistration(String registrationId) async {
|
||||
try {
|
||||
await _firestore
|
||||
.collection('time_registrations')
|
||||
.doc(registrationId)
|
||||
.delete();
|
||||
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');
|
||||
}
|
||||
@@ -205,6 +264,13 @@ class TimeService {
|
||||
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);
|
||||
@@ -245,6 +311,8 @@ class TimeService {
|
||||
// Hent pågående timeregistrering
|
||||
Future<TimeRegistration?> getActiveRegistration(String userId) async {
|
||||
try {
|
||||
// TODO: Sjekk lokal cache først
|
||||
|
||||
final snapshot = await _firestore
|
||||
.collection('time_registrations')
|
||||
.where('userId', isEqualTo: userId)
|
||||
@@ -267,6 +335,8 @@ class TimeService {
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
}) {
|
||||
// TODO: Kombiner lokal cache og firestore stream
|
||||
|
||||
Query query = _firestore
|
||||
.collection('time_registrations')
|
||||
.where('userId', isEqualTo: userId);
|
||||
@@ -320,6 +390,7 @@ class TimeService {
|
||||
'overtime': overtimeMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
// Hent avvik for organisasjon
|
||||
Future<List<Deviation>> getDeviations({
|
||||
required String organizationId,
|
||||
@@ -348,11 +419,22 @@ class TimeService {
|
||||
String? comment,
|
||||
}) async {
|
||||
try {
|
||||
await _firestore.collection('deviations').doc(deviationId).update({
|
||||
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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user