Files
TimeReg/lib/models/time_registration.dart
steinhelge c829f78984 Initial commit: TimeReg Flutter app med Firebase backend
- Opprettet Flutter-prosjekt med alle nødvendige avhengigheter
- Implementert datamodeller (User, TimeRegistration, TariffProfile, Deviation, AuditLog)
- Implementert tjenester (AuthService, TimeService)
- Implementert Riverpod providers for state management
- Opprettet autentiseringsskjermer (login, signup, reset password)
- Opprettet hjemmeskjerm med timer-funksjonalitet
- Opprettet placeholder-skjermer for historikk, rapporter og profil
- Lagt til norsk dokumentasjon i README
2025-11-24 20:52:27 +01:00

211 lines
5.6 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
enum RegistrationType {
ordinary,
overtime,
oncall,
travel,
}
enum SyncStatus {
synced,
pending,
conflict,
}
class TimeBreak {
final DateTime startTime;
final DateTime endTime;
final int duration; // in minutes
TimeBreak({
required this.startTime,
required this.endTime,
required this.duration,
});
factory TimeBreak.fromMap(Map<String, dynamic> map) {
return TimeBreak(
startTime: (map['startTime'] as Timestamp).toDate(),
endTime: (map['endTime'] as Timestamp).toDate(),
duration: map['duration'] ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
'startTime': Timestamp.fromDate(startTime),
'endTime': Timestamp.fromDate(endTime),
'duration': duration,
};
}
}
class TimeRegistration {
final String id;
final String userId;
final String organizationId;
final DateTime startTime;
final DateTime? endTime;
final int duration; // in minutes
final RegistrationType type;
final String? projectId;
final String? customerId;
final String? comment;
final List<TimeBreak> breaks;
final DateTime createdAt;
final DateTime updatedAt;
final String createdBy;
final String? lastModifiedBy;
final List<String> deviations;
final bool isManual;
final SyncStatus syncStatus;
TimeRegistration({
required this.id,
required this.userId,
required this.organizationId,
required this.startTime,
this.endTime,
required this.duration,
this.type = RegistrationType.ordinary,
this.projectId,
this.customerId,
this.comment,
this.breaks = const [],
required this.createdAt,
required this.updatedAt,
required this.createdBy,
this.lastModifiedBy,
this.deviations = const [],
this.isManual = false,
this.syncStatus = SyncStatus.synced,
});
factory TimeRegistration.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return TimeRegistration(
id: doc.id,
userId: data['userId'] ?? '',
organizationId: data['organizationId'] ?? '',
startTime: (data['startTime'] as Timestamp).toDate(),
endTime: data['endTime'] != null
? (data['endTime'] as Timestamp).toDate()
: null,
duration: data['duration'] ?? 0,
type: _parseType(data['type']),
projectId: data['projectId'],
customerId: data['customerId'],
comment: data['comment'],
breaks: (data['breaks'] as List<dynamic>?)
?.map((b) => TimeBreak.fromMap(b as Map<String, dynamic>))
.toList() ??
[],
createdAt: (data['createdAt'] as Timestamp).toDate(),
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
createdBy: data['createdBy'] ?? '',
lastModifiedBy: data['lastModifiedBy'],
deviations: List<String>.from(data['deviations'] ?? []),
isManual: data['isManual'] ?? false,
syncStatus: _parseSyncStatus(data['syncStatus']),
);
}
Map<String, dynamic> toFirestore() {
return {
'userId': userId,
'organizationId': organizationId,
'startTime': Timestamp.fromDate(startTime),
'endTime': endTime != null ? Timestamp.fromDate(endTime!) : null,
'duration': duration,
'type': type.name,
'projectId': projectId,
'customerId': customerId,
'comment': comment,
'breaks': breaks.map((b) => b.toMap()).toList(),
'createdAt': Timestamp.fromDate(createdAt),
'updatedAt': Timestamp.fromDate(updatedAt),
'createdBy': createdBy,
'lastModifiedBy': lastModifiedBy,
'deviations': deviations,
'isManual': isManual,
'syncStatus': syncStatus.name,
};
}
static RegistrationType _parseType(String? typeString) {
switch (typeString) {
case 'overtime':
return RegistrationType.overtime;
case 'oncall':
return RegistrationType.oncall;
case 'travel':
return RegistrationType.travel;
default:
return RegistrationType.ordinary;
}
}
static SyncStatus _parseSyncStatus(String? statusString) {
switch (statusString) {
case 'pending':
return SyncStatus.pending;
case 'conflict':
return SyncStatus.conflict;
default:
return SyncStatus.synced;
}
}
int get totalBreakMinutes {
return breaks.fold(0, (sum, b) => sum + b.duration);
}
int get netWorkMinutes {
return duration - totalBreakMinutes;
}
bool get isActive {
return endTime == null;
}
TimeRegistration copyWith({
String? userId,
String? organizationId,
DateTime? startTime,
DateTime? endTime,
int? duration,
RegistrationType? type,
String? projectId,
String? customerId,
String? comment,
List<TimeBreak>? breaks,
DateTime? updatedAt,
String? lastModifiedBy,
List<String>? deviations,
bool? isManual,
SyncStatus? syncStatus,
}) {
return TimeRegistration(
id: id,
userId: userId ?? this.userId,
organizationId: organizationId ?? this.organizationId,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
duration: duration ?? this.duration,
type: type ?? this.type,
projectId: projectId ?? this.projectId,
customerId: customerId ?? this.customerId,
comment: comment ?? this.comment,
breaks: breaks ?? this.breaks,
createdAt: createdAt,
updatedAt: updatedAt ?? this.updatedAt,
createdBy: createdBy,
lastModifiedBy: lastModifiedBy ?? this.lastModifiedBy,
deviations: deviations ?? this.deviations,
isManual: isManual ?? this.isManual,
syncStatus: syncStatus ?? this.syncStatus,
);
}
}