refactor: Projektstruktur in app/ und backend/ aufgeteilt
This commit is contained in:
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user