Pflichtfeld-Kennzeichen (required) aus dem Workflow-Editor auswerten
- TourField/TourEntry um optionales required-Flag erweitert (Altdaten ohne Angabe verhalten sich unverändert) - Weiter-Button frei, wenn alle Pflichtfelder erfüllt sind oder der Task optional ist; Listen ohne Pflichtfelder (inkl. leerer Listen) sind frei - Pflichtfelder und Pflicht-Checkboxen mit rotem * markiert; Titel-* nur noch, wenn der Task tatsächlich zwingende Eingaben verlangt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d864b9c72a
commit
f58f3f0c59
2 files changed
+91
-5
No files matched your search
@@ -215,7 +215,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
(taskInfo.task?.topic ?? '') + ((taskInfo.task?.opt ?? false) ? "" : " *"),
|
(taskInfo.task?.topic ?? '') + (_isTaskOptional(taskInfo) ? "" : " *"),
|
||||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -455,7 +455,16 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
if ((fields[i].title ?? '').isNotEmpty)
|
if ((fields[i].title ?? '').isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 4, top: 4),
|
padding: const EdgeInsets.only(bottom: 4, top: 4),
|
||||||
child: Text(fields[i].title!, style: const TextStyle(fontWeight: FontWeight.w600)),
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: fields[i].title!,
|
||||||
|
children: [
|
||||||
|
if (fields[i].required == true)
|
||||||
|
const TextSpan(text: ' *', style: TextStyle(color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Util.getDefaultTextField(
|
Util.getDefaultTextField(
|
||||||
fields[i].placeholder ?? '',
|
fields[i].placeholder ?? '',
|
||||||
@@ -485,7 +494,15 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
activeColor: Constants.colorPrimary,
|
activeColor: Constants.colorPrimary,
|
||||||
title: Text(e.text),
|
title: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: e.text,
|
||||||
|
children: [
|
||||||
|
if (e.required == true)
|
||||||
|
const TextSpan(text: ' *', style: TextStyle(color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
onChanged: (value) => setState(() {
|
onChanged: (value) => setState(() {
|
||||||
if (value ?? false) {
|
if (value ?? false) {
|
||||||
draft.checked.add(e.code);
|
draft.checked.add(e.code);
|
||||||
@@ -616,6 +633,31 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
return _isValid(taskInfo);
|
return _isValid(taskInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ob die Aufgabe insgesamt optional ist (steuert das "*" im Titel):
|
||||||
|
/// entweder per {@code opt}-Flag oder – bei Listen-Tasks – weil kein
|
||||||
|
/// einziger Eintrag als Pflichtfeld gekennzeichnet ist. Altdaten ohne
|
||||||
|
/// Pflicht-Kennzeichen gelten wie bisher als verpflichtend.
|
||||||
|
bool _isTaskOptional(TaskInfo taskInfo) {
|
||||||
|
final task = taskInfo.task;
|
||||||
|
if (task == null) return true;
|
||||||
|
if (task.opt ?? false) return true;
|
||||||
|
switch (task.type) {
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
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:
|
||||||
|
final entries = task.entries ?? [];
|
||||||
|
if (entries.isEmpty) return true;
|
||||||
|
if (entries.every((e) => e.required == null)) return false;
|
||||||
|
return entries.every((e) => e.required != true);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool _isValid(TaskInfo taskInfo) {
|
bool _isValid(TaskInfo taskInfo) {
|
||||||
final draft = _draftFor(taskInfo);
|
final draft = _draftFor(taskInfo);
|
||||||
final type = taskInfo.task?.type ?? 0;
|
final type = taskInfo.task?.type ?? 0;
|
||||||
@@ -647,17 +689,49 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
return true;
|
return true;
|
||||||
case 12:
|
case 12:
|
||||||
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
||||||
return draft.fields.isNotEmpty && draft.fields.every((c) => c.text.trim().isNotEmpty);
|
return _fieldsValid(taskInfo, draft, numeric: false);
|
||||||
case 13:
|
case 13:
|
||||||
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
||||||
return draft.fields.isNotEmpty && draft.fields.every((c) => _isNumeric(c.text));
|
return _fieldsValid(taskInfo, draft, numeric: true);
|
||||||
case 14:
|
case 14:
|
||||||
|
final entries = taskInfo.task?.entries ?? [];
|
||||||
|
// Ohne definierte Einträge gibt es keine Pflichtfelder -> freigeben.
|
||||||
|
if (entries.isEmpty) return true;
|
||||||
|
// Altdaten ohne Pflicht-Kennzeichen: wie bisher mindestens ein Häkchen.
|
||||||
|
if (entries.every((e) => e.required == null)) {
|
||||||
return draft.checked.isNotEmpty;
|
return draft.checked.isNotEmpty;
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
.where((e) => e.required == true)
|
||||||
|
.every((e) => draft.checked.contains(e.code));
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prüft die Eingabefelder einer Text-/Zahlen-Liste (Typ 12/13) 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
|
||||||
|
/// Felder dürfen leer bleiben, müssen aber – falls ausgefüllt – bei
|
||||||
|
/// Zahlenlisten ebenfalls numerisch sein.
|
||||||
|
bool _fieldsValid(TaskInfo taskInfo, _TaskDraft draft, {required bool numeric}) {
|
||||||
|
final fields = taskInfo.task?.fields ?? [];
|
||||||
|
// Ohne definierte Felder gibt es keine Pflichtfelder -> freigeben.
|
||||||
|
if (fields.isEmpty) return true;
|
||||||
|
final legacy = fields.every((f) => f.required == null);
|
||||||
|
for (int i = 0; i < fields.length && i < draft.fields.length; i++) {
|
||||||
|
final text = draft.fields[i].text.trim();
|
||||||
|
final mandatory = legacy || fields[i].required == true;
|
||||||
|
if (mandatory) {
|
||||||
|
if (numeric ? !_isNumeric(text) : text.isEmpty) return false;
|
||||||
|
} else if (numeric && text.isNotEmpty && !_isNumeric(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ob der Bearbeiter überhaupt Daten erfasst hat (für optionale Aufgaben:
|
/// Ob der Bearbeiter überhaupt Daten erfasst hat (für optionale Aufgaben:
|
||||||
/// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird).
|
/// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird).
|
||||||
bool _hasData(TaskInfo taskInfo) {
|
bool _hasData(TaskInfo taskInfo) {
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ class TourEntry {
|
|||||||
late String text;
|
late String text;
|
||||||
late bool queryRemark;
|
late bool queryRemark;
|
||||||
|
|
||||||
|
/// Pflicht-Kennzeichen aus dem Workflow-Editor (Checkbox-Liste, Typ 14).
|
||||||
|
/// `null`, wenn das JSON keine Angabe enthält (Altdaten).
|
||||||
|
bool? required;
|
||||||
|
|
||||||
TourEntry();
|
TourEntry();
|
||||||
|
|
||||||
factory TourEntry.fromJson(Map<String, dynamic> json) {
|
factory TourEntry.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -89,6 +93,7 @@ class TourEntry {
|
|||||||
entry.code = json['code'] as int;
|
entry.code = json['code'] as int;
|
||||||
entry.text = json['text'] as String;
|
entry.text = json['text'] as String;
|
||||||
entry.queryRemark = json['query_remark'] as bool? ?? false;
|
entry.queryRemark = json['query_remark'] as bool? ?? false;
|
||||||
|
entry.required = json['required'] as bool?;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +102,7 @@ class TourEntry {
|
|||||||
'code': code,
|
'code': code,
|
||||||
'text': text,
|
'text': text,
|
||||||
'query_remark': queryRemark,
|
'query_remark': queryRemark,
|
||||||
|
if (required != null) 'required': required,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,12 +150,17 @@ class TourField {
|
|||||||
String? title;
|
String? title;
|
||||||
String? placeholder;
|
String? placeholder;
|
||||||
|
|
||||||
|
/// Pflicht-Kennzeichen aus dem Workflow-Editor. `null`, wenn das JSON
|
||||||
|
/// keine Angabe enthält (Altdaten).
|
||||||
|
bool? required;
|
||||||
|
|
||||||
TourField();
|
TourField();
|
||||||
|
|
||||||
factory TourField.fromJson(Map<String, dynamic> json) {
|
factory TourField.fromJson(Map<String, dynamic> json) {
|
||||||
final field = TourField();
|
final field = TourField();
|
||||||
field.title = json['title'] as String?;
|
field.title = json['title'] as String?;
|
||||||
field.placeholder = json['placeholder'] as String?;
|
field.placeholder = json['placeholder'] as String?;
|
||||||
|
field.required = json['required'] as bool?;
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +168,7 @@ class TourField {
|
|||||||
return {
|
return {
|
||||||
'title': title,
|
'title': title,
|
||||||
'placeholder': placeholder,
|
'placeholder': placeholder,
|
||||||
|
if (required != null) 'required': required,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user