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 <noreply@anthropic.com>
This commit is contained in:
Sven CarstensenandClaude Fable 5 committed 2026-08-03 14:29:22 +02:00
1 parent aa44311776
commit ac4266079a
2 files changed
+88 -54

No files matched your search

+53 -51
View File
@@ -313,19 +313,19 @@ class _TaskListPageState extends State<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> with ProducerMixin {
Future<void> _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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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<TaskListPage> 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 = <FinFieldAnswer>[];
@@ -891,7 +893,7 @@ class _TaskListPageState extends State<TaskListPage> 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<TaskListPage> 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<TaskListPage> with ProducerMixin {
try {
final answer = FinTourTask.fromJson(jsonDecode(taskInfo.answer!) as Map<String, dynamic>);
switch (type) {
case 1:
case 6:
case 3:
case 12:
draft.confirmed = true;
break;
case 7:
@@ -970,14 +972,14 @@ class _TaskListPageState extends State<TaskListPage> 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;
}
+35 -3
View File
@@ -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<TourChoice>? choices;
/// Eingabefelder einer Text-/Zahlen-Liste (Typ 12/13).
/// Eingabefelder einer Text-/Zahlen-Liste (Typ 13/14).
List<TourField>? 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<String, dynamic> 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;