implemented absence report module with form validation, prefill support, and API integration

This commit is contained in:
2026-07-26 11:45:48 +02:00
parent 75080a2c49
commit b957189fd3
10 changed files with 523 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
import '../../marianumconnect/queries/absence/absence_prefill_response.dart';
/// Demo fixtures for the absence-report form: the class dropdown and the
/// identity/phone prefill. Typed (compile-checked) so a field rename can't
/// silently drift the demo shape away from what the queries parse.
class DemoAbsence {
const DemoAbsence._();
static List<String> classes() => const ['5a', '6b', '7c', '9d', '10a', 'Q1'];
static AbsencePrefillResponse prefill() => AbsencePrefillResponse(
firstName: 'Max',
lastName: 'Mustermann',
className: '10a',
phone: '0123 456789',
);
}
+5
View File
@@ -1,3 +1,4 @@
import 'data/demo_absence.dart';
import 'data/demo_breaker.dart';
import 'data/demo_holidays.dart';
import 'data/demo_timetable.dart';
@@ -39,6 +40,10 @@ class DemoMarianumConnect {
.toList();
case 'breaker':
return DemoBreaker.none().toJson();
case 'absence/classes':
return DemoAbsence.classes();
case 'absence/prefill':
return DemoAbsence.prefill().toJson();
case 'timetable/elements/teachers':
case 'timetable/elements/students':
case 'timetable/elements/classes':
@@ -0,0 +1,13 @@
import '../../marianumconnect_query.dart';
/// GETs the selectable classes for the absence form (`absence/classes`). The
/// body is a bare JSON string array, so [getList] (which maps objects) does not
/// fit — read the raw list and cast.
class AbsenceClasses extends MarianumConnectQuery {
AbsenceClasses({super.dio});
Future<List<String>> run() => guard(() async {
final response = await dio.get<List<dynamic>>(endpoint('absence/classes'));
return response.data!.cast<String>();
});
}
@@ -0,0 +1,10 @@
import '../../marianumconnect_query.dart';
import 'absence_prefill_response.dart';
/// GETs the absence-report form prefill (`absence/prefill`, bearer-auth).
class AbsencePrefill extends MarianumConnectQuery {
AbsencePrefill({super.dio});
Future<AbsencePrefillResponse> run() =>
getObject('absence/prefill', AbsencePrefillResponse.fromJson);
}
@@ -0,0 +1,28 @@
import 'package:json_annotation/json_annotation.dart';
part 'absence_prefill_response.g.dart';
/// Prefill for the absence-report form: identity from LDAP plus the phone
/// number from the user's last report (empty strings when unknown).
@JsonSerializable()
class AbsencePrefillResponse {
@JsonKey(defaultValue: '')
final String firstName;
@JsonKey(defaultValue: '')
final String lastName;
@JsonKey(defaultValue: '')
final String className;
@JsonKey(defaultValue: '')
final String phone;
AbsencePrefillResponse({
required this.firstName,
required this.lastName,
required this.className,
required this.phone,
});
factory AbsencePrefillResponse.fromJson(Map<String, dynamic> json) =>
_$AbsencePrefillResponseFromJson(json);
Map<String, dynamic> toJson() => _$AbsencePrefillResponseToJson(this);
}
@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'absence_prefill_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AbsencePrefillResponse _$AbsencePrefillResponseFromJson(
Map<String, dynamic> json,
) => AbsencePrefillResponse(
firstName: json['firstName'] as String? ?? '',
lastName: json['lastName'] as String? ?? '',
className: json['className'] as String? ?? '',
phone: json['phone'] as String? ?? '',
);
Map<String, dynamic> _$AbsencePrefillResponseToJson(
AbsencePrefillResponse instance,
) => <String, dynamic>{
'firstName': instance.firstName,
'lastName': instance.lastName,
'className': instance.className,
'phone': instance.phone,
};
@@ -0,0 +1,32 @@
import '../../marianumconnect_query.dart';
/// Submits an absence report (`POST absence`, bearer-auth, `source=mobile`).
/// Empty identity fields are backfilled from LDAP server-side; validation
/// (all fields required, class must exist, no past start date, end >= start)
/// also runs server-side and mirrors the client checks.
class AbsenceSubmit extends MarianumConnectQuery {
AbsenceSubmit({super.dio});
Future<void> run({
required String firstName,
required String lastName,
required String className,
required DateTime absentFrom,
required DateTime absentUntil,
required String phone,
required String note,
}) => guard(() async {
await dio.post<void>(
endpoint('absence'),
data: {
'firstName': firstName,
'lastName': lastName,
'className': className,
'absentFrom': isoDate(absentFrom),
'absentUntil': isoDate(absentUntil),
'phone': phone,
'note': note,
},
);
});
}