fix: Stationsabschluss absichern und Kundenformulare anpassen
Sperrt nachträgliche Aufgabenabschlüsse in App und Backend und zeigt erledigte Aufträge trotz offener optionaler Aufgaben grün an. Ergänzt Regressionstests, die Anredeauswahl und optionale Steuerangaben für Kunden. Enthält App-Version 0.9.25, Backend-Version 0.9.26 und das aktualisierte Frontend-Bundle.
This commit is contained in:
1 parent
8758c53e5b
commit
5e6044e4b1
16 files changed
+638
-69
No files matched your search
+54
-21
@@ -8,6 +8,7 @@ import 'services/dart_mq.dart';
|
||||
import 'services/chat_service.dart';
|
||||
import 'models/delivery_station.dart';
|
||||
import 'models/job.dart';
|
||||
import 'models/job_progress.dart';
|
||||
import 'models/task.dart';
|
||||
import 'models/tasks/confirmation_task.dart';
|
||||
import 'models/tasks/photo_task.dart';
|
||||
@@ -23,6 +24,13 @@ import 'services/database_service.dart';
|
||||
import 'navigation_observer.dart';
|
||||
import 'routing_view.dart';
|
||||
|
||||
@visibleForTesting
|
||||
Color? jobCardBackgroundColor(JobProgress progress) {
|
||||
if (progress.isCompleted) return AppColors.successSoft;
|
||||
if (progress.hasProgress) return AppColors.warningSoft;
|
||||
return null;
|
||||
}
|
||||
|
||||
class JobsView extends StatefulWidget {
|
||||
const JobsView({super.key});
|
||||
|
||||
@@ -63,6 +71,7 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
bool _isLoggingOut = false;
|
||||
final DatabaseService _databaseService = DatabaseService();
|
||||
Map<String, bool> _taskStatuses = const {};
|
||||
Map<String, Set<int>> _completedStations = const {};
|
||||
Map<String, bool> _jobSeen = const {};
|
||||
Map<String, double> _jobSwipeOffsets = const {};
|
||||
String? _openJobId;
|
||||
@@ -479,9 +488,15 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
|
||||
Future<void> _loadLocalTaskStatuses() async {
|
||||
final map = await _databaseService.loadAllTaskStatuses();
|
||||
final completedStations = <String, Set<int>>{};
|
||||
for (final job in _appState.assignedJobs) {
|
||||
completedStations[job.id] = await _databaseService
|
||||
.loadCompletedStationOrders(job);
|
||||
}
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_taskStatuses = map;
|
||||
_completedStations = completedStations;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -950,25 +965,15 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
statusColor = AppColors.textMuted;
|
||||
}
|
||||
|
||||
// Determine card background color based on task completion
|
||||
final progress = JobProgress(
|
||||
job,
|
||||
taskStatuses: _taskStatuses,
|
||||
localCompletedStations: _completedStations[job.id] ?? const {},
|
||||
);
|
||||
final totalTasks = job.tasks.length;
|
||||
int completedTasks = 0;
|
||||
for (final t in job.tasks) {
|
||||
final isCompleted = _taskStatuses[t.id] ?? t.completed;
|
||||
if (isCompleted) completedTasks++;
|
||||
}
|
||||
|
||||
// Check if all tasks are completed (job is done)
|
||||
final bool isJobCompleted = totalTasks > 0 && completedTasks == totalTasks;
|
||||
|
||||
Color? cardBg;
|
||||
if (totalTasks == 0 || completedTasks == 0) {
|
||||
cardBg = null; // unchanged (default)
|
||||
} else if (completedTasks > 0 && completedTasks < totalTasks) {
|
||||
cardBg = AppColors.warningSoft;
|
||||
} else if (completedTasks == totalTasks) {
|
||||
cardBg = AppColors.successSoft;
|
||||
}
|
||||
final completedTasks = progress.completedTaskCount;
|
||||
final isJobCompleted = progress.isCompleted;
|
||||
final cardBg = jobCardBackgroundColor(progress);
|
||||
// Build robust display strings with fallbacks
|
||||
final pickupName = _joinNonEmpty([job.pickupFirstName, job.pickupLastName]);
|
||||
final pickupDisplayName =
|
||||
@@ -1211,7 +1216,17 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
),
|
||||
],
|
||||
),
|
||||
// Progress bar for tasks
|
||||
if (isJobCompleted) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context).jobCompleted,
|
||||
style: const TextStyle(
|
||||
color: AppColors.success,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
// Progress bar for tasks (optional tasks retain their actual state).
|
||||
if (totalTasks > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
@@ -1235,7 +1250,8 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
minHeight: 8,
|
||||
backgroundColor: AppColors.border,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
completedTasks >= totalTasks
|
||||
isJobCompleted ||
|
||||
completedTasks >= totalTasks
|
||||
? AppColors.success
|
||||
: (completedTasks > 0
|
||||
? AppColors.warning
|
||||
@@ -1447,6 +1463,14 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
}
|
||||
|
||||
void _toggleTaskCompletion(Job job, int taskIndex) {
|
||||
final progress = JobProgress(
|
||||
job,
|
||||
taskStatuses: _taskStatuses,
|
||||
localCompletedStations: _completedStations[job.id] ?? const {},
|
||||
);
|
||||
if (progress.isStationClosed(job.tasks[taskIndex].stationOrder ?? 0)) {
|
||||
return;
|
||||
}
|
||||
// Create a new task with toggled completion status
|
||||
final updatedTask = job.tasks[taskIndex].copyWith(
|
||||
completed: !job.tasks[taskIndex].completed,
|
||||
@@ -1488,6 +1512,7 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
id: job.id,
|
||||
jobNumber: job.jobNumber,
|
||||
status: job.status,
|
||||
completedStationOrders: job.completedStationOrders,
|
||||
createdAt: job.createdAt,
|
||||
updatedAt: job.updatedAt,
|
||||
createdBy: job.createdBy,
|
||||
@@ -1690,7 +1715,15 @@ class _JobsViewState extends State<JobsView> with RouteAware {
|
||||
const SizedBox(height: 8),
|
||||
...job.tasks.asMap().entries.map(
|
||||
(entry) => GestureDetector(
|
||||
onTap: () => _toggleTaskCompletion(job, entry.key),
|
||||
onTap:
|
||||
JobProgress(
|
||||
job,
|
||||
taskStatuses: _taskStatuses,
|
||||
localCompletedStations:
|
||||
_completedStations[job.id] ?? const {},
|
||||
).isStationClosed(entry.value.stationOrder ?? 0)
|
||||
? null
|
||||
: () => _toggleTaskCompletion(job, entry.key),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
|
||||
Reference in new issue
Block a user