From ac4266079ac4ba69c7db4211f6ace9793ed5e0b5 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Mon, 3 Aug 2026 14:29:22 +0200 Subject: [PATCH] Task-Typnummern neu zugeordnet und benutzerdefinierte Typen via effectiveType unterstuetzt - TourTask: base_type-Feld und effectiveType-Getter ergaenzt; benutzerdefinierte Typen (>= 1000) aus dem Workflow-Editor werden auf ihren Basis-Typ abgebildet, bei Altdaten ohne base_type wird der Basis-Typ aus den Eigenschaften abgeleitet - TaskListPage: alle Typ-Switches auf effectiveType und die neuen Typnummern umgestellt (u.a. Ankunft 1->3, Fotos 3->4, Bestaetigung 6->12, Listen 12-14->13-15) Co-Authored-By: Claude Fable 5 --- lib/content_views/task_list_page.dart | 104 +++++++++++++------------- lib/model/tour_task.dart | 38 +++++++++- 2 files changed, 88 insertions(+), 54 deletions(-) diff --git a/lib/content_views/task_list_page.dart b/lib/content_views/task_list_page.dart index 98082f4..c680e9b 100644 --- a/lib/content_views/task_list_page.dart +++ b/lib/content_views/task_list_page.dart @@ -313,19 +313,19 @@ class _TaskListPageState extends State with ProducerMixin { Widget _buildEditableBody(TaskInfo taskInfo) { final draft = _draftFor(taskInfo); - final type = taskInfo.task?.type ?? 0; + // Benutzerdefinierte Typen aus dem Workflow-Editor (>= 1000) verhalten + // sich wie ihr Basis-Typ, siehe TourTask.effectiveType. + final type = taskInfo.task?.effectiveType ?? 0; switch (type) { - case 1: - return _buildConfirmButtonBody(draft, 'confirm_arrival'.tr); case 2: return _buildSignatureBody(taskInfo, draft); case 3: + return _buildConfirmButtonBody(draft, 'confirm_arrival'.tr); + case 4: return _buildPhotosBody(taskInfo, draft); case 5: return Text('barcode_disabled'.tr, style: const TextStyle(color: Colors.grey)); - case 6: - return _buildConfirmBody(draft, taskInfo.task?.topic ?? 'confirm'.tr); case 7: case 8: case 9: @@ -333,17 +333,19 @@ class _TaskListPageState extends State with ProducerMixin { case 11: return _buildStatusBody(taskInfo, draft); case 12: - return _buildFieldsBody(taskInfo, draft, numeric: false); + return _buildConfirmBody(draft, taskInfo.task?.topic ?? 'confirm'.tr); case 13: - return _buildFieldsBody(taskInfo, draft, numeric: true); + return _buildFieldsBody(taskInfo, draft, numeric: false); case 14: + return _buildFieldsBody(taskInfo, draft, numeric: true); + case 15: return _buildCheckboxListBody(taskInfo, draft); default: return Text('task_type_unsupported'.tr, style: const TextStyle(color: Colors.grey)); } } - /// Bestätigung per Button (Ankunft melden = 1). Antippen bestätigt bzw. + /// Bestätigung per Button (Ankunft melden = 3). Antippen bestätigt bzw. /// nimmt die Bestätigung wieder zurück; der Zustand wird über ein /// Häkchen-Symbol im Button angezeigt. Widget _buildConfirmButtonBody(_TaskDraft draft, String label) { @@ -375,7 +377,7 @@ class _TaskListPageState extends State with ProducerMixin { ); } - /// Bestätigungs-Aufgabe als Checkbox (Typ 6). + /// Bestätigungs-Aufgabe als Checkbox (Typ 12). Widget _buildConfirmBody(_TaskDraft draft, String label) { return CheckboxListTile( value: draft.confirmed, @@ -440,7 +442,7 @@ class _TaskListPageState extends State with ProducerMixin { ); } - /// Text-/Zahlen-Liste (Typ 12/13): je Feld ein beschriftetes Eingabefeld. + /// Text-/Zahlen-Liste (Typ 13/14): je Feld ein beschriftetes Eingabefeld. Widget _buildFieldsBody(TaskInfo taskInfo, _TaskDraft draft, {required bool numeric}) { final fields = taskInfo.task?.fields ?? []; if (fields.isEmpty) { @@ -480,7 +482,7 @@ class _TaskListPageState extends State with ProducerMixin { ); } - /// Checkbox-Liste (Typ 14): Mehrfachauswahl aus den entries. + /// Checkbox-Liste (Typ 15): Mehrfachauswahl aus den entries. Widget _buildCheckboxListBody(TaskInfo taskInfo, _TaskDraft draft) { final entries = taskInfo.task?.entries ?? []; if (entries.isEmpty) { @@ -554,7 +556,7 @@ class _TaskListPageState extends State with ProducerMixin { ); } - /// Fotos aufnehmen (Typ 3) – Inline-Aufnahme und Vorschau. + /// Fotos aufnehmen (Typ 4) – Inline-Aufnahme und Vorschau. Widget _buildPhotosBody(TaskInfo taskInfo, _TaskDraft draft) { final max = taskInfo.task?.max ?? 999; final min = taskInfo.task?.min ?? 0; @@ -641,14 +643,14 @@ class _TaskListPageState extends State with ProducerMixin { final task = taskInfo.task; if (task == null) return true; if (task.opt ?? false) return true; - switch (task.type) { - case 12: + switch (task.effectiveType) { case 13: + case 14: final fields = task.fields ?? []; if (fields.isEmpty) return true; if (fields.every((f) => f.required == null)) return false; return fields.every((f) => f.required != true); - case 14: + case 15: final entries = task.entries ?? []; if (entries.isEmpty) return true; if (entries.every((e) => e.required == null)) return false; @@ -660,15 +662,15 @@ class _TaskListPageState extends State with ProducerMixin { bool _isValid(TaskInfo taskInfo) { final draft = _draftFor(taskInfo); - final type = taskInfo.task?.type ?? 0; + final type = taskInfo.task?.effectiveType ?? 0; switch (type) { - case 1: - case 6: - return draft.confirmed; case 2: final sig = draft.signatureController; return sig != null && sig.points.length > 50; case 3: + case 12: + return draft.confirmed; + case 4: final min = (taskInfo.task?.min ?? 0); final max = (taskInfo.task?.max ?? 999); final required = min > 0 ? min : 1; @@ -687,13 +689,13 @@ class _TaskListPageState extends State with ProducerMixin { return (draft.remark?.text.trim().isNotEmpty ?? false); } return true; - case 12: - _ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0); - return _fieldsValid(taskInfo, draft, numeric: false); case 13: _ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0); - return _fieldsValid(taskInfo, draft, numeric: true); + return _fieldsValid(taskInfo, draft, numeric: false); case 14: + _ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0); + return _fieldsValid(taskInfo, draft, numeric: true); + case 15: final entries = taskInfo.task?.entries ?? []; // Ohne definierte Einträge gibt es keine Pflichtfelder -> freigeben. if (entries.isEmpty) return true; @@ -709,7 +711,7 @@ class _TaskListPageState extends State with ProducerMixin { } } - /// Prüft die Eingabefelder einer Text-/Zahlen-Liste (Typ 12/13) gegen ihre + /// Prüft die Eingabefelder einer Text-/Zahlen-Liste (Typ 13/14) gegen ihre /// Pflicht-Kennzeichen. Altdaten ohne Kennzeichen behalten das bisherige /// Verhalten (alle Felder müssen gefüllt bzw. numerisch sein). Sonst müssen /// nur Pflichtfelder gefüllt (bei Zahlenlisten numerisch) sein; optionale @@ -736,15 +738,15 @@ class _TaskListPageState extends State with ProducerMixin { /// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird). bool _hasData(TaskInfo taskInfo) { final draft = _draftFor(taskInfo); - final type = taskInfo.task?.type ?? 0; + final type = taskInfo.task?.effectiveType ?? 0; switch (type) { - case 1: - case 6: - return draft.confirmed; case 2: final sig = draft.signatureController; return sig != null && sig.points.length > 50; case 3: + case 12: + return draft.confirmed; + case 4: return draft.photos.isNotEmpty; case 7: case 8: @@ -752,10 +754,10 @@ class _TaskListPageState extends State with ProducerMixin { return (draft.text?.text.trim().isNotEmpty ?? false); case 11: return draft.selectedCode != null; - case 12: case 13: - return draft.fields.any((c) => c.text.trim().isNotEmpty); case 14: + return draft.fields.any((c) => c.text.trim().isNotEmpty); + case 15: return draft.checked.isNotEmpty; default: return false; @@ -800,7 +802,7 @@ class _TaskListPageState extends State with ProducerMixin { Future _commitCurrent(TaskInfo taskInfo) async { final draft = _draftFor(taskInfo); - final type = taskInfo.task?.type ?? 0; + final type = taskInfo.task?.effectiveType ?? 0; // Optionale Aufgabe ohne Eingabe: als erledigt markieren, keine Antwort. if (!_hasData(taskInfo)) { @@ -812,15 +814,6 @@ class _TaskListPageState extends State with ProducerMixin { } switch (type) { - case 1: // Ankunft melden - final answer = await _createAnswerHeader(taskInfo); - taskInfo.answer = jsonEncode(answer.toJson()); - taskInfo.additionalText = DateFormat("dd.MM.yyyy HH:mm").format(DateTime.now()); - taskInfo.completed = true; - _persist(taskInfo); - _sendArrivedMessage(taskInfo); - break; - case 2: // Unterschrift final answer = await _createAnswerHeader(taskInfo); answer.sign = _convertSignaturePoints(draft.signatureController!.points); @@ -830,7 +823,16 @@ class _TaskListPageState extends State with ProducerMixin { _persist(taskInfo); break; - case 3: // Fotos + case 3: // Ankunft melden + final answer = await _createAnswerHeader(taskInfo); + taskInfo.answer = jsonEncode(answer.toJson()); + taskInfo.additionalText = DateFormat("dd.MM.yyyy HH:mm").format(DateTime.now()); + taskInfo.completed = true; + _persist(taskInfo); + _sendArrivedMessage(taskInfo); + break; + + case 4: // Fotos _storePhotos(draft.photos); taskInfo.answer = null; // Bilder werden separat als pic-Nachrichten gesendet. taskInfo.additionalText = 'photos_taken'.tr; @@ -838,7 +840,7 @@ class _TaskListPageState extends State with ProducerMixin { _persist(taskInfo); break; - case 6: // Checkbox / Bestätigung + case 12: // Checkbox / Bestätigung final answer = await _createAnswerHeader(taskInfo); answer.val = "1"; taskInfo.answer = jsonEncode(answer.toJson()); @@ -871,8 +873,8 @@ class _TaskListPageState extends State with ProducerMixin { _persist(taskInfo); break; - case 12: // Text-Liste - case 13: // Zahlen-Liste + case 13: // Text-Liste + case 14: // Zahlen-Liste final answer = await _createAnswerHeader(taskInfo); final fields = taskInfo.task?.fields ?? []; final answers = []; @@ -891,7 +893,7 @@ class _TaskListPageState extends State with ProducerMixin { _persist(taskInfo); break; - case 14: // Checkbox-Liste + case 15: // Checkbox-Liste final answer = await _createAnswerHeader(taskInfo); final checked = draft.checked.toList()..sort(); answer.checked = checked; @@ -943,9 +945,9 @@ class _TaskListPageState extends State with ProducerMixin { /// erneut bearbeitbaren Aufgaben aus der gespeicherten Antwort vor. _TaskDraft _buildDraft(TaskInfo taskInfo) { final draft = _TaskDraft(); - final type = taskInfo.task?.type ?? 0; + final type = taskInfo.task?.effectiveType ?? 0; - if (type == 12 || type == 13) { + if (type == 13 || type == 14) { _ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0); } @@ -957,8 +959,8 @@ class _TaskListPageState extends State with ProducerMixin { try { final answer = FinTourTask.fromJson(jsonDecode(taskInfo.answer!) as Map); switch (type) { - case 1: - case 6: + case 3: + case 12: draft.confirmed = true; break; case 7: @@ -970,14 +972,14 @@ class _TaskListPageState extends State with ProducerMixin { draft.selectedCode = answer.code; draft.remark = _listenedController(answer.val ?? ''); break; - case 12: case 13: + case 14: final values = answer.fields ?? []; for (int i = 0; i < draft.fields.length && i < values.length; i++) { draft.fields[i].text = values[i].val ?? ''; } break; - case 14: + case 15: draft.checked = (answer.checked ?? []).toSet(); break; } diff --git a/lib/model/tour_task.dart b/lib/model/tour_task.dart index 2001f7e..e7dc207 100644 --- a/lib/model/tour_task.dart +++ b/lib/model/tour_task.dart @@ -1,6 +1,16 @@ class TourTask { + /// Untergrenze der im Workflow-Editor vergebenen Typnummern für + /// benutzerdefinierte Typen. + static const int customTypeStart = 1000; + late int sId; late int type; + + /// Typnummer des Basis-Typs bei benutzerdefinierten Typen + /// (>= [customTypeStart]) aus dem Workflow-Editor. `null` bei + /// Standard-Typen und bei Altdaten ohne Angabe. + int? baseType; + late String topic; bool? opt; bool? reenter; @@ -17,15 +27,36 @@ class TourTask { /// Auswahloptionen eines Status-Tasks (Typ 11). List? choices; - /// Eingabefelder einer Text-/Zahlen-Liste (Typ 12/13). + /// Eingabefelder einer Text-/Zahlen-Liste (Typ 13/14). List? fields; TourTask(); + /// Der für Darstellung und Validierung maßgebliche Typ: Standard-Typen + /// unverändert; benutzerdefinierte Typen (>= [customTypeStart]) werden auf + /// ihren Basis-Typ abgebildet. Fehlt {@code base_type} (Altdaten), wird der + /// Basis-Typ aus den typprägenden Eigenschaften abgeleitet. + int get effectiveType { + if (type < customTypeStart) { + return type; + } + if (baseType != null) { + return baseType!; + } + if (choices != null && choices!.isNotEmpty) return 11; // Statusauswahl + if (entries != null && entries!.isNotEmpty) return 15; // Checkbox-Liste + if (fields != null && fields!.isNotEmpty) return 13; // Text-Liste + if (min != null || max != null) return 4; // Fotos + if (maxLen != null) return 8; // Texteingabe (Bemerkung) + if (track != null) return 3; // Ankunft melden + return 12; // Bestätigung per Checkbox + } + factory TourTask.fromJson(Map json) { final task = TourTask(); task.sId = json['s_id'] as int; task.type = json['type'] as int; + task.baseType = json['base_type'] as int?; task.topic = json['topic'] as String; task.opt = json['opt'] as bool? ?? false; task.reenter = json['reenter'] as bool? ?? false; @@ -59,6 +90,7 @@ class TourTask { return { 's_id': sId, 'type': type, + if (baseType != null) 'base_type': baseType, 'topic': topic, 'opt': opt, 'reenter': reenter, @@ -82,7 +114,7 @@ class TourEntry { late String text; late bool queryRemark; - /// Pflicht-Kennzeichen aus dem Workflow-Editor (Checkbox-Liste, Typ 14). + /// Pflicht-Kennzeichen aus dem Workflow-Editor (Checkbox-Liste, Typ 15). /// `null`, wenn das JSON keine Angabe enthält (Altdaten). bool? required; @@ -145,7 +177,7 @@ class TourChoice { bool get requiresRemark => (quest != null && quest!.trim().isNotEmpty); } -/// Ein Eingabefeld einer Text-/Zahlen-Liste (Typ 12/13). +/// Ein Eingabefeld einer Text-/Zahlen-Liste (Typ 13/14). class TourField { String? title; String? placeholder;