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
+92
-6
No files matched your search
@@ -215,7 +215,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
(taskInfo.task?.topic ?? '') + ((taskInfo.task?.opt ?? false) ? "" : " *"),
|
||||
(taskInfo.task?.topic ?? '') + (_isTaskOptional(taskInfo) ? "" : " *"),
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
@@ -455,7 +455,16 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
||||
if ((fields[i].title ?? '').isNotEmpty)
|
||||
Padding(
|
||||
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(
|
||||
fields[i].placeholder ?? '',
|
||||
@@ -485,7 +494,15 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
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(() {
|
||||
if (value ?? false) {
|
||||
draft.checked.add(e.code);
|
||||
@@ -616,6 +633,31 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
||||
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) {
|
||||
final draft = _draftFor(taskInfo);
|
||||
final type = taskInfo.task?.type ?? 0;
|
||||
@@ -647,17 +689,49 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
||||
return true;
|
||||
case 12:
|
||||
_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:
|
||||
_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:
|
||||
return draft.checked.isNotEmpty;
|
||||
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 entries
|
||||
.where((e) => e.required == true)
|
||||
.every((e) => draft.checked.contains(e.code));
|
||||
default:
|
||||
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:
|
||||
/// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird).
|
||||
bool _hasData(TaskInfo taskInfo) {
|
||||
|
||||
@@ -82,6 +82,10 @@ class TourEntry {
|
||||
late String text;
|
||||
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();
|
||||
|
||||
factory TourEntry.fromJson(Map<String, dynamic> json) {
|
||||
@@ -89,6 +93,7 @@ class TourEntry {
|
||||
entry.code = json['code'] as int;
|
||||
entry.text = json['text'] as String;
|
||||
entry.queryRemark = json['query_remark'] as bool? ?? false;
|
||||
entry.required = json['required'] as bool?;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -97,6 +102,7 @@ class TourEntry {
|
||||
'code': code,
|
||||
'text': text,
|
||||
'query_remark': queryRemark,
|
||||
if (required != null) 'required': required,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -144,12 +150,17 @@ class TourField {
|
||||
String? title;
|
||||
String? placeholder;
|
||||
|
||||
/// Pflicht-Kennzeichen aus dem Workflow-Editor. `null`, wenn das JSON
|
||||
/// keine Angabe enthält (Altdaten).
|
||||
bool? required;
|
||||
|
||||
TourField();
|
||||
|
||||
factory TourField.fromJson(Map<String, dynamic> json) {
|
||||
final field = TourField();
|
||||
field.title = json['title'] as String?;
|
||||
field.placeholder = json['placeholder'] as String?;
|
||||
field.required = json['required'] as bool?;
|
||||
return field;
|
||||
}
|
||||
|
||||
@@ -157,6 +168,7 @@ class TourField {
|
||||
return {
|
||||
'title': title,
|
||||
'placeholder': placeholder,
|
||||
if (required != null) 'required': required,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user