refactor: Projektstruktur in app/ und backend/ aufgeteilt
This commit is contained in:
85
app/lib/models/acknowledgment_message.dart
Normal file
85
app/lib/models/acknowledgment_message.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
/// Acknowledgment message sent by client to confirm message receipt
|
||||
class AcknowledgmentMessage {
|
||||
/// ID of the message being acknowledged
|
||||
final String messageId;
|
||||
|
||||
/// Status of the acknowledgment
|
||||
final AcknowledgmentStatus status;
|
||||
|
||||
/// Timestamp when the acknowledgment was created
|
||||
final DateTime timestamp;
|
||||
|
||||
/// Optional error message if status is FAILED
|
||||
final String? errorMessage;
|
||||
|
||||
AcknowledgmentMessage({
|
||||
required this.messageId,
|
||||
required this.status,
|
||||
required this.timestamp,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
/// Create AcknowledgmentMessage from JSON
|
||||
factory AcknowledgmentMessage.fromJson(Map<String, dynamic> json) {
|
||||
return AcknowledgmentMessage(
|
||||
messageId: json['messageId'] as String,
|
||||
status: AcknowledgmentStatus.fromString(json['status'] as String),
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert AcknowledgmentMessage to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'messageId': messageId,
|
||||
'status': status.toString(),
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
if (errorMessage != null) 'errorMessage': errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AcknowledgmentMessage(messageId: $messageId, status: $status)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Status of an acknowledgment
|
||||
enum AcknowledgmentStatus {
|
||||
/// Message was received by the client
|
||||
received,
|
||||
|
||||
/// Message was processed successfully
|
||||
processed,
|
||||
|
||||
/// Message processing failed
|
||||
failed;
|
||||
|
||||
/// Convert string to AcknowledgmentStatus
|
||||
static AcknowledgmentStatus fromString(String value) {
|
||||
switch (value.toUpperCase()) {
|
||||
case 'RECEIVED':
|
||||
return AcknowledgmentStatus.received;
|
||||
case 'PROCESSED':
|
||||
return AcknowledgmentStatus.processed;
|
||||
case 'FAILED':
|
||||
return AcknowledgmentStatus.failed;
|
||||
default:
|
||||
return AcknowledgmentStatus.received;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (this) {
|
||||
case AcknowledgmentStatus.received:
|
||||
return 'RECEIVED';
|
||||
case AcknowledgmentStatus.processed:
|
||||
return 'PROCESSED';
|
||||
case AcknowledgmentStatus.failed:
|
||||
return 'FAILED';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
app/lib/models/cargo_item.dart
Normal file
99
app/lib/models/cargo_item.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
class CargoItem {
|
||||
final String id; // Will store the timestamp as string
|
||||
final String jobId; // Will store the timestamp as string
|
||||
final String description;
|
||||
final int quantity;
|
||||
final double weightKg;
|
||||
final double lengthCm;
|
||||
final double widthCm;
|
||||
final double heightCm;
|
||||
|
||||
CargoItem({
|
||||
required this.id,
|
||||
required this.jobId,
|
||||
required this.description,
|
||||
required this.quantity,
|
||||
required this.weightKg,
|
||||
required this.lengthCm,
|
||||
required this.widthCm,
|
||||
required this.heightCm,
|
||||
});
|
||||
|
||||
static String _readString(dynamic value, {String fallback = ''}) {
|
||||
if (value is String) {
|
||||
return value;
|
||||
}
|
||||
if (value is num || value is bool) {
|
||||
return value.toString();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
factory CargoItem.fromJson(Map<String, dynamic> json) {
|
||||
// Parse the complex id object - can be either a Map or a simple string
|
||||
String idValue = '';
|
||||
if (json['id'] is Map) {
|
||||
final idMap = json['id'] as Map<String, dynamic>;
|
||||
idValue = idMap['timestamp']?.toString() ?? '';
|
||||
} else {
|
||||
idValue = json['id']?.toString() ?? '';
|
||||
}
|
||||
|
||||
// Parse the complex jobId object - can be either a Map or a simple string
|
||||
String jobIdValue = '';
|
||||
if (json['jobId'] is Map) {
|
||||
final jobIdMap = json['jobId'] as Map<String, dynamic>;
|
||||
jobIdValue = jobIdMap['timestamp']?.toString() ?? '';
|
||||
} else {
|
||||
jobIdValue = json['jobId']?.toString() ?? '';
|
||||
}
|
||||
|
||||
return CargoItem(
|
||||
id: idValue,
|
||||
jobId: jobIdValue,
|
||||
description: _readString(json['description']),
|
||||
quantity: json['quantity'] is num ? json['quantity'].toInt() : 0,
|
||||
weightKg: json['weightKg'] is num ? json['weightKg'].toDouble() : 0.0,
|
||||
lengthCm: json['lengthMm'] is num ? json['lengthMm'].toDouble() : 0.0,
|
||||
widthCm: json['widthMm'] is num ? json['widthMm'].toDouble() : 0.0,
|
||||
heightCm: json['heightMm'] is num ? json['heightMm'].toDouble() : 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'description': description,
|
||||
'quantity': quantity,
|
||||
'weightKg': weightKg,
|
||||
'lengthMm': lengthCm,
|
||||
'widthMm': widthCm,
|
||||
'heightMm': heightCm,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CargoItem(id: $id, description: $description, quantity: $quantity)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is CargoItem && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// Get formatted dimensions string for display
|
||||
String get formattedDimensions {
|
||||
return '${lengthCm.toInt()} × ${widthCm.toInt()} × ${heightCm.toInt()} cm';
|
||||
}
|
||||
|
||||
/// Get formatted weight string for display
|
||||
String get formattedWeight {
|
||||
return '${weightKg.toStringAsFixed(1)} kg';
|
||||
}
|
||||
}
|
||||
162
app/lib/models/chat.dart
Normal file
162
app/lib/models/chat.dart
Normal file
@@ -0,0 +1,162 @@
|
||||
import 'chat_message.dart';
|
||||
|
||||
enum ChatType { general, jobSpecific }
|
||||
|
||||
class Chat {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? receiver;
|
||||
final ChatType type;
|
||||
final String? jobId; // only for job-specific chats
|
||||
final String? jobNumber; // only for job-specific chats
|
||||
final List<ChatMessage> messages;
|
||||
final DateTime lastMessageTime;
|
||||
final String lastMessagePreview;
|
||||
|
||||
Chat({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.receiver,
|
||||
required this.type,
|
||||
this.jobId,
|
||||
this.jobNumber,
|
||||
required this.messages,
|
||||
required this.lastMessageTime,
|
||||
required this.lastMessagePreview,
|
||||
});
|
||||
|
||||
factory Chat.fromJson(Map<String, dynamic> json) {
|
||||
final messagesList = json['messages'] as List? ?? [];
|
||||
final messages =
|
||||
messagesList
|
||||
.map(
|
||||
(messageJson) =>
|
||||
ChatMessage.fromJson(messageJson as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return Chat(
|
||||
id: json['id']?.toString() ?? '',
|
||||
title: json['title']?.toString() ?? '',
|
||||
receiver: json['receiver']?.toString(),
|
||||
type:
|
||||
json['type'] == 'jobSpecific'
|
||||
? ChatType.jobSpecific
|
||||
: ChatType.general,
|
||||
jobId: json['jobId']?.toString(),
|
||||
jobNumber: json['jobNumber']?.toString(),
|
||||
messages: messages,
|
||||
lastMessageTime: _resolveLastMessageTime(json, messages),
|
||||
lastMessagePreview: _resolveLastMessagePreview(json, messages),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'receiver': receiver,
|
||||
'type': type == ChatType.jobSpecific ? 'jobSpecific' : 'general',
|
||||
'jobId': jobId,
|
||||
'jobNumber': jobNumber,
|
||||
'messages': messages.map((message) => message.toJson()).toList(),
|
||||
'lastMessageTime': lastMessageTime.toIso8601String(),
|
||||
'lastMessagePreview': lastMessagePreview,
|
||||
};
|
||||
}
|
||||
|
||||
// Factory constructor for general chat
|
||||
factory Chat.general({
|
||||
required String id,
|
||||
required String title,
|
||||
String? receiver,
|
||||
required List<ChatMessage> messages,
|
||||
}) {
|
||||
final lastMessage = messages.isNotEmpty ? messages.last : null;
|
||||
return Chat(
|
||||
id: id,
|
||||
title: title,
|
||||
receiver: receiver,
|
||||
type: ChatType.general,
|
||||
messages: messages,
|
||||
lastMessageTime: lastMessage?.createdAt ?? DateTime.now(),
|
||||
lastMessagePreview:
|
||||
lastMessage != null
|
||||
? _previewForMessage(lastMessage)
|
||||
: 'Noch keine Nachrichten',
|
||||
);
|
||||
}
|
||||
|
||||
// Factory constructor for job-specific chat
|
||||
factory Chat.jobSpecific({
|
||||
required String id,
|
||||
required String jobId,
|
||||
required String jobNumber,
|
||||
String? receiver,
|
||||
required List<ChatMessage> messages,
|
||||
}) {
|
||||
final lastMessage = messages.isNotEmpty ? messages.last : null;
|
||||
return Chat(
|
||||
id: id,
|
||||
title: 'Job $jobNumber',
|
||||
receiver: receiver,
|
||||
type: ChatType.jobSpecific,
|
||||
jobId: jobId,
|
||||
jobNumber: jobNumber,
|
||||
messages: messages,
|
||||
lastMessageTime: lastMessage?.createdAt ?? DateTime.now(),
|
||||
lastMessagePreview:
|
||||
lastMessage != null
|
||||
? _previewForMessage(lastMessage)
|
||||
: 'Noch keine Nachrichten',
|
||||
);
|
||||
}
|
||||
|
||||
static DateTime _resolveLastMessageTime(
|
||||
Map<String, dynamic> json,
|
||||
List<ChatMessage> messages,
|
||||
) {
|
||||
if (messages.isNotEmpty) {
|
||||
return messages.last.createdAt;
|
||||
}
|
||||
final raw = json['lastMessageTime']?.toString();
|
||||
if (raw != null) {
|
||||
final parsed = DateTime.tryParse(raw);
|
||||
if (parsed != null) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
static String _resolveLastMessagePreview(
|
||||
Map<String, dynamic> json,
|
||||
List<ChatMessage> messages,
|
||||
) {
|
||||
if (messages.isNotEmpty) {
|
||||
return _previewForMessage(messages.last);
|
||||
}
|
||||
return json['lastMessagePreview']?.toString() ?? 'Noch keine Nachrichten';
|
||||
}
|
||||
|
||||
static String _previewForMessage(ChatMessage message) {
|
||||
if (message.contentType == ChatContentType.image) {
|
||||
return '[Bild]';
|
||||
}
|
||||
return message.content;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Chat(id: $id, title: $title, type: $type, jobId: $jobId, messagesCount: ${messages.length})';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Chat && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
211
app/lib/models/chat_message.dart
Normal file
211
app/lib/models/chat_message.dart
Normal file
@@ -0,0 +1,211 @@
|
||||
import 'package:votianlt_app/services/developer.dart' as developer;
|
||||
|
||||
enum ChatDirection { incoming, outgoing }
|
||||
|
||||
enum ChatMessageType { general, jobRelated }
|
||||
|
||||
enum ChatContentType { text, image }
|
||||
|
||||
ChatDirection chatDirectionFromString(
|
||||
String? value, {
|
||||
ChatDirection fallback = ChatDirection.incoming,
|
||||
}) {
|
||||
switch (value?.toUpperCase()) {
|
||||
case 'CLIENT':
|
||||
case 'OUTGOING':
|
||||
return ChatDirection.outgoing;
|
||||
case 'SERVER':
|
||||
case 'INCOMING':
|
||||
return ChatDirection.incoming;
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
String chatDirectionToString(ChatDirection direction) {
|
||||
return direction == ChatDirection.outgoing ? 'CLIENT' : 'SERVER';
|
||||
}
|
||||
|
||||
ChatMessageType chatMessageTypeFromString(
|
||||
String? value, {
|
||||
ChatMessageType fallback = ChatMessageType.general,
|
||||
}) {
|
||||
switch (value?.toUpperCase()) {
|
||||
case 'JOB_RELATED':
|
||||
return ChatMessageType.jobRelated;
|
||||
case 'GENERAL':
|
||||
return ChatMessageType.general;
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
String chatMessageTypeToString(ChatMessageType type) {
|
||||
return type == ChatMessageType.jobRelated ? 'JOB_RELATED' : 'GENERAL';
|
||||
}
|
||||
|
||||
ChatContentType chatContentTypeFromString(
|
||||
String? value, {
|
||||
ChatContentType fallback = ChatContentType.text,
|
||||
}) {
|
||||
switch (value?.toUpperCase()) {
|
||||
case 'IMAGE':
|
||||
return ChatContentType.image;
|
||||
case 'TEXT':
|
||||
return ChatContentType.text;
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
String chatContentTypeToString(ChatContentType type) {
|
||||
return type == ChatContentType.image ? 'IMAGE' : 'TEXT';
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final String id;
|
||||
final String content;
|
||||
final DateTime createdAt;
|
||||
final ChatDirection direction;
|
||||
final ChatMessageType messageType;
|
||||
final ChatContentType contentType;
|
||||
final String? jobId;
|
||||
final String? jobNumber;
|
||||
final bool read;
|
||||
final bool pendingSync;
|
||||
|
||||
const ChatMessage({
|
||||
required this.id,
|
||||
required this.content,
|
||||
required this.createdAt,
|
||||
required this.direction,
|
||||
required this.messageType,
|
||||
this.contentType = ChatContentType.text,
|
||||
this.jobId,
|
||||
this.jobNumber,
|
||||
this.read = false,
|
||||
this.pendingSync = false,
|
||||
});
|
||||
|
||||
factory ChatMessage.fromJson(Map<String, dynamic> json) {
|
||||
final rawId = (json['messageId'] ?? json['id'] ?? '').toString();
|
||||
final rawContent = (json['content'] ?? json['text'] ?? '').toString();
|
||||
final rawContentType = json['contentType']?.toString();
|
||||
|
||||
DateTime createdAt;
|
||||
final createdAtRaw = json['createdAt'] ?? json['timestamp'];
|
||||
if (createdAtRaw is DateTime) {
|
||||
createdAt = createdAtRaw;
|
||||
} else {
|
||||
createdAt =
|
||||
DateTime.tryParse(createdAtRaw?.toString() ?? '') ?? DateTime.now();
|
||||
}
|
||||
|
||||
var direction = chatDirectionFromString(
|
||||
json['origin']?.toString() ?? json['direction']?.toString(),
|
||||
fallback:
|
||||
json['isOwn'] == true
|
||||
? ChatDirection.outgoing
|
||||
: ChatDirection.incoming,
|
||||
);
|
||||
|
||||
var messageType = chatMessageTypeFromString(
|
||||
json['messageType']?.toString(),
|
||||
fallback:
|
||||
((json['jobId']?.toString().isNotEmpty ?? false) ||
|
||||
(json['jobNumber']?.toString().isNotEmpty ?? false))
|
||||
? ChatMessageType.jobRelated
|
||||
: ChatMessageType.general,
|
||||
);
|
||||
|
||||
final jobIdRaw = json['jobId']?.toString();
|
||||
final jobNumberRaw = json['jobNumber']?.toString();
|
||||
final jobId = jobIdRaw?.trim();
|
||||
final jobNumber = jobNumberRaw?.trim();
|
||||
|
||||
if (rawId.isEmpty || rawContent.isEmpty) {
|
||||
developer.log(
|
||||
'Invalid ChatMessage payload (one or more required fields missing): $json',
|
||||
);
|
||||
}
|
||||
|
||||
return ChatMessage(
|
||||
id: rawId,
|
||||
content: rawContent,
|
||||
createdAt: createdAt,
|
||||
direction: direction,
|
||||
messageType: messageType,
|
||||
contentType: chatContentTypeFromString(rawContentType),
|
||||
jobId: jobId?.isEmpty ?? true ? null : jobId,
|
||||
jobNumber: jobNumber?.isEmpty ?? true ? null : jobNumber,
|
||||
read: json['read'] == true,
|
||||
pendingSync: json['pendingSync'] == true,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{
|
||||
'messageId': id,
|
||||
'content': content,
|
||||
'origin': chatDirectionToString(direction),
|
||||
'messageType': chatMessageTypeToString(messageType),
|
||||
'contentType': chatContentTypeToString(contentType),
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'read': read,
|
||||
};
|
||||
|
||||
if (jobId != null && jobId!.isNotEmpty) {
|
||||
map['jobId'] = jobId;
|
||||
}
|
||||
if (jobNumber != null && jobNumber!.isNotEmpty) {
|
||||
map['jobNumber'] = jobNumber;
|
||||
}
|
||||
if (pendingSync) {
|
||||
map['pendingSync'] = true;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
bool get isOwn => direction == ChatDirection.outgoing;
|
||||
|
||||
ChatMessage copyWith({
|
||||
String? id,
|
||||
String? content,
|
||||
DateTime? createdAt,
|
||||
ChatDirection? direction,
|
||||
ChatMessageType? messageType,
|
||||
ChatContentType? contentType,
|
||||
String? jobId,
|
||||
String? jobNumber,
|
||||
bool? read,
|
||||
bool? pendingSync,
|
||||
}) {
|
||||
return ChatMessage(
|
||||
id: id ?? this.id,
|
||||
content: content ?? this.content,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
direction: direction ?? this.direction,
|
||||
messageType: messageType ?? this.messageType,
|
||||
contentType: contentType ?? this.contentType,
|
||||
jobId: jobId ?? this.jobId,
|
||||
jobNumber: jobNumber ?? this.jobNumber,
|
||||
read: read ?? this.read,
|
||||
pendingSync: pendingSync ?? this.pendingSync,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatMessage(id: $id, direction: $direction, messageType: $messageType, contentType: $contentType, createdAt: $createdAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is ChatMessage && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
136
app/lib/models/delivery_station.dart
Normal file
136
app/lib/models/delivery_station.dart
Normal file
@@ -0,0 +1,136 @@
|
||||
import 'task.dart';
|
||||
|
||||
class DeliveryStation {
|
||||
final int stationOrder;
|
||||
final String company;
|
||||
final String? salutation;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String phone;
|
||||
final String street;
|
||||
final String houseNumber;
|
||||
final String addressAddition;
|
||||
final String zip;
|
||||
final String city;
|
||||
final String deliveryDate;
|
||||
final String deliveryTime;
|
||||
final List<Task> tasks;
|
||||
|
||||
DeliveryStation({
|
||||
required this.stationOrder,
|
||||
required this.company,
|
||||
this.salutation,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.phone,
|
||||
required this.street,
|
||||
required this.houseNumber,
|
||||
required this.addressAddition,
|
||||
required this.zip,
|
||||
required this.city,
|
||||
required this.deliveryDate,
|
||||
required this.deliveryTime,
|
||||
required this.tasks,
|
||||
});
|
||||
|
||||
static String _readString(dynamic value, {String fallback = ''}) {
|
||||
if (value is String) {
|
||||
return value;
|
||||
}
|
||||
if (value is num || value is bool) {
|
||||
return value.toString();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
factory DeliveryStation.fromJson(Map<String, dynamic> json) {
|
||||
final stationOrder =
|
||||
json['stationOrder'] is num
|
||||
? (json['stationOrder'] as num).toInt()
|
||||
: int.tryParse(json['stationOrder']?.toString() ?? '') ?? 0;
|
||||
|
||||
final tasks =
|
||||
(json['tasks'] as List? ?? const []).map((rawTask) {
|
||||
final taskJson = Map<String, dynamic>.from(rawTask as Map);
|
||||
taskJson['stationOrder'] ??= stationOrder;
|
||||
return Task.fromJson(taskJson);
|
||||
}).toList()
|
||||
..sort((a, b) => (a.taskOrder ?? 0).compareTo(b.taskOrder ?? 0));
|
||||
|
||||
return DeliveryStation(
|
||||
stationOrder: stationOrder,
|
||||
company: _readString(json['company']),
|
||||
salutation: json['salutation']?.toString(),
|
||||
firstName: _readString(json['firstName']),
|
||||
lastName: _readString(json['lastName']),
|
||||
phone: _readString(json['phone']),
|
||||
street: _readString(json['street']),
|
||||
houseNumber: _readString(json['houseNumber']),
|
||||
addressAddition: _readString(json['addressAddition']),
|
||||
zip: _readString(json['zip']),
|
||||
city: _readString(json['city']),
|
||||
deliveryDate: _readString(json['deliveryDate']),
|
||||
deliveryTime: _readString(json['deliveryTime']),
|
||||
tasks: tasks,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'stationOrder': stationOrder,
|
||||
'company': company,
|
||||
'salutation': salutation,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'phone': phone,
|
||||
'street': street,
|
||||
'houseNumber': houseNumber,
|
||||
'addressAddition': addressAddition,
|
||||
'zip': zip,
|
||||
'city': city,
|
||||
'deliveryDate': deliveryDate,
|
||||
'deliveryTime': deliveryTime,
|
||||
'tasks': tasks.map((task) => task.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
DeliveryStation normalized() {
|
||||
String t(String? value) => (value ?? '').trim();
|
||||
return DeliveryStation(
|
||||
stationOrder: stationOrder,
|
||||
company: t(company),
|
||||
salutation: t(salutation),
|
||||
firstName: t(firstName),
|
||||
lastName: t(lastName),
|
||||
phone: t(phone),
|
||||
street: t(street),
|
||||
houseNumber: t(houseNumber),
|
||||
addressAddition: t(addressAddition),
|
||||
zip: t(zip),
|
||||
city: t(city),
|
||||
deliveryDate: t(deliveryDate),
|
||||
deliveryTime: t(deliveryTime),
|
||||
tasks: tasks,
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
final name = [
|
||||
firstName.trim(),
|
||||
lastName.trim(),
|
||||
].where((part) => part.isNotEmpty).join(' ');
|
||||
return name.isNotEmpty ? name : company;
|
||||
}
|
||||
|
||||
String get formattedAddress {
|
||||
final streetPart = [
|
||||
street.trim(),
|
||||
houseNumber.trim(),
|
||||
].where((part) => part.isNotEmpty).join(' ');
|
||||
final cityPart = [
|
||||
zip.trim(),
|
||||
city.trim(),
|
||||
].where((part) => part.isNotEmpty).join(' ');
|
||||
return [streetPart, cityPart].where((part) => part.isNotEmpty).join(', ');
|
||||
}
|
||||
}
|
||||
542
app/lib/models/job.dart
Normal file
542
app/lib/models/job.dart
Normal file
@@ -0,0 +1,542 @@
|
||||
import 'cargo_item.dart';
|
||||
import 'delivery_station.dart';
|
||||
import 'task.dart';
|
||||
|
||||
class Job {
|
||||
final String id; // Will store the timestamp as string
|
||||
final String jobNumber;
|
||||
final String status;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String createdBy;
|
||||
final String customerSelection;
|
||||
final String pickupCompany;
|
||||
final String? pickupSalutation;
|
||||
final String pickupFirstName;
|
||||
final String pickupLastName;
|
||||
final String pickupPhone;
|
||||
final String pickupStreet;
|
||||
final String pickupHouseNumber;
|
||||
final String pickupAddressAddition;
|
||||
final String pickupZip;
|
||||
final String pickupCity;
|
||||
final String deliveryCompany;
|
||||
final String? deliverySalutation;
|
||||
final String deliveryFirstName;
|
||||
final String deliveryLastName;
|
||||
final String deliveryPhone;
|
||||
final String deliveryStreet;
|
||||
final String deliveryHouseNumber;
|
||||
final String deliveryAddressAddition;
|
||||
final String deliveryZip;
|
||||
final String deliveryCity;
|
||||
final bool digitalProcessing;
|
||||
final String appUser;
|
||||
final String pickupDate;
|
||||
final String pickupTime;
|
||||
final String deliveryDate;
|
||||
final String deliveryTime;
|
||||
final String remark;
|
||||
final double price;
|
||||
final bool draft;
|
||||
|
||||
// New fields for cargoItems and tasks
|
||||
final List<CargoItem> cargoItems;
|
||||
final List<DeliveryStation> deliveryStations;
|
||||
final List<Task> tasks;
|
||||
final String deliveryCitiesDisplay;
|
||||
final String firstDeliveryCity;
|
||||
final String lastDeliveryCity;
|
||||
|
||||
// Legacy fields for backward compatibility
|
||||
final String title;
|
||||
final String description;
|
||||
final String priority;
|
||||
final DateTime? dueDate;
|
||||
final String? assignedTo;
|
||||
final String? location;
|
||||
final Map<String, dynamic>? additionalData;
|
||||
|
||||
Job({
|
||||
required this.id,
|
||||
required this.jobNumber,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.createdBy,
|
||||
required this.customerSelection,
|
||||
required this.pickupCompany,
|
||||
this.pickupSalutation,
|
||||
required this.pickupFirstName,
|
||||
required this.pickupLastName,
|
||||
required this.pickupPhone,
|
||||
required this.pickupStreet,
|
||||
required this.pickupHouseNumber,
|
||||
required this.pickupAddressAddition,
|
||||
required this.pickupZip,
|
||||
required this.pickupCity,
|
||||
required this.deliveryCompany,
|
||||
this.deliverySalutation,
|
||||
required this.deliveryFirstName,
|
||||
required this.deliveryLastName,
|
||||
required this.deliveryPhone,
|
||||
required this.deliveryStreet,
|
||||
required this.deliveryHouseNumber,
|
||||
required this.deliveryAddressAddition,
|
||||
required this.deliveryZip,
|
||||
required this.deliveryCity,
|
||||
required this.digitalProcessing,
|
||||
required this.appUser,
|
||||
required this.pickupDate,
|
||||
required this.pickupTime,
|
||||
required this.deliveryDate,
|
||||
required this.deliveryTime,
|
||||
required this.remark,
|
||||
required this.price,
|
||||
required this.draft,
|
||||
// New fields for cargoItems and tasks
|
||||
required this.cargoItems,
|
||||
required this.tasks,
|
||||
this.deliveryStations = const [],
|
||||
this.deliveryCitiesDisplay = '',
|
||||
this.firstDeliveryCity = '',
|
||||
this.lastDeliveryCity = '',
|
||||
// Legacy fields
|
||||
String? title,
|
||||
String? description,
|
||||
this.priority = 'normal',
|
||||
this.dueDate,
|
||||
this.assignedTo,
|
||||
this.location,
|
||||
this.additionalData,
|
||||
}) : title = title ?? 'Job $jobNumber',
|
||||
description =
|
||||
description ?? 'Transport von $pickupCity nach $deliveryCity';
|
||||
|
||||
/// Parse DateTime from either string or array format
|
||||
static DateTime? _parseDateTime(dynamic value) {
|
||||
if (value == null) return null;
|
||||
|
||||
if (value is String) {
|
||||
return DateTime.tryParse(value);
|
||||
}
|
||||
|
||||
if (value is List && value.isNotEmpty) {
|
||||
try {
|
||||
// Array format: [year, month, day, hour, minute, second, nanosecond]
|
||||
final year = value[0] as int;
|
||||
final month = value.length > 1 ? value[1] as int : 1;
|
||||
final day = value.length > 2 ? value[2] as int : 1;
|
||||
final hour = value.length > 3 ? value[3] as int : 0;
|
||||
final minute = value.length > 4 ? value[4] as int : 0;
|
||||
final second = value.length > 5 ? value[5] as int : 0;
|
||||
final nanosecond = value.length > 6 ? value[6] as int : 0;
|
||||
|
||||
// Convert nanoseconds to microseconds (divide by 1000)
|
||||
final microsecond = nanosecond ~/ 1000;
|
||||
|
||||
return DateTime(year, month, day, hour, minute, second, microsecond);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Parse time string (for pickupTime/deliveryTime)
|
||||
static String? _parseTimeString(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is String && value.isNotEmpty && value != 'null') {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Parse date string from either string or array format (for pickupDate/deliveryDate)
|
||||
static String? _parseDateString(dynamic value) {
|
||||
if (value == null) return null;
|
||||
|
||||
if (value is String) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value is List && value.isNotEmpty) {
|
||||
try {
|
||||
// Array format: [year, month, day]
|
||||
final year = value[0] as int;
|
||||
final month = value.length > 1 ? value[1] as int : 1;
|
||||
final day = value.length > 2 ? value[2] as int : 1;
|
||||
|
||||
// Format as ISO date string
|
||||
return '${year.toString().padLeft(4, '0')}-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static String _readString(dynamic value, {String fallback = ''}) {
|
||||
if (value is String) {
|
||||
return value;
|
||||
}
|
||||
if (value is num || value is bool) {
|
||||
return value.toString();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
factory Job.fromJson(Map<String, dynamic> json) {
|
||||
// Support both flat structure and { job: {...}, cargoItems: [...], tasks: [...] }
|
||||
final jobJson =
|
||||
(json['job'] is Map)
|
||||
? Map<String, dynamic>.from(json['job'] as Map)
|
||||
: json;
|
||||
|
||||
// Determine the id robustly. Prefer the inner job.id if present.
|
||||
String idValue = '';
|
||||
final dynamic innerId = jobJson['id'];
|
||||
if (innerId is Map<String, dynamic>) {
|
||||
// Some backends send an object; try common fields
|
||||
idValue =
|
||||
innerId['timestamp']?.toString() ??
|
||||
innerId[r'$oid']?.toString() ??
|
||||
'';
|
||||
} else if (innerId != null) {
|
||||
idValue = innerId.toString();
|
||||
}
|
||||
if (idValue.isEmpty) {
|
||||
// Fallback to outer json['id'] if provided
|
||||
final dynamic outerId = json['id'];
|
||||
if (outerId is Map<String, dynamic>) {
|
||||
idValue =
|
||||
outerId['timestamp']?.toString() ??
|
||||
outerId[r'$oid']?.toString() ??
|
||||
'';
|
||||
} else if (outerId != null) {
|
||||
idValue = outerId.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse cargoItems array
|
||||
List<CargoItem> cargoItems = [];
|
||||
if (json['cargoItems'] is List) {
|
||||
cargoItems =
|
||||
(json['cargoItems'] as List)
|
||||
.map(
|
||||
(item) =>
|
||||
CargoItem.fromJson(Map<String, dynamic>.from(item as Map)),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Parse delivery stations and prefer their tasks over the legacy top-level tasks.
|
||||
List<DeliveryStation> deliveryStations = [];
|
||||
final deliveryStationsRaw =
|
||||
jobJson['deliveryStations'] ?? json['deliveryStations'];
|
||||
if (deliveryStationsRaw is List) {
|
||||
deliveryStations =
|
||||
deliveryStationsRaw
|
||||
.map(
|
||||
(station) => DeliveryStation.fromJson(
|
||||
Map<String, dynamic>.from(station as Map),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
..sort((a, b) => a.stationOrder.compareTo(b.stationOrder));
|
||||
}
|
||||
|
||||
int compareTasks(Task a, Task b) {
|
||||
final stationCompare = (a.stationOrder ?? -1).compareTo(
|
||||
b.stationOrder ?? -1,
|
||||
);
|
||||
if (stationCompare != 0) {
|
||||
return stationCompare;
|
||||
}
|
||||
return (a.taskOrder ?? 0).compareTo(b.taskOrder ?? 0);
|
||||
}
|
||||
|
||||
// Parse tasks array
|
||||
List<Task> tasks = [];
|
||||
if (deliveryStations.isNotEmpty) {
|
||||
tasks =
|
||||
deliveryStations.expand((station) => station.tasks).toList()
|
||||
..sort(compareTasks);
|
||||
} else if (json['tasks'] is List) {
|
||||
tasks =
|
||||
(json['tasks'] as List)
|
||||
.map(
|
||||
(task) => Task.fromJson(Map<String, dynamic>.from(task as Map)),
|
||||
)
|
||||
.toList()
|
||||
..sort(compareTasks);
|
||||
} else if (jobJson['tasks'] is List) {
|
||||
tasks =
|
||||
(jobJson['tasks'] as List)
|
||||
.map(
|
||||
(task) => Task.fromJson(Map<String, dynamic>.from(task as Map)),
|
||||
)
|
||||
.toList()
|
||||
..sort(compareTasks);
|
||||
}
|
||||
|
||||
// As a last resort, derive a deterministic id if still empty (avoid UNIQUE '' collisions)
|
||||
if (idValue.isEmpty) {
|
||||
final jobNumber = jobJson['jobNumber']?.toString();
|
||||
final createdAt = jobJson['createdAt']?.toString();
|
||||
idValue =
|
||||
(jobNumber?.isNotEmpty == true)
|
||||
? 'jobnum:$jobNumber'
|
||||
: (createdAt?.isNotEmpty == true
|
||||
? 'ts:${createdAt!}'
|
||||
: DateTime.now().millisecondsSinceEpoch.toString());
|
||||
}
|
||||
|
||||
return Job(
|
||||
id: idValue,
|
||||
jobNumber: jobJson['jobNumber']?.toString() ?? '',
|
||||
status: jobJson['status']?.toString() ?? 'UNKNOWN',
|
||||
createdAt: _parseDateTime(jobJson['createdAt']) ?? DateTime.now(),
|
||||
updatedAt: _parseDateTime(jobJson['updatedAt']) ?? DateTime.now(),
|
||||
createdBy: jobJson['createdBy']?.toString() ?? '',
|
||||
customerSelection: jobJson['customerSelection']?.toString() ?? '',
|
||||
pickupCompany: jobJson['pickupCompany']?.toString() ?? '',
|
||||
pickupSalutation: jobJson['pickupSalutation']?.toString(),
|
||||
pickupFirstName: jobJson['pickupFirstName']?.toString() ?? '',
|
||||
pickupLastName: jobJson['pickupLastName']?.toString() ?? '',
|
||||
pickupPhone: jobJson['pickupPhone']?.toString() ?? '',
|
||||
pickupStreet: jobJson['pickupStreet']?.toString() ?? '',
|
||||
pickupHouseNumber: jobJson['pickupHouseNumber']?.toString() ?? '',
|
||||
pickupAddressAddition: jobJson['pickupAddressAddition']?.toString() ?? '',
|
||||
pickupZip: jobJson['pickupZip']?.toString() ?? '',
|
||||
pickupCity: jobJson['pickupCity']?.toString() ?? '',
|
||||
deliveryCompany: jobJson['deliveryCompany']?.toString() ?? '',
|
||||
deliverySalutation: jobJson['deliverySalutation']?.toString(),
|
||||
deliveryFirstName: jobJson['deliveryFirstName']?.toString() ?? '',
|
||||
deliveryLastName: jobJson['deliveryLastName']?.toString() ?? '',
|
||||
deliveryPhone: jobJson['deliveryPhone']?.toString() ?? '',
|
||||
deliveryStreet: jobJson['deliveryStreet']?.toString() ?? '',
|
||||
deliveryHouseNumber: jobJson['deliveryHouseNumber']?.toString() ?? '',
|
||||
deliveryAddressAddition:
|
||||
jobJson['deliveryAddressAddition']?.toString() ?? '',
|
||||
deliveryZip: jobJson['deliveryZip']?.toString() ?? '',
|
||||
deliveryCity: jobJson['deliveryCity']?.toString() ?? '',
|
||||
digitalProcessing: jobJson['digitalProcessing'] == true,
|
||||
appUser: jobJson['appUser']?.toString() ?? '',
|
||||
pickupDate: _parseDateString(jobJson['pickupDate']) ?? '',
|
||||
pickupTime: _parseTimeString(jobJson['pickupTime']) ?? '',
|
||||
deliveryDate: _parseDateString(jobJson['deliveryDate']) ?? '',
|
||||
deliveryTime: _parseTimeString(jobJson['deliveryTime']) ?? '',
|
||||
remark: _readString(jobJson['remark']),
|
||||
price: (jobJson['price'] is num) ? jobJson['price'].toDouble() : 0.0,
|
||||
draft: jobJson['draft'] == true,
|
||||
// New fields for cargoItems and tasks
|
||||
cargoItems: cargoItems,
|
||||
deliveryStations: deliveryStations,
|
||||
tasks: tasks,
|
||||
deliveryCitiesDisplay: _readString(jobJson['deliveryCitiesDisplay']),
|
||||
firstDeliveryCity: _readString(jobJson['firstDeliveryCity']),
|
||||
lastDeliveryCity: _readString(jobJson['lastDeliveryCity']),
|
||||
// Legacy fields for backward compatibility
|
||||
title: jobJson['title']?.toString(),
|
||||
description: jobJson['description']?.toString(),
|
||||
priority: jobJson['priority']?.toString() ?? 'normal',
|
||||
dueDate: _parseDateTime(jobJson['dueDate']),
|
||||
assignedTo: jobJson['assignedTo']?.toString(),
|
||||
location: jobJson['location']?.toString(),
|
||||
additionalData: jobJson['additionalData'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Return a normalized copy (trim strings, null→'')
|
||||
Job normalized() {
|
||||
String t(String? s) => (s ?? '').trim();
|
||||
return Job(
|
||||
id: id,
|
||||
jobNumber: t(jobNumber),
|
||||
status: t(status),
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
createdBy: t(createdBy),
|
||||
customerSelection: t(customerSelection),
|
||||
pickupCompany: t(pickupCompany),
|
||||
pickupSalutation: t(pickupSalutation),
|
||||
pickupFirstName: t(pickupFirstName),
|
||||
pickupLastName: t(pickupLastName),
|
||||
pickupPhone: t(pickupPhone),
|
||||
pickupStreet: t(pickupStreet),
|
||||
pickupHouseNumber: t(pickupHouseNumber),
|
||||
pickupAddressAddition: t(pickupAddressAddition),
|
||||
pickupZip: t(pickupZip),
|
||||
pickupCity: t(pickupCity),
|
||||
deliveryCompany: t(deliveryCompany),
|
||||
deliverySalutation: t(deliverySalutation),
|
||||
deliveryFirstName: t(deliveryFirstName),
|
||||
deliveryLastName: t(deliveryLastName),
|
||||
deliveryPhone: t(deliveryPhone),
|
||||
deliveryStreet: t(deliveryStreet),
|
||||
deliveryHouseNumber: t(deliveryHouseNumber),
|
||||
deliveryAddressAddition: t(deliveryAddressAddition),
|
||||
deliveryZip: t(deliveryZip),
|
||||
deliveryCity: t(deliveryCity),
|
||||
digitalProcessing: digitalProcessing,
|
||||
appUser: t(appUser),
|
||||
pickupDate: t(pickupDate),
|
||||
pickupTime: t(pickupTime),
|
||||
deliveryDate: t(deliveryDate),
|
||||
deliveryTime: t(deliveryTime),
|
||||
remark: t(remark),
|
||||
price: price,
|
||||
draft: draft,
|
||||
cargoItems: cargoItems,
|
||||
deliveryStations:
|
||||
deliveryStations.map((station) => station.normalized()).toList(),
|
||||
tasks: tasks,
|
||||
deliveryCitiesDisplay: t(deliveryCitiesDisplay),
|
||||
firstDeliveryCity: t(firstDeliveryCity),
|
||||
lastDeliveryCity: t(lastDeliveryCity),
|
||||
title: t(title),
|
||||
description: t(description),
|
||||
priority: t(priority),
|
||||
dueDate: dueDate,
|
||||
assignedTo: t(assignedTo),
|
||||
location: t(location),
|
||||
additionalData: additionalData,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobNumber': jobNumber,
|
||||
'status': status,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'createdBy': createdBy,
|
||||
'customerSelection': customerSelection,
|
||||
'pickupCompany': pickupCompany,
|
||||
'pickupSalutation': pickupSalutation,
|
||||
'pickupFirstName': pickupFirstName,
|
||||
'pickupLastName': pickupLastName,
|
||||
'pickupPhone': pickupPhone,
|
||||
'pickupStreet': pickupStreet,
|
||||
'pickupHouseNumber': pickupHouseNumber,
|
||||
'pickupAddressAddition': pickupAddressAddition,
|
||||
'pickupZip': pickupZip,
|
||||
'pickupCity': pickupCity,
|
||||
'deliveryCompany': deliveryCompany,
|
||||
'deliverySalutation': deliverySalutation,
|
||||
'deliveryFirstName': deliveryFirstName,
|
||||
'deliveryLastName': deliveryLastName,
|
||||
'deliveryPhone': deliveryPhone,
|
||||
'deliveryStreet': deliveryStreet,
|
||||
'deliveryHouseNumber': deliveryHouseNumber,
|
||||
'deliveryAddressAddition': deliveryAddressAddition,
|
||||
'deliveryZip': deliveryZip,
|
||||
'deliveryCity': deliveryCity,
|
||||
'digitalProcessing': digitalProcessing,
|
||||
'appUser': appUser,
|
||||
'pickupDate': pickupDate,
|
||||
'pickupTime': pickupTime,
|
||||
'deliveryDate': deliveryDate,
|
||||
'deliveryTime': deliveryTime,
|
||||
'remark': remark,
|
||||
'price': price,
|
||||
'draft': draft,
|
||||
// New fields for cargoItems and tasks
|
||||
'cargoItems': cargoItems.map((item) => item.toJson()).toList(),
|
||||
'deliveryStations':
|
||||
deliveryStations.map((station) => station.toJson()).toList(),
|
||||
'tasks': tasks.map((task) => task.toJson()).toList(),
|
||||
'deliveryCitiesDisplay': deliveryCitiesDisplay,
|
||||
'firstDeliveryCity': firstDeliveryCity,
|
||||
'lastDeliveryCity': lastDeliveryCity,
|
||||
// Legacy fields
|
||||
'title': title,
|
||||
'description': description,
|
||||
'priority': priority,
|
||||
'dueDate': dueDate?.toIso8601String(),
|
||||
'assignedTo': assignedTo,
|
||||
'location': location,
|
||||
'additionalData': additionalData,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Job(id: $id, title: $title, status: $status, priority: $priority)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Job && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// Get status color for UI display
|
||||
String get statusColor {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'created':
|
||||
return 'orange';
|
||||
case 'pending':
|
||||
case 'assigned':
|
||||
return 'orange';
|
||||
case 'in_progress':
|
||||
case 'started':
|
||||
return 'blue';
|
||||
case 'completed':
|
||||
case 'done':
|
||||
return 'green';
|
||||
case 'cancelled':
|
||||
case 'failed':
|
||||
return 'red';
|
||||
default:
|
||||
return 'grey';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status display text in German
|
||||
String get statusDisplayText {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'created':
|
||||
return 'Erstellt';
|
||||
case 'pending':
|
||||
return 'Wartend';
|
||||
case 'assigned':
|
||||
return 'Zugewiesen';
|
||||
case 'in_progress':
|
||||
case 'started':
|
||||
return 'In Bearbeitung';
|
||||
case 'completed':
|
||||
case 'done':
|
||||
return 'Abgeschlossen';
|
||||
case 'cancelled':
|
||||
return 'Abgebrochen';
|
||||
case 'failed':
|
||||
return 'Fehlgeschlagen';
|
||||
default:
|
||||
return status; // Show the original status if not mapped
|
||||
}
|
||||
}
|
||||
|
||||
/// Get priority display text in German
|
||||
String get priorityDisplayText {
|
||||
switch (priority.toLowerCase()) {
|
||||
case 'low':
|
||||
return 'Niedrig';
|
||||
case 'normal':
|
||||
return 'Normal';
|
||||
case 'high':
|
||||
return 'Hoch';
|
||||
case 'urgent':
|
||||
return 'Dringend';
|
||||
default:
|
||||
return 'Normal';
|
||||
}
|
||||
}
|
||||
}
|
||||
90
app/lib/models/message_envelope.dart
Normal file
90
app/lib/models/message_envelope.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
/// Message Envelope for wrapping all WebSocket messages with metadata
|
||||
///
|
||||
/// This provides reliable message delivery with acknowledgment mechanism
|
||||
class MessageEnvelope {
|
||||
/// Unique message identifier (UUID)
|
||||
final String messageId;
|
||||
|
||||
/// Timestamp when the message was created
|
||||
final DateTime timestamp;
|
||||
|
||||
/// Target topic
|
||||
final String topic;
|
||||
|
||||
/// Original message payload (can be Map or List)
|
||||
final dynamic payload;
|
||||
|
||||
/// Whether this message requires acknowledgment
|
||||
final bool requiresAck;
|
||||
|
||||
/// Number of retry attempts (for tracking)
|
||||
final int retryCount;
|
||||
|
||||
/// Optional expiration timestamp
|
||||
final DateTime? expiresAt;
|
||||
|
||||
MessageEnvelope({
|
||||
required this.messageId,
|
||||
required this.timestamp,
|
||||
required this.topic,
|
||||
required this.payload,
|
||||
this.requiresAck = true,
|
||||
this.retryCount = 0,
|
||||
this.expiresAt,
|
||||
});
|
||||
|
||||
/// Create MessageEnvelope from JSON
|
||||
factory MessageEnvelope.fromJson(Map<String, dynamic> json) {
|
||||
return MessageEnvelope(
|
||||
messageId: json['messageId'] as String,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
topic: json['topic'] as String,
|
||||
payload: json['payload'],
|
||||
requiresAck: json['requiresAck'] as bool? ?? true,
|
||||
retryCount: json['retryCount'] as int? ?? 0,
|
||||
expiresAt: json['expiresAt'] != null
|
||||
? DateTime.parse(json['expiresAt'] as String)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert MessageEnvelope to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'messageId': messageId,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'topic': topic,
|
||||
'payload': payload,
|
||||
'requiresAck': requiresAck,
|
||||
'retryCount': retryCount,
|
||||
if (expiresAt != null) 'expiresAt': expiresAt!.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a copy with updated fields
|
||||
MessageEnvelope copyWith({
|
||||
String? messageId,
|
||||
DateTime? timestamp,
|
||||
String? topic,
|
||||
dynamic payload,
|
||||
bool? requiresAck,
|
||||
int? retryCount,
|
||||
DateTime? expiresAt,
|
||||
}) {
|
||||
return MessageEnvelope(
|
||||
messageId: messageId ?? this.messageId,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
topic: topic ?? this.topic,
|
||||
payload: payload ?? this.payload,
|
||||
requiresAck: requiresAck ?? this.requiresAck,
|
||||
retryCount: retryCount ?? this.retryCount,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MessageEnvelope(messageId: $messageId, topic: $topic, requiresAck: $requiresAck, retryCount: $retryCount)';
|
||||
}
|
||||
}
|
||||
|
||||
51
app/lib/models/queued_message.dart
Normal file
51
app/lib/models/queued_message.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
class QueuedMessage {
|
||||
final String id;
|
||||
final String topic;
|
||||
final Map<String, dynamic> payload;
|
||||
final DateTime createdAt;
|
||||
final int retryCount;
|
||||
|
||||
QueuedMessage({
|
||||
required this.id,
|
||||
required this.topic,
|
||||
required this.payload,
|
||||
required this.createdAt,
|
||||
this.retryCount = 0,
|
||||
});
|
||||
|
||||
factory QueuedMessage.fromJson(Map<String, dynamic> json) {
|
||||
return QueuedMessage(
|
||||
id: json['id'],
|
||||
topic: json['topic'],
|
||||
payload: Map<String, dynamic>.from(json['payload']),
|
||||
createdAt: DateTime.parse(json['createdAt']),
|
||||
retryCount: json['retryCount'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'topic': topic,
|
||||
'payload': payload,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'retryCount': retryCount,
|
||||
};
|
||||
}
|
||||
|
||||
QueuedMessage copyWith({
|
||||
String? id,
|
||||
String? topic,
|
||||
Map<String, dynamic>? payload,
|
||||
DateTime? createdAt,
|
||||
int? retryCount,
|
||||
}) {
|
||||
return QueuedMessage(
|
||||
id: id ?? this.id,
|
||||
topic: topic ?? this.topic,
|
||||
payload: payload ?? this.payload,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
retryCount: retryCount ?? this.retryCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
20
app/lib/models/remark_translation.dart
Normal file
20
app/lib/models/remark_translation.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
/// Represents a translated remark in a specific language
|
||||
class RemarkTranslation {
|
||||
final String language;
|
||||
final String text;
|
||||
|
||||
RemarkTranslation({required this.language, required this.text});
|
||||
|
||||
factory RemarkTranslation.fromJson(Map<String, dynamic> json) {
|
||||
return RemarkTranslation(language: json['language']?.toString() ?? '', text: json['text']?.toString() ?? '');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'language': language, 'text': text};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RemarkTranslation(language: $language, text: $text)';
|
||||
}
|
||||
}
|
||||
198
app/lib/models/task.dart
Normal file
198
app/lib/models/task.dart
Normal file
@@ -0,0 +1,198 @@
|
||||
// Import all task types
|
||||
import 'tasks/generic_task.dart';
|
||||
import 'tasks/confirmation_task.dart';
|
||||
import 'tasks/photo_task.dart';
|
||||
import 'tasks/todolist_task.dart';
|
||||
import 'tasks/signature_task.dart';
|
||||
import 'tasks/barcode_task.dart';
|
||||
import 'tasks/comment_task.dart';
|
||||
|
||||
abstract class Task {
|
||||
final String id;
|
||||
final String jobId;
|
||||
final int? stationOrder;
|
||||
final bool completed;
|
||||
final bool optional;
|
||||
final DateTime? completedAt;
|
||||
final String? completedBy;
|
||||
final int? taskOrder;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? displayName;
|
||||
|
||||
Task({
|
||||
required this.id,
|
||||
required this.jobId,
|
||||
this.stationOrder,
|
||||
this.completed = false,
|
||||
this.optional = false,
|
||||
this.completedAt,
|
||||
this.completedBy,
|
||||
this.taskOrder,
|
||||
this.title,
|
||||
this.description,
|
||||
this.displayName,
|
||||
});
|
||||
|
||||
factory Task.fromJson(Map<String, dynamic> json) {
|
||||
// Get task specific data to determine task type
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>?;
|
||||
final taskType =
|
||||
(taskSpecificData?['taskType'] ?? json['taskType'])?.toString();
|
||||
|
||||
// Create specific task type based on taskType
|
||||
switch (taskType) {
|
||||
case 'CONFIRMATION':
|
||||
return ConfirmationTask.fromJson(json);
|
||||
case 'PHOTO':
|
||||
return PhotoTask.fromJson(json);
|
||||
case 'TODOLIST':
|
||||
return TodoListTask.fromJson(json);
|
||||
case 'SIGNATURE':
|
||||
return SignatureTask.fromJson(json);
|
||||
case 'BARCODE':
|
||||
return BarcodeTask.fromJson(json);
|
||||
case 'COMMENT':
|
||||
return CommentTask.fromJson(json);
|
||||
case 'GENERIC':
|
||||
return GenericTask.fromJson(json);
|
||||
default:
|
||||
// Fallback to a generic task if no specific type is found
|
||||
return GenericTask.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
Task copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Task(id: $id, jobId: $jobId, stationOrder: $stationOrder, completed: $completed, taskOrder: $taskOrder)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Task && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// Parse DateTime from either string or array format
|
||||
static DateTime? _parseDateTime(dynamic value) {
|
||||
if (value == null) return null;
|
||||
|
||||
if (value is String) {
|
||||
return DateTime.tryParse(value);
|
||||
}
|
||||
|
||||
if (value is List && value.isNotEmpty) {
|
||||
try {
|
||||
// Array format: [year, month, day, hour, minute, second, microsecond]
|
||||
final year = value[0] as int;
|
||||
final month = value.length > 1 ? value[1] as int : 1;
|
||||
final day = value.length > 2 ? value[2] as int : 1;
|
||||
final hour = value.length > 3 ? value[3] as int : 0;
|
||||
final minute = value.length > 4 ? value[4] as int : 0;
|
||||
final second = value.length > 5 ? value[5] as int : 0;
|
||||
final microsecond = value.length > 6 ? value[6] as int : 0;
|
||||
|
||||
return DateTime(year, month, day, hour, minute, second, microsecond);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String? _readOptionalString(dynamic value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is String) {
|
||||
return value;
|
||||
}
|
||||
if (value is num || value is bool) {
|
||||
return value.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static int? _readOptionalInt(dynamic value) {
|
||||
if (value is int) {
|
||||
return value;
|
||||
}
|
||||
if (value is num) {
|
||||
return value.toInt();
|
||||
}
|
||||
if (value is String) {
|
||||
return int.tryParse(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper method to parse common properties
|
||||
static Map<String, dynamic> parseCommonProperties(Map<String, dynamic> json) {
|
||||
// Parse the complex id object - can be either a Map or a simple string
|
||||
String idValue = '';
|
||||
if (json['id'] is Map) {
|
||||
final idMap = json['id'] as Map<String, dynamic>;
|
||||
idValue = idMap['timestamp']?.toString() ?? '';
|
||||
} else {
|
||||
idValue = json['id']?.toString() ?? '';
|
||||
}
|
||||
|
||||
// Parse the complex jobId object - can be either a Map or a simple string
|
||||
String jobIdValue = '';
|
||||
if (json['jobId'] is Map) {
|
||||
final jobIdMap = json['jobId'] as Map<String, dynamic>;
|
||||
jobIdValue = jobIdMap['timestamp']?.toString() ?? '';
|
||||
} else {
|
||||
jobIdValue = json['jobId']?.toString() ?? '';
|
||||
}
|
||||
|
||||
// Parse completedAt using the helper method to handle both string and array formats
|
||||
final completedAt = _parseDateTime(json['completedAt']);
|
||||
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>?;
|
||||
final stationOrder = _readOptionalInt(json['stationOrder']);
|
||||
final title = _readOptionalString(
|
||||
json['title'] ?? taskSpecificData?['title'],
|
||||
);
|
||||
final description = _readOptionalString(
|
||||
json['description'] ?? taskSpecificData?['description'],
|
||||
);
|
||||
final displayName = _readOptionalString(
|
||||
json['displayName'] ?? taskSpecificData?['displayName'],
|
||||
);
|
||||
|
||||
return {
|
||||
'id': idValue,
|
||||
'jobId': jobIdValue,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': json['completed'] ?? false,
|
||||
'optional': json['optional'] ?? false,
|
||||
'completedAt': completedAt,
|
||||
'completedBy': json['completedBy'],
|
||||
'taskOrder': json['taskOrder'],
|
||||
'title': title,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
};
|
||||
}
|
||||
}
|
||||
99
app/lib/models/tasks/barcode_task.dart
Normal file
99
app/lib/models/tasks/barcode_task.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Barcode Task
|
||||
class BarcodeTask extends Task {
|
||||
final int minBarcodeCount;
|
||||
final int maxBarcodeCount;
|
||||
|
||||
BarcodeTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
required this.minBarcodeCount,
|
||||
required this.maxBarcodeCount,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory BarcodeTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>;
|
||||
|
||||
return BarcodeTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
minBarcodeCount: taskSpecificData['minBarcodeCount'] ?? 1,
|
||||
maxBarcodeCount: taskSpecificData['maxBarcodeCount'] ?? 10,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {
|
||||
'taskType': 'BARCODE',
|
||||
'title': title,
|
||||
'minBarcodeCount': minBarcodeCount,
|
||||
'maxBarcodeCount': maxBarcodeCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
BarcodeTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
int? minBarcodeCount,
|
||||
int? maxBarcodeCount,
|
||||
}) {
|
||||
return BarcodeTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
minBarcodeCount: minBarcodeCount ?? this.minBarcodeCount,
|
||||
maxBarcodeCount: maxBarcodeCount ?? this.maxBarcodeCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
99
app/lib/models/tasks/comment_task.dart
Normal file
99
app/lib/models/tasks/comment_task.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Comment Task
|
||||
class CommentTask extends Task {
|
||||
final String commentText;
|
||||
final bool required;
|
||||
|
||||
CommentTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
required this.commentText,
|
||||
this.required = false,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory CommentTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>;
|
||||
|
||||
return CommentTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
commentText: taskSpecificData['commentText'] ?? '',
|
||||
required: taskSpecificData['required'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {
|
||||
'taskType': 'COMMENT',
|
||||
'title': title,
|
||||
'commentText': commentText,
|
||||
'required': required,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
CommentTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
String? commentText,
|
||||
bool? required,
|
||||
}) {
|
||||
return CommentTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
commentText: commentText ?? this.commentText,
|
||||
required: required ?? this.required,
|
||||
);
|
||||
}
|
||||
}
|
||||
95
app/lib/models/tasks/confirmation_task.dart
Normal file
95
app/lib/models/tasks/confirmation_task.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Confirmation Task
|
||||
class ConfirmationTask extends Task {
|
||||
final String buttonText;
|
||||
|
||||
ConfirmationTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
required this.buttonText,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory ConfirmationTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>;
|
||||
final buttonText =
|
||||
taskSpecificData['buttonText']?.toString() ?? 'Bestätigen';
|
||||
|
||||
return ConfirmationTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
buttonText: buttonText,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {
|
||||
'taskType': 'CONFIRMATION',
|
||||
'title': title,
|
||||
'buttonText': buttonText,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
ConfirmationTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
String? buttonText,
|
||||
}) {
|
||||
return ConfirmationTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
buttonText: buttonText ?? this.buttonText,
|
||||
);
|
||||
}
|
||||
}
|
||||
81
app/lib/models/tasks/generic_task.dart
Normal file
81
app/lib/models/tasks/generic_task.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Generic Task implementation for fallback
|
||||
class GenericTask extends Task {
|
||||
GenericTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory GenericTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
return GenericTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {'taskType': 'GENERIC', 'title': title},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
GenericTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
}) {
|
||||
return GenericTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
);
|
||||
}
|
||||
}
|
||||
99
app/lib/models/tasks/photo_task.dart
Normal file
99
app/lib/models/tasks/photo_task.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Photo Task
|
||||
class PhotoTask extends Task {
|
||||
final int minPhotoCount;
|
||||
final int maxPhotoCount;
|
||||
|
||||
PhotoTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
required this.minPhotoCount,
|
||||
required this.maxPhotoCount,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory PhotoTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>;
|
||||
|
||||
return PhotoTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
minPhotoCount: taskSpecificData['minPhotoCount'] ?? 1,
|
||||
maxPhotoCount: taskSpecificData['maxPhotoCount'] ?? 5,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {
|
||||
'taskType': 'PHOTO',
|
||||
'title': title,
|
||||
'minPhotoCount': minPhotoCount,
|
||||
'maxPhotoCount': maxPhotoCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
PhotoTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
int? minPhotoCount,
|
||||
int? maxPhotoCount,
|
||||
}) {
|
||||
return PhotoTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
minPhotoCount: minPhotoCount ?? this.minPhotoCount,
|
||||
maxPhotoCount: maxPhotoCount ?? this.maxPhotoCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
82
app/lib/models/tasks/signature_task.dart
Normal file
82
app/lib/models/tasks/signature_task.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import '../task.dart';
|
||||
|
||||
// Signature Task
|
||||
class SignatureTask extends Task {
|
||||
SignatureTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory SignatureTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
|
||||
return SignatureTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {'taskType': 'SIGNATURE', 'title': title},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
SignatureTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
}) {
|
||||
return SignatureTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
);
|
||||
}
|
||||
}
|
||||
96
app/lib/models/tasks/todolist_task.dart
Normal file
96
app/lib/models/tasks/todolist_task.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import '../task.dart';
|
||||
|
||||
// TodoList Task
|
||||
class TodoListTask extends Task {
|
||||
final List<String> todoItems;
|
||||
|
||||
TodoListTask({
|
||||
required super.id,
|
||||
required super.jobId,
|
||||
required this.todoItems,
|
||||
super.stationOrder,
|
||||
super.completed = false,
|
||||
super.optional = false,
|
||||
super.completedAt,
|
||||
super.completedBy,
|
||||
super.taskOrder,
|
||||
super.title,
|
||||
super.description,
|
||||
super.displayName,
|
||||
});
|
||||
|
||||
factory TodoListTask.fromJson(Map<String, dynamic> json) {
|
||||
final commonProps = Task.parseCommonProperties(json);
|
||||
final taskSpecificData = json['taskSpecificData'] as Map<String, dynamic>;
|
||||
|
||||
final rawItems = taskSpecificData['todoItems'] as List? ?? [];
|
||||
final todoItems = rawItems.map((item) => item?.toString() ?? '').toList();
|
||||
|
||||
return TodoListTask(
|
||||
id: commonProps['id'],
|
||||
jobId: commonProps['jobId'],
|
||||
stationOrder: commonProps['stationOrder'],
|
||||
completed: commonProps['completed'],
|
||||
optional: commonProps['optional'],
|
||||
completedAt: commonProps['completedAt'],
|
||||
completedBy: commonProps['completedBy'],
|
||||
taskOrder: commonProps['taskOrder'],
|
||||
title: commonProps['title'],
|
||||
description: commonProps['description'],
|
||||
displayName: commonProps['displayName'],
|
||||
todoItems: todoItems,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'jobId': jobId,
|
||||
'stationOrder': stationOrder,
|
||||
'completed': completed,
|
||||
'optional': optional,
|
||||
'completedAt': completedAt?.toIso8601String(),
|
||||
'completedBy': completedBy,
|
||||
'taskOrder': taskOrder,
|
||||
'description': description,
|
||||
'displayName': displayName,
|
||||
'taskSpecificData': {
|
||||
'taskType': 'TODOLIST',
|
||||
'title': title,
|
||||
'todoItems': todoItems,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
TodoListTask copyWith({
|
||||
String? id,
|
||||
String? jobId,
|
||||
int? stationOrder,
|
||||
bool? completed,
|
||||
bool? optional,
|
||||
DateTime? completedAt,
|
||||
String? completedBy,
|
||||
int? taskOrder,
|
||||
String? title,
|
||||
String? description,
|
||||
String? displayName,
|
||||
List<String>? todoItems,
|
||||
}) {
|
||||
return TodoListTask(
|
||||
id: id ?? this.id,
|
||||
jobId: jobId ?? this.jobId,
|
||||
stationOrder: stationOrder ?? this.stationOrder,
|
||||
completed: completed ?? this.completed,
|
||||
optional: optional ?? this.optional,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
completedBy: completedBy ?? this.completedBy,
|
||||
taskOrder: taskOrder ?? this.taskOrder,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
displayName: displayName ?? this.displayName,
|
||||
todoItems: todoItems ?? this.todoItems,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user