feat: Frachtanzeige und Stationsabschluss für Version 0.9.23
Fracht mit Maßen und Gewicht anzeigen und Stationsabschlüsse gegen Wiederholung absichern. Backend-Abschluss atomar ausführen und mit Regressionstests abdecken. Optionale Aufgaben beim Kopieren erhalten und Kunden mit Adressen anzeigen. App und Backend auf Version 0.9.23 aktualisieren.
This commit is contained in:
1 parent
d7c0173493
commit
7325c77739
11 files changed
+277
-65
No files matched your search
+46
-20
@@ -42,6 +42,9 @@ class TaskView extends StatefulWidget {
|
||||
class _TaskViewState extends State<TaskView> {
|
||||
final Set<String> _completedTasks = {};
|
||||
final DatabaseService _databaseService = DatabaseService();
|
||||
bool _statusesLoaded = false;
|
||||
bool _stationCompleted = false;
|
||||
bool _isCompletingStation = false;
|
||||
// Store SVG representations of signatures per task for later use
|
||||
final Map<String, String> _signatureSvgByTask = {};
|
||||
|
||||
@@ -61,10 +64,19 @@ class _TaskViewState extends State<TaskView> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
String get _stationCompletionKey =>
|
||||
'station_completed:${widget.job.id}:${widget.stationOrder ?? 0}';
|
||||
|
||||
/// Load task completion and skipped statuses from database and merge with JSON task states
|
||||
Future<void> _loadTaskStatuses() async {
|
||||
final statuses = await _databaseService.loadAllTaskStatuses();
|
||||
final stationCompleted =
|
||||
await _databaseService.loadKeyValue(_stationCompletionKey) == '1';
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_stationCompleted =
|
||||
stationCompleted || widget.job.status.toUpperCase() == 'COMPLETED';
|
||||
_statusesLoaded = true;
|
||||
_completedTasks.clear();
|
||||
// 1) Add all completed from DB
|
||||
for (final entry in statuses.entries) {
|
||||
@@ -148,9 +160,11 @@ class _TaskViewState extends State<TaskView> {
|
||||
}
|
||||
|
||||
bool get _canCompleteStation {
|
||||
if (!_statusesLoaded || _stationCompleted || _isCompletingStation) {
|
||||
return false;
|
||||
}
|
||||
// Station kann abgeschlossen werden, wenn alle nicht-optionalen Aufgaben
|
||||
// erledigt sind. Sind nur optionale Aufgaben vorhanden, ist der Button
|
||||
// immer aktiv.
|
||||
// erledigt sind und der Stationsabschluss noch nicht gesendet wurde.
|
||||
for (final t in _visibleTasks) {
|
||||
if (!t.optional && !_completedTasks.contains(t.id)) {
|
||||
return false;
|
||||
@@ -184,6 +198,7 @@ class _TaskViewState extends State<TaskView> {
|
||||
}
|
||||
|
||||
void _onCompleteStationPressed() {
|
||||
if (!_canCompleteStation) return;
|
||||
if (_hasIncompleteOptionalTasks) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -214,21 +229,30 @@ class _TaskViewState extends State<TaskView> {
|
||||
}
|
||||
}
|
||||
|
||||
void _sendStationCompleted() {
|
||||
Future<void> _sendStationCompleted() async {
|
||||
if (!_canCompleteStation) return;
|
||||
setState(() => _isCompletingStation = true);
|
||||
final stationOrder = widget.stationOrder ?? 0;
|
||||
try {
|
||||
StompService().sendStationCompleted(
|
||||
await StompService().sendStationCompleted(
|
||||
jobId: widget.job.id,
|
||||
jobNumber: widget.job.jobNumber,
|
||||
stationOrder: stationOrder,
|
||||
hasIncompleteOptionalTasks: _hasIncompleteOptionalTasks,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Station abgeschlossen')),
|
||||
);
|
||||
_stationCompleted = true;
|
||||
await _databaseService.saveKeyValue(_stationCompletionKey, '1');
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Station abgeschlossen')));
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
developer.log('Error sending station completion: $e', name: 'TaskView');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isCompletingStation = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,18 +396,18 @@ class _TaskViewState extends State<TaskView> {
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.warningSoft,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppColors.warning.withValues(alpha: 0.5),
|
||||
width: 1,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.warningSoft,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppColors.warning.withValues(alpha: 0.5),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Optional',
|
||||
style: TextStyle(
|
||||
@@ -756,7 +780,7 @@ class _TaskViewState extends State<TaskView> {
|
||||
// den Benutzer fragen, ob er die Station jetzt abschließen möchte.
|
||||
if (hadOpenMandatoryBefore && !_hasOpenMandatoryTasks) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
if (mounted && _canCompleteStation) {
|
||||
_showLastMandatoryCompletedDialog();
|
||||
}
|
||||
});
|
||||
@@ -899,7 +923,9 @@ class _TaskViewState extends State<TaskView> {
|
||||
? localizeKnownText(context, task.description!)
|
||||
: null;
|
||||
final String? signatureNote =
|
||||
(task is SignatureTask && task.note != null && task.note!.trim().isNotEmpty)
|
||||
(task is SignatureTask &&
|
||||
task.note != null &&
|
||||
task.note!.trim().isNotEmpty)
|
||||
? task.note!.trim()
|
||||
: null;
|
||||
|
||||
|
||||
Reference in new issue
Block a user