Skip to content

Commit 5de304f

Browse files
add migrationBackground to PupilIdentity, update excel template
1 parent e4f2d42 commit 5de304f

16 files changed

Lines changed: 156 additions & 272 deletions

File tree

docs/model_relations/school_data_hub_server.svg

Lines changed: 1 addition & 1 deletion
Loading

school_data_hub_client/lib/src/protocol/_features/pupil/models/pupil_identity/pupil_identity.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ abstract class PupilIdentity implements _i1.SerializableModel {
2525
this.specialNeeds,
2626
required this.gender,
2727
required this.language,
28+
required this.migrationBackground,
2829
this.nationality,
2930
this.family,
3031
required this.birthday,
@@ -50,6 +51,7 @@ abstract class PupilIdentity implements _i1.SerializableModel {
5051
List<String>? specialNeeds,
5152
required String gender,
5253
required String language,
54+
required bool migrationBackground,
5355
String? nationality,
5456
String? family,
5557
required DateTime birthday,
@@ -79,6 +81,7 @@ abstract class PupilIdentity implements _i1.SerializableModel {
7981
.toList(),
8082
gender: jsonSerialization['gender'] as String,
8183
language: jsonSerialization['language'] as String,
84+
migrationBackground: jsonSerialization['migrationBackground'] as bool,
8285
nationality: jsonSerialization['nationality'] as String?,
8386
family: jsonSerialization['family'] as String?,
8487
birthday:
@@ -134,6 +137,8 @@ abstract class PupilIdentity implements _i1.SerializableModel {
134137

135138
String language;
136139

140+
bool migrationBackground;
141+
137142
String? nationality;
138143

139144
String? family;
@@ -172,6 +177,7 @@ abstract class PupilIdentity implements _i1.SerializableModel {
172177
List<String>? specialNeeds,
173178
String? gender,
174179
String? language,
180+
bool? migrationBackground,
175181
String? nationality,
176182
String? family,
177183
DateTime? birthday,
@@ -198,6 +204,7 @@ abstract class PupilIdentity implements _i1.SerializableModel {
198204
if (specialNeeds != null) 'specialNeeds': specialNeeds?.toJson(),
199205
'gender': gender,
200206
'language': language,
207+
'migrationBackground': migrationBackground,
201208
if (nationality != null) 'nationality': nationality,
202209
if (family != null) 'family': family,
203210
'birthday': birthday.toJson(),
@@ -238,6 +245,7 @@ class _PupilIdentityImpl extends PupilIdentity {
238245
List<String>? specialNeeds,
239246
required String gender,
240247
required String language,
248+
required bool migrationBackground,
241249
String? nationality,
242250
String? family,
243251
required DateTime birthday,
@@ -261,6 +269,7 @@ class _PupilIdentityImpl extends PupilIdentity {
261269
specialNeeds: specialNeeds,
262270
gender: gender,
263271
language: language,
272+
migrationBackground: migrationBackground,
264273
nationality: nationality,
265274
family: family,
266275
birthday: birthday,
@@ -290,6 +299,7 @@ class _PupilIdentityImpl extends PupilIdentity {
290299
Object? specialNeeds = _Undefined,
291300
String? gender,
292301
String? language,
302+
bool? migrationBackground,
293303
Object? nationality = _Undefined,
294304
Object? family = _Undefined,
295305
DateTime? birthday,
@@ -318,6 +328,7 @@ class _PupilIdentityImpl extends PupilIdentity {
318328
: this.specialNeeds?.map((e0) => e0).toList(),
319329
gender: gender ?? this.gender,
320330
language: language ?? this.language,
331+
migrationBackground: migrationBackground ?? this.migrationBackground,
321332
nationality: nationality is String? ? nationality : this.nationality,
322333
family: family is String? ? family : this.family,
323334
birthday: birthday ?? this.birthday,

school_data_hub_flutter/assets/school_data_hub_server.svg

Lines changed: 1 addition & 1 deletion
Loading

school_data_hub_flutter/lib/app_utils/pupil_identity_file_import.dart

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ Future<String?> pickPupilIdentityFileContent() async {
3131

3232
/// SchILD export column indices (0-based). Mapping by position, not header names.
3333
/// Headers: Interne ID-Nummer, Vorname, Nachname, Klasse, Klassenlehrer, Stv., Jahrgang,
34-
/// Förderschwerpunkt 1, Förderschwerpunkt 2, Geschlecht, Staatsangehörigkeit, Verkehrssprache,
35-
/// Externe ID-Nummer, Geburtsdatum, Ende Eingliederungsphase, Aufnahmedatum, bes.Merkmal,
36-
/// Konfession, Religionsanmeldung, Religionsabmeldung, Ausweisnummer, Übergangsempfehlung, Entlassdatum
37-
/// Hijacked: Ausweisnummer (20) -> family; Externe ID-Nummer (12) -> familyLanguageLessonsSince.
34+
/// Förderschwerpunkt 1, Förderschwerpunkt 2, Geschlecht, Staatsangehörigkeit,
35+
/// Migrationshintergrund vorhanden, Verkehrssprache, Externe ID-Nummer, Geburtsdatum,
36+
/// Ende Eingliederungsphase, Aufnahmedatum, bes.Merkmal, Konfession, Religionsanmeldung,
37+
/// Religionsabmeldung, Ausweisnummer, Übergangsempfehlung, Entlassdatum
3838
class SchildExportColumns {
3939
SchildExportColumns._();
4040

@@ -49,17 +49,18 @@ class SchildExportColumns {
4949
static const int specialNeeds2 = 8; // Förderschwerpunkt 2
5050
static const int gender = 9; // Geschlecht
5151
static const int nationality = 10; // Staatsangehörigkeit (Schlüssel)
52-
static const int language = 11; // Verkehrssprache in der Familie
53-
static const int familyLanguageLessonsSince = 12; // Externe ID-Nummer (hijacked)
54-
static const int birthday = 13; // Geburtsdatum
55-
static const int migrationSupportEnds = 14; // Ende der Eingliederungsphase
56-
static const int pupilSince = 15; // Aufnahmedatum
57-
static const int religion = 17; // Konfession (Klartext)
58-
static const int religionLessonsSince = 18; // Religionsanmeldung
59-
static const int religionLessonsCancelledAt = 19; // Religionsabmeldung
60-
static const int family = 20; // Ausweisnummer (hijacked for family code)
61-
static const int schoolTransitionRecommendation = 21; // Übergangsempfehlung
62-
static const int leavingDate = 22; // Entlassdatum
52+
static const int migrationBackground = 11; // Migrationshintergrund vorhanden
53+
static const int language = 12; // Verkehrssprache in der Familie
54+
static const int familyLanguageLessonsSince = 13; // Externe ID-Nummer (hijacked)
55+
static const int birthday = 14; // Geburtsdatum
56+
static const int migrationSupportEnds = 15; // Ende der Eingliederungsphase
57+
static const int pupilSince = 16; // Aufnahmedatum
58+
static const int religion = 18; // Konfession (Klartext)
59+
static const int religionLessonsSince = 19; // Religionsanmeldung
60+
static const int religionLessonsCancelledAt = 20; // Religionsabmeldung
61+
static const int family = 21; // Ausweisnummer (hijacked for family code)
62+
static const int schoolTransitionRecommendation = 22; // Übergangsempfehlung
63+
static const int leavingDate = 23; // Entlassdatum
6364
}
6465

6566
String _xlsxToPupilIdentityLines(List<int> bytes) {
@@ -108,6 +109,9 @@ String? _rowToCanonicalLine(
108109
final schoolGradeStr = cellStr(SchildExportColumns.schoolGrade).trim();
109110
final grade = schoolGradeStr.isNotEmpty ? schoolGradeStr : 'E1';
110111

112+
final migrationBg =
113+
_parseBoolCell(cellStr(SchildExportColumns.migrationBackground));
114+
111115
final parts = <String>[
112116
idVal.toString(),
113117
cellStr(SchildExportColumns.firstName),
@@ -119,6 +123,7 @@ String? _rowToCanonicalLine(
119123
cellStr(SchildExportColumns.specialNeeds2),
120124
cellStr(SchildExportColumns.gender),
121125
cellStr(SchildExportColumns.language),
126+
migrationBg ? 'true' : 'false',
122127
cellStr(SchildExportColumns.family),
123128
_normalizeDateCell(cellStr(SchildExportColumns.birthday), dateFormat),
124129
_normalizeDateCell(
@@ -157,6 +162,16 @@ String _cellValueToCanonicalString(dynamic value, DateFormat dateFormat) {
157162
return value.toString();
158163
}
159164

165+
/// Parses a cell string as bool (e.g. "ja", "j", "true", "1", "x" -> true).
166+
bool _parseBoolCell(String raw) {
167+
final s = raw.trim().toLowerCase();
168+
if (s.isEmpty) return false;
169+
if (s == 'true' || s == '1' || s == 'ja' || s == 'j' || s == 'x' || s == 'yes') {
170+
return true;
171+
}
172+
return false;
173+
}
174+
160175
/// Returns a date string (yyyy-MM-dd) or '' if [raw] is not a valid date.
161176
/// Avoids passing non-date values (e.g. category codes like "LE") into date fields.
162177
String _normalizeDateCell(String raw, DateFormat dateFormat) {

school_data_hub_flutter/lib/features/_pupil/domain/models/pupil_identity_extensions.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extension PupilIdentityExtension on PupilIdentity {
2323
specialNeeds2,
2424
gender,
2525
language,
26+
migrationBackground ? 'true' : 'false',
2627
family ?? '',
2728
birthday.formatDateForJson(normalizeUtc: false),
2829
migrationSupportEnds,
@@ -50,6 +51,7 @@ extension PupilIdentityExtension on PupilIdentity {
5051
const ListEquality<String>().equals(specialNeeds, other.specialNeeds) &&
5152
gender == other.gender &&
5253
language == other.language &&
54+
migrationBackground == other.migrationBackground &&
5355
nationality == other.nationality &&
5456
family == other.family &&
5557
birthday == other.birthday &&

school_data_hub_flutter/lib/features/_pupil/domain/models/pupil_proxy.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class PupilProxy with ChangeNotifier {
169169

170170
String? get groupTutor => _pupilIdentity.groupTutor;
171171
String? get deputyGroupTutor => _pupilIdentity.deputyGroupTutor;
172+
bool get migrationBackground => _pupilIdentity.migrationBackground;
172173

173174
DateTime? get migrationSupportEnds => _pupilIdentity.migrationSupportEnds;
174175
DateTime get pupilSince => _pupilIdentity.pupilSince;

school_data_hub_flutter/lib/features/_pupil/domain/pupil_identity_helper.dart

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class PupilIdentityHelper {
8080
normalized['deputyGroupTutor'] ??= null;
8181
normalized['nationality'] ??= null;
8282
normalized['schoolTransitionRecommendation'] ??= null;
83+
normalized['migrationBackground'] ??= false;
8384

8485
return normalized;
8586
}
@@ -120,8 +121,8 @@ class PupilIdentityHelper {
120121

121122
//- OBJECT HELPERS
122123

123-
/// Builds the reduced sync content (id,afterSchoolCare per line) from full 20-column
124-
/// newline-separated text. Column 14: OFFGANZ or non-empty -> true, else false.
124+
/// Builds the reduced sync content (id,afterSchoolCare per line) from full
125+
/// newline-separated text. Column 15: OFFGANZ or non-empty -> true, else false.
125126
/// Used when sending to backend via string transport.
126127
static String buildReducedPupilSyncContent(String fullCsvContent) {
127128
final lines = fullCsvContent.split('\n');
@@ -133,8 +134,8 @@ class PupilIdentityHelper {
133134
final id = int.tryParse(parts[0].trim());
134135
if (id == null) continue;
135136
final afterSchoolCare =
136-
parts.length > 14 &&
137-
(parts[14] == 'OFFGANZ' || parts[14].trim().isNotEmpty);
137+
parts.length > 15 &&
138+
(parts[15] == 'OFFGANZ' || parts[15].trim().isNotEmpty);
138139
reduced.add('$id,$afterSchoolCare');
139140
}
140141
return reduced.join('\n');
@@ -179,41 +180,45 @@ class PupilIdentityHelper {
179180
schoolGrade: schoolgrade,
180181
specialNeeds: _specialNeedsListFromCanonical(
181182
pupilIdentityStringItems[6], pupilIdentityStringItems[7]),
182-
deputyGroupTutor: pupilIdentityStringItems.length > 20
183-
? _emptyToNull(pupilIdentityStringItems[20])
183+
deputyGroupTutor: pupilIdentityStringItems.length > 21
184+
? _emptyToNull(pupilIdentityStringItems[21])
184185
: null,
185186
gender: pupilIdentityStringItems[8],
186187
language: pupilIdentityStringItems[9],
187-
nationality: pupilIdentityStringItems.length > 21
188-
? _emptyToNull(pupilIdentityStringItems[21])
188+
migrationBackground: _parseBoolCanonical(
189+
pupilIdentityStringItems.length > 10
190+
? pupilIdentityStringItems[10]
191+
: ''),
192+
nationality: pupilIdentityStringItems.length > 22
193+
? _emptyToNull(pupilIdentityStringItems[22])
189194
: null,
190-
family: pupilIdentityStringItems[10] == ''
191-
? null
192-
: pupilIdentityStringItems[10],
193-
birthday: pupilIdentityStringItems[11].toDateOnlyUtc(),
194-
migrationSupportEnds: pupilIdentityStringItems[12] == ''
195+
family: pupilIdentityStringItems[11] == ''
195196
? null
196-
: pupilIdentityStringItems[12].toDateOnlyUtc(),
197-
pupilSince: pupilIdentityStringItems[13].toDateOnlyUtc(),
198-
afterSchoolCare: pupilIdentityStringItems[14] != '' ? true : false,
199-
religion: pupilIdentityStringItems[15] == ''
197+
: pupilIdentityStringItems[11],
198+
birthday: pupilIdentityStringItems[12].toDateOnlyUtc(),
199+
migrationSupportEnds: pupilIdentityStringItems[13] == ''
200200
? null
201-
: pupilIdentityStringItems[15],
202-
religionLessonsSince: pupilIdentityStringItems[16] == ''
201+
: pupilIdentityStringItems[13].toDateOnlyUtc(),
202+
pupilSince: pupilIdentityStringItems[14].toDateOnlyUtc(),
203+
afterSchoolCare: pupilIdentityStringItems[15] != '' ? true : false,
204+
religion: pupilIdentityStringItems[16] == ''
203205
? null
204-
: pupilIdentityStringItems[16].tryToDateOnlyUtc(),
205-
religionLessonsCancelledAt: pupilIdentityStringItems[17] == ''
206+
: pupilIdentityStringItems[16],
207+
religionLessonsSince: pupilIdentityStringItems[17] == ''
206208
? null
207209
: pupilIdentityStringItems[17].tryToDateOnlyUtc(),
208-
familyLanguageLessonsSince: pupilIdentityStringItems[18] == ''
210+
religionLessonsCancelledAt: pupilIdentityStringItems[18] == ''
209211
? null
210212
: pupilIdentityStringItems[18].tryToDateOnlyUtc(),
211-
leavingDate: pupilIdentityStringItems[19] == ''
213+
familyLanguageLessonsSince: pupilIdentityStringItems[19] == ''
212214
? null
213215
: pupilIdentityStringItems[19].tryToDateOnlyUtc(),
216+
leavingDate: pupilIdentityStringItems[20] == ''
217+
? null
218+
: pupilIdentityStringItems[20].tryToDateOnlyUtc(),
214219
schoolTransitionRecommendation:
215-
pupilIdentityStringItems.length > 22
216-
? _emptyToNull(pupilIdentityStringItems[22])
220+
pupilIdentityStringItems.length > 23
221+
? _emptyToNull(pupilIdentityStringItems[23])
217222
: null,
218223
);
219224

@@ -233,6 +238,11 @@ class PupilIdentityHelper {
233238
return t.isEmpty ? null : t;
234239
}
235240

241+
static bool _parseBoolCanonical(String s) {
242+
final t = s.trim().toLowerCase();
243+
return t == 'true' || t == '1' || t == 'ja' || t == 'j' || t == 'x' || t == 'yes';
244+
}
245+
236246
Future<String> generateEncryptedPupilIdentitiesTransferString(
237247
List<int> internalIds,
238248
) async {

school_data_hub_flutter/lib/features/_pupil/presentation/pupil_profile_page/widgets/pupil_profile_page_content/language_content/pupil_language_card.dart

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import 'package:flutter/material.dart';
2-
import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart';
2+
import 'package:flutter_it/flutter_it.dart';
3+
import 'package:gap/gap.dart';
34
import 'package:school_data_hub_flutter/features/_pupil/domain/models/pupil_proxy.dart';
45
import 'package:school_data_hub_flutter/features/_pupil/presentation/pupil_profile_page/pupil_profile_page.dart';
56
import 'package:school_data_hub_flutter/features/_pupil/presentation/pupil_profile_page/widgets/pupil_profile_navigation.dart';
67
import 'package:school_data_hub_flutter/features/_pupil/presentation/widgets/avatar.dart';
7-
import 'package:flutter_it/flutter_it.dart';
8+
import 'package:school_data_hub_flutter/features/app_main_navigation/domain/main_menu_bottom_nav_manager.dart';
89

910
class PupilLanguageCard extends StatelessWidget {
1011
final PupilProxy passedPupil;
@@ -26,7 +27,7 @@ class PupilLanguageCard extends StatelessWidget {
2627
ProfileNavigationState.language.value,
2728
);
2829
Navigator.of(context).push(
29-
MaterialPageRoute(
30+
MaterialPageRoute<void>(
3031
builder: (ctx) => PupilProfilePage(pupil: pupil),
3132
),
3233
);
@@ -44,12 +45,27 @@ class PupilLanguageCard extends StatelessWidget {
4445
),
4546
overflow: TextOverflow.ellipsis,
4647
),
47-
Text(
48-
'Familiensprache: ${pupil.language}',
49-
style: const TextStyle(
50-
fontSize: 14,
51-
fontWeight: FontWeight.bold,
52-
),
48+
const Gap(5),
49+
Row(
50+
children: [
51+
const Text('Familiensprache:'),
52+
const Gap(5),
53+
Text(
54+
pupil.language,
55+
style: const TextStyle(fontWeight: FontWeight.bold),
56+
),
57+
],
58+
),
59+
const Gap(5),
60+
Row(
61+
children: [
62+
const Text('Staatsangehörigkeit:'),
63+
const Gap(5),
64+
Text(
65+
pupil.nationality ?? 'Kein Eintrag',
66+
style: const TextStyle(fontWeight: FontWeight.bold),
67+
),
68+
],
5369
),
5470
],
5571
),

0 commit comments

Comments
 (0)